Svelte Navigator History
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.
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 callinghistory.go
. -
"PUSH"
: A new entry has been pushed to the history stack, by callinghistory.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 whenhistory.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