cdellacqua / svelte-stack-router

Bridging the gap between Native Apps and WebApps. A Svelte Router that caches page components and will make your WebApp feel more native
MIT License
40 stars 6 forks source link

svelte-stack-router

Bridging the gap between Native Apps and WebApps. A Svelte Router that caches page components and will make your WebApp feel more native

NPM Package

npm install svelte-stack-router

Working demo

svelte-stack-router demo

The demo files can be found in src/

Quick setup

Example:

App.svelte

<script>
    import { StackRouter } from 'svelte-stack-router';
    import Home from './Home.svelte';
    import NotFound from './NotFound.svelte';

    const routes = {
        "/": Home,
        "*": NotFound
    };
</script>
<StackRouter {routes} />

How it works

Page components are cached, this ensures that going back in the browser history resumes the complete previous state.

In other words: previously instantiated pages don't get destroyed by default, they just get paused and resumed to reduce re-renders and preserve their full state.

Note that a cached page is uniquely identified by its route template (i.e. the key in the routes/myRoutes objects in the examples above), not by the component.

Enhanced lifecycle functions

In addition to the onMount and onDestroy lifecycle functions provided by Svelte, this library offers onPause, onResume, onBeforeLoad, onBeforeUnload, onAfterLoad and onAfterUnload.

All these new lifecycle functions accept synchronous and asynchronous callbacks. In case of asynchronous callbacks they are executed one by one in the same order they were registered.

The complete lifecycle is (|| = semi-parallel execution, achieved with Promise.all):

All these additional lifecycle functions can be called by the Page component and by its children during initialization, this means they should be invoked directly in the script section of the Svelte component (e.g. not inside onMount or in reactive statements).

If you have a component which shouldn't be paused or resumed by the StackRouter, you can call setResumable(false)

Doing this will make your component disposable, so that it will be mounted and destroyed and never paused or resumed.

Notes on StackRouter destruction

When the <StackRouter /> components gets unmounted (e.g. if it is inside an {#if ...}), all the components that were previously cached by it will be destroyed. In particular, if the currently active component has registered any onBeforeUnload, onPause or onAfterUnload callbacks, those will get executed before the destruction of the component with a boolean parameter set to true to signal the urgency to clean up.

<script>
    onAfterUnload((force) => {
        if (force) {
            // non interactive operations
        } else {
            // asks for a user interaction (e.g. confirmation modal)
        }
    });
</script>

Router events

You can listen for the following events:

Example:

<StackRouter {routes} on:navigation-start={() => alert('navigation started')} />

Navigation functions, links and stores

Programmatic navigation

The following functions enable programmatic navigation:

Links

This library also provides a custom use:link action that you can add to your <a> elements to create links. This action serves two purposes:

Stores

To easily access information about the current route, you can import the following stores:

For example, if you are on a page like http://localhost:5000/#/home/some-page/?someParam=a%20string, the following comparisons will be true:

These features have been heavily inspired by svelte-spa-router.

Returning values

When the pop function is called it can receive an optional parameter, which acts like a return value.

This value will be passed on as an argument to all the callback functions that are registered in the onResume hook of the component that is about to be resumed, thus allowing two components to communicate with each other.

For example:

For example:

Customizing behavior

The <StackRouter> component supports a variety of options:

name type description default
defaultResumable boolean whether or not the default behavior should be to resume or recreate the components true
useHash boolean whether or not to prefix routes with '#' to implement a server-agnostic client side routing (e.g. no need to redirect 404 to index) true
restoreScroll boolean whether or not to restore the scroll position when navigating backwards true
transitionFn TransitionFunction a function that handles the transition between two pages dive(300)
routes Record.<string, SvelteComponentConstructor> a key-value object associating a route path (e.g. '/a/route/path/:variable1?) to a SvelteComponent constructor N/A - required

Advanced routing

This router supports guards and asynchronously provided components (e.g. when using lazy loading).

Guards are functions that can either return a boolean or a Promise<boolean>. They are called sequentially and the first one to return (or resolve to) false causes a navigation error ("access forbidden by guard").

If a route has some parameters, the guard will receive them before the component.

Guard type:

export type Guard = (paramsPreview?: Params) => boolean | Promise<boolean>;

Here are some examples:

TransitionFunction and available transitions

This library provides 3 types of transitions between pages:

You can also implement a custom transition animation by implementing a transition function that reflect the following type definition:

/**
 * A function that handles the transition between two pages
 * @param {NavigationType} data.navigationType describes the navigation that occurred (e.g. backward, replace, forward, ...)
 * @param {HTMLElement} data.mountPointToLoad the mount point of the page that is being loaded
 * @param {HTMLElement} data.mountPointToUnload the mount point of the page that is being unloaded
 * @param {HTMLElement} data.routerMountPoint the router mount point, when this function is called it contains both the mountPointToLoad and the mountPointToUnload
 * @param {{x: number, y: number}} data.scroll if scroll restoration is enabled and the current component is being resumed, this object contains the x and y coordinates needed to bring the window scrollbars back to where they were when the component was paused
 * @return {Promise} a promise that resolves once the transition has finished
 */
export type TransitionFunction = (data: TransitionFunctionData) => Promise<void>;

You can also generate a TransitionFunction using the helpers provided in transition-functions.js