npm

@svelte-navigator/history
TypeScript icon, indicating that this package has built-in type declarations

0.5.0 • Public • Published

Svelte Navigator History

npm package npm bundle size NPM GitHub last commit Code Style Prettier Build Status

History module for svelte-navigator. It abstracts the management of the apps location using either the HTML5 History API, the hash fragment of the URL or an in-memory mode.

⚠️⚠️⚠️ This is an experimental package, that will be used for the next version of svelte-navigator. ⚠️⚠️⚠️

Table of Contents

Build requirements

Svelte Navigator History depends on a build process in which certain environment variables are replaced. This is necessary in order to provide descriptive error message in development, while keeping the production bundles lean.

If you're using rollup you can use @rollup/plugin-replace like so:

// rollup.config.js
import replace from "@rollup/plugin-replace";

const isDev = Boolean(process.env.ROLLUP_WATCH);
const nodeEnv = isDev ? "development" : "production";

export default {
	// ...
	plugins: [
		// ...
		replace({ "process.env.NODE_ENV": JSON.stringify(nodeEnv) }),
	],
};

If you're using webpack you can use webpack.DefinePlugin:

// webpack.config.js
const webpack = require("webpack");

module.exports = {
	// ...
	plugins: [
		// ...
		new webpack.DefinePlugin({
			"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
		}),
	],
};

If you don't have access to your build pipeline, @svelte-navigator/history provides pre-built development and production bundles you can use. Each are available as es-module and umd bundle. Import the desired format from the dist folder:

import * as History from "@svelte-navigator/history/dist/history.development.mjs";
import * as History from "@svelte-navigator/history/dist/history.production.mjs";
import * as History from "@svelte-navigator/history/dist/history.development.umd.js";
import * as History from "@svelte-navigator/history/dist/history.production.umd.js";

API

NavigatorHistory

Each History implements the following interface:

interface NavigatorHistory<State = unknown> {
	push: (uri: string, state?: State | null) => void;
	replace: (uri: string, state?: State | null) => void;
	go: (delta: number) => void;
	navigate: NavigateFn<State>;

	readonly location: NavigatorLocation<State>;
	readonly action: Action;

	subscribe: (subscriber: Subscriber<HistoryUpdate<State>>) => () => void;
	createHref: (to: string) => string;
	release: () => void;
}

push

Calling history.push navigates to a new URL and optionally pushes a value to the history stack. The state is an arbitrary (serializable) value. It can be thought of as something like the message body of an http post request. It contains details which are not visible in the URL, but are bound to the specific request.

// Navigate to "/blog?id=123"
history.push("/blog?id=123");

// Navigate to "/about" and push an object to the stack, which can
// be read by accessing `history.location.state`
history.push("/about", { from: "/blog" });

replace

Calling history.replace replaces the current URL and optionally the value of the history stack.

// Replace current entry, which could be a protected route, with "/login"
history.replace("/login");

// Save the location we're coming from, so we can navigate back, when
// login was successfull
history.replace("/login", { from: history.location });

go

history.go allows you to navigate the history stack, similar to using the back and forward buttons of the browser.

const goBack = () => history.go(-1);
const goForward = () => history.go(1);

navigate

history.navigate is a convenience method, that combines the functionality of push, replace and go.

// Go to "/blog?id=123"
history.navigate("/blog?id=123");

// Replace current entry and go to "/login".
// Save the location we're coming from, so we can navigate back,
// when login was successfull
history.navigate("/login", {
	state: { from: history.location },
	replce: true,
});

// Go back one entry in the history stack
history.navigate(-1);

location

The location represents the current state of the URL. It is very similar to the built-in window.location, in that it has properties for the pathname, search and hash of the URL. It can however also carry a state, that can be set when changing location.

A location looks like this:

interface NavigatorLocation<State = unknown> {
	pathname: string;
	// `search` and `hash` are `""`, when they are not present in the URL.
	// When they are, they begin with a `"?"` or a `"#"` respectively
	search: string;
	hash: string;
	state: State | null;
	// `key` is a unique id for each location entry. It can be used to
	// reference a location entry elsewhere, e.g. when storing entries
	// in `localStorage`
	key: string;
}

action

The action represents what event lead to the change in location. Possible values are:

  • "POP": The default value. It is caused by hitting the back or forward button of the browser, or by calling history.go.
  • "PUSH": A new entry has been pushed to the history stack, by calling history.push. This is what you want to use when navigating programmatically in your app.
  • "REPLACE": An entry of the history stack has been replaced with another one. This is caused when history.replace has been called.

subscribe

You can register an event handler by calling history.subscribe. The handler you pass to subscribe is called with the location and action of the latest navigation. It is also called when the subscriber is registered. subscribe returns an unsubscribe function, which when called removes the event subscriber.

const unsubscribe = history.subscribe(({ location, action }) => {
	const url = `${location.pathname}${location.search}${location.hash}`;
	console.log(`Action: ${action}; Url: ${url}`);
});

// ... do something ...

// Remove the subscriber
unsubscribe();

createHref

history.createHref takes a path and returns a string, you can use as a href attribute of an <a> element. For hash routing for example, a "#" has to be prepended to the beginning of a path.

release

A history might need to attach some global event handlers to be able to react to changes in location. If you're for whatever reason creating lots of history instances (which is probably a bad idea...), you should call history.release when you don't need the instance anymore. It will unregister all event handlers and allow the instance to be garbage collected, preventing memory leaks.

Browser History

Browser history uses the HTML5 History API to store app's location and state in the URL. This is probably the best choice for most apps, as it enables best SEO possibilities and will be most intuitive for most users. This setup however needs some additional work because you need to configure your server to always serve your index.html file when a request doesn't match a file. You can read more about it in vue-routers docs about history routing.

createBrowserHistory

Create an instance of a browser history. It will update the URL when the app's location changes. It will also listen to navigation events dispatched by the browser after the back and forward buttons have been clicked.

When using createBrowserHistory don't interact with the browsers history object yourself. It will lead to inconsistent states and you won't have a good time debugging that... Also, always use the created history instance to read the current location, and don't use the browsers location object. Again inconsistent location states aren't fun.

import { createBrowserHistory } from "@svelte-navigator/history";

const history = createBrowserHistory();

history.subscribe(console.log);

history.navigate("/blog?id=123");

Hash History

Hash history uses the hash fragment of the URL to simulate routing via URL. This approach is great if you want to get started quickly or if you don't have access to your server's configuration.

createHashHistory

Create an instance of a hash history. It will change the hash of the URL when the app's location changes.

Again, you should not interact with the global history or location objects yourself.

import { createHashHistory } from "@svelte-navigator/history";

const history = createHashHistory();

history.subscribe(console.log);

history.navigate("/blog?id=123");

Memory History

Memory history keeps the location state of your app in memory. This is mainly useful for testing if you don't run your tests in a browser. You could also use a memory history if you want to control the state of a widget you embed in another app using a router.

createMemoryHistory

Create an instance of a hash history. It will change the hash of the URL when the app's location changes.

Again, you should not interact with the global history or location objects yourself.

import { createMemoryHistory } from "@svelte-navigator/history";

const history = createMemoryHistory();

history.subscribe(console.log);

history.navigate("/blog?id=123");

parsePath

Create a location object from a URL string.

import { parsePath } from "@svelte-navigator/history";

const path = "/search?q=falafel#result-3";
const location = parsePath(path);
// -> {
//   pathname: "/search",
//   search: "?q=falafel",
//   hash: "#result-3",
// };

stringifyPath

Joins a location object to one path string.

import { stringifyPath } from "@svelte-navigator/history";

const location = {
	pathname: "/search",
	search: "?q=falafel",
	hash: "#result-3",
};
const path = stringifyPath(location);
// -> "/search?q=falafel#result-3"

createNavigate

Create the createNavigate convenience method expected in the NavigatorHistory interface. Pass a Partial NavigatorHistory object, which implements the push, replace and go methods, to the factory function.

import { createNavigate } from "@svelte-navigator/history";

const customHistory = {
	push(uri, state) {
		console.log("PUSH");
	},
	replace(uri, state) {
		console.log("REPLACE");
	},
	go(delta) {
		console.log("GO");
	},
};

customHistory.navigate = createNavigate(customHistory);

License

MIT

Package Sidebar

Install

npm i @svelte-navigator/history

Weekly Downloads

1

Version

0.5.0

License

MIT

Unpacked Size

143 kB

Total Files

28

Last publish

Collaborators

  • svelte-navigator