npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@webruntime/navigation

v1.66.8

Published

LWR Client-side Navigation APIs

Downloads

1,757

Readme

Lightning Web Runtime :: Navigation and Routing

Table of contents

Introduction

This is a client-side routing package for off-core LWC-based applications, from Lightning Web Runtime (Webruntime).

It supplies an API with the ability to create a router, navigate, generate URLs and subscribe to navigation events.

Routers can be customized at various plug points, or used as-is. Routers can also be nested, to create a hierarchy on the page.

Built on top of the Webruntime routing APIs is a Lightning Navigation layer. It provides implementations for the NavigationMixin and CurrentPageReference wire adapter from lightning/navigation. This allows a component to be written once, and plugged in anywhere that uses the lightning/navigation contracts.

A consumer of this package can write an application that uses the Webruntime routing APIs, the Lightning Navigation layer, or both. Customer components or applications will most likely use the Lightning Navigation API, as our public-facing contract.

Background

Definitions

These concepts are used throughout this documentation. All examples are using this URL:

https://www.somewhere.com/case/10?param1=one&param2=two&param3
  • URL: A browser URL, either absolute or relative, containing both the path and query parameters (e.g.: https://www.somewhere.com/case/10?param1=one&param2=two&param3 or /case/10?param1=one&param2=two&param3)
  • path: The pathname of a URL (e.g.: /case/10)
  • absolute path: A path that contains the entire pathname of a URL (e.g.: /case/10 NOT /10)
  • query string: The query part of a URL (e.g.: ?param1=one&param2=two&param3)
  • query object: An object representing a parsed query string, e.g.:
{
    "param1": "one",
    "param2": "two",
    "param3": ""
}
  • route definition: An object used to describe a URL category or pattern. A shape containing a unique id and an optionally parameterized path is used. Path-to-regexp is used to parse the path. To support lightning/navigation APIs, add the page property. Additional custom data may also be included, e.g.:
{
    /* required data */
    "path": "/case/:recordId/:optional?", // Uses path-to-regexp for parsing parameters.
    "exact": true/false, // When true, will only match if the path matches location.pathname exactly. The default is `true`.

    /* Basic routing */
    "id": "case-detail",

    /* lightning/navigation */
    "page": {
        "type": "standard__recordPage",
        "attributes": {
            "objectApiName": "Case"
        }
    },

    /* custom data */
    "view": "caseDetail"
    "label": "Case Detail"
}

Note: More information about route definition matching can be found here.

  • route: An object representing the current URL. The id comes from the matching route definition. The attributes property contains the parameters parsed from the URL using the route definition path. The state property contains the query object, e.g.:
{
    "id": "case-detail",
    "attributes": {
        "objectApiName": "Case",
        "recordId": "place"
    },
    "state": {
        "param1": "one",
        "param2": "two",
        "param3": ""
    }
}
  • page reference: A special type of route object used for lightning/navigation. The type property is used instead of the id, e.g.:
{
    "type": "standard__recordPage",
    "attributes": {
        "objectApiName": "Case",
        "recordId": "place"
    },
    "state": {
        "param1": "one",
        "param2": "two",
        "param3": ""
    }
}
  • simple route: When a URL does not match any route definitions, a default route containing the absolute path and query object is produced by the router, e.g.:
{
    "type": "standard__simpleRoute",
    "attributes": {
        "path": "/case/10"
    },
    "state": {
        "param1": "one",
        "param2": "two",
        "param3": ""
    }
}
  • navigation event: The navigation state is changing, either by a browser URL update, or programmatically.
  • navigation context: When a component provides navigation context, it implements the functions necessary to support the Webruntime routing APIs for its descendent components.
  • router: A piece of code that manages navigation changes. A router can have up to 1 direct child router. A router provides navigation context.
  • root router: The router instance that sits highest up in the DOM hierarchy. Updates flow from the root router to its child router, then grandchild router, etc. Some applications may only have a root router.
  • translation layer: The functions a router uses to translate a URL to a route, and a route to a URL.

Prerequisites

The navigation package has a dependency upon the LWC wire-service. To include the wire-service:

  1. Preload the wire-service in webruntime-app.config.js:
preloadModules: ['wire-service'],
  1. Register the wire-service in the root component of the application:
import { LightningElement, register } from 'lwc';
import { registerWireService } from 'wire-service';

registerWireService(register);

export default class RootApp extends LightningElement {
    // ...
}

Webruntime routing APIs

The Webruntime routing APIs supply functions to create a root router, navigate (programmatically and declaratively), generate URLs, and subscribe to navigation events.

// app.js
import { createRouter, navigate, generateUrl, subscribe, NavigationContext } from 'webruntime/navigation';

<!-- component.html -->
<webruntime-link path="/some/where">CLICK ME</webruntime-link>`

createRouter()

const router = createRouter(config);

Create a new root router for an application with arguments:

  • config: Configure the router with an object containing these properties:
    • basePath: Set a base path for this router. The default is an empty string.
    • routes: Set an array of route definitions for URL parsing. The default is an empty array.
    • noHistory: By default, the router will manage the browser history. To turn this behavior off, set noHistory: true.
    • caseSensitive: When true, the route definition path matching will be case sensitive. The default is false.

A router object is returned:

  • connect(): Call this to start the router.
  • disconnect(): Call this to stop the router from interacting with the application.
  • addPreNavigate(function | function[]) -> this router: Add a listener function, or array of functions, to the pre navigate event hook.
  • addPostNavigate(function | function[]) -> this router: Add a listener function, or array of functions, to the post navigate event hook.
  • addErrorNavigate(function | function[]) -> this router: Add a listener function, or array of functions, to the navigation error hook.
  • id: The navigation context ID for this router.

Lifecycle hooks

A router can have 0 or more listeners attached to each of its lifecycle hooks. The listeners are fired synchronously in the order in which they were attached:

  • preNavigate(transaction): This runs during a navigation event, before the state is changed. Listeners can stop the transaction at this point. If stopped, the errorNavigate listeners will be fired. The preNavigate functions should return one of these values:
    • true: The route should be processed.
    • false: The navigation event should be stopped, and no more listeners run on any router.
    • Promise: Resolves to one of the values above; rejected Promises are treated as false.
  • postNavigate(transaction): This runs after a navigation event completes. Subscribers will not be notified until all post navigation listeners have finished executing. If a postNavigate listener returns false or a rejected Promise, the remaining listeners for that router are skipped, but the postNavigate listeners on any descendent routers will still run.
  • errorNavigate(error): This runs if the router encounters an error trying to navigate.

The pre and post navigation listens are passed a read-only transaction object:

  • current: The navigation information { route, data } for the current state, route may be null during preNavigate.
  • next (preNavigate only): The navigation information that will load next (if not stopped).
  • previous (postNavigate only): The navigation information that just unloaded, route may be null.

The postNavigate listeners are passed an error object:

{
    code: an integer error code,
    message: a string error message,
    level: 1,
    url: an optional URL where more information on the error can be found
}

See an example here.

NavigationContext

@wire(NavigationContext)
navContext;

A wire adapter that gives a component access to the its navigation context. The navigation context is used to call the Webruntime routing APIs below.

navigate()

navigate(navigationContext, location, shouldReplace);

Navigate to a new page programmatically with arguments:

  • navigationContext: The navigation context in which to navigate.
  • location: A route, or absolute path, to navigate to.
  • shouldReplace: Send true if the new location should replace the current one in the browser history. The default is false.

generateUrl()

generateUrl(navigationContext, route).then((url) => this.path = url);

Given a route, returns a Promise to a string URL.

subscribe()

const callback = (route, data) => console.log(route, data);
this.subscription = subscribe(navigationContext, callback);
// .....
if (this.subscription) {
    this.subscription.unsubscribe();
}

Pass in a callback function that will get executed every time the navigation state changes. Receive a Promise to an observer with an unsubscribe() function in return.

The callback function takes these arguments:

  • route: The new current route.
  • data: Extra data associated with the current route. In the default implementation, this is the route definition which matches the current route. It will be null in the case of Simple Route (i.e.: no match).

webruntime-link

A LWC used for declarative navigation. It can take either a string URL or a route as input.

<webruntime-link path="/a/path?filter=all">
    <span>CLICK HERE</span>
</webruntime-link>
<webruntime-link route="{route}">
    <img src="/my/link.gif" alt="click me" />
</webruntime-link>

The following CSS variables are used to style the internal a tag. Overwrite the values to update the look of the links.

a {
    color: var(--webruntime-link-color);
    font-size: var(--webruntime-link-font-size);
    text-decoration: var(--webruntime-link-text-decoration);
}
a:hover,
a:active {
    color: var(--webruntime-link-color-active, var(--webruntime-link-color));
    font-size: var(--webruntime-link-font-size-active, var(--webruntime-link-font-size));
    text-decoration: var(--webruntime-link-text-decoration-active);
}

Usage examples

Creating a root router with hooks

import { createRouter } from 'webruntime/navigation';
const ROUTE_DEFINITIONS = [
    {
        id: 'recordPage',
        path: '/r/:objectApiName/:recordId/:actionName',
    },
    {
        id: 'objectPage',
        path: '/o/:objectApiName/:actionName',
    },
    {
        id: 'homePage',
        path: '/', // This is the default route, and it must be last.
    },
];

const router = createRouter({ basePath: '/demo', routes: ROUTE_DEFINITIONS });

// Add logger pre and post navigation hook listeners. Then connect.
router
    .addPreNavigate((t) => console.log(`pre: Current: %o. Next: %o`, t.current.route, t.next.route))
    .addPostNavigate((t) =>
        console.log(`post: Current: %o. Previous: %o`, t.current.route, t.previous.route)
    )
    .addErrorNavigate((e) =>
        console.error(`There was a problem during navigation: ${e.code} :: ${e.message}`)
    )
    .connect();

Using the webruntime/navigation APIs

import { track, wire, LightningElement } from 'lwc';
import { navigate, generateUrl, subscribe, NavigationContext } from 'webruntime/navigation';

const aRoute = {
    id: 'page',
    attributes: {
        name: 'sample',
    },
};

export default class Example extends LightningElement {
    @track name = '';
    @track path = null;
    subscription = null;

    // Get a reference to the navigation context for this component.
    @wire(NavigationContext)
    navContext;

    // Subscribe to updates on the current state.
    connectedCallback() {
        this.subscription = subscribe(this.navContext, (route, data) => {
            this.name = route.attributes.name || '';
            this.path = data ? data.path : null;
        });
    }

    // Navigate programmatically by URL.
    navUrl() {
        navigate(this.navContext, '/some/path');
    }

    // Navigate programmatically by route.
    navRoute() {
        navigate(this.navContext, aRoute);
    }

    // Generate a URL for a route.
    getUrl() {
        generateUrl(this.navContext, aRoute).then((url) => console.log(url));
    }

    // Disconnect from the navigation event subscription.
    disconnectedCallback() {
        if (this.subscription) {
            this.subscription.unsubscribe();
        }
    }
}

Customize a router

A router has plug-points that accept logic to override the default behavior. The translation layer (route <-> URL) and navigation event handling can be customized. Pass the plug-points in as properties on a router's config object.

Translation layer plug-points

  • getRouteFromUrl: f(url, defaultImpl) -> { route, data }: Given a URL (absolute or relative), return the associated route and data. If a route could not be found to match the given URL then route: null.
  • getUrlFromRoute: f(route, defaultImpl) -> { url, data }: Given a route, return the associated relative URL string with an absolute path and data. If a URL could not be created for the given route then url: null.

Things to note:

  • Returning null for a url or route will result in the errorNavigation hook listeners being call on all routers.
  • If a plug-point is not provided, a default implementation is used. The default implementation function is provided as a second argument to the plug-point functions for convenience.
  • The default implementation of getRouteFromUrl will never return route: null. A Simple Route is returned for URLs that do not match any route definition. See this example for turning off Simple Routes.

Navigation event plug-points

  • handleNavigation: f(url | route, shouldReplace) -> boolean: This is called whenever a navigation event bubbles up through a router. Return false to stop propagation and cancel the event. This function is a no-op by default. The arguments are:
    • url | route: The string URL or route object passed in from navigate().
    • shouldReplace: true if the URL should replace the current one in the browser history.

Usage examples

Customizing a root router

import { LightningElement } from 'lwc';
import { createRouter } from 'webruntime/navigation';
import ROUTE_DEFINITIONS from './routeDefinitions';

export default class Example extends LightningElement {
    constructor() {
        super();
        this.router = createRouter({
            basePath: '/demo',
            routes: ROUTE_DEFINITIONS,
            handleNavigation: this.handleNavigation.bind(this),
            getRouteFromUrl: this.getRouteFromUrl.bind(this),
            getUrlFromRoute: this.getUrlFromRoute.bind(this),
        });
        this.router.connect();
    }

    disconnectedCallback() {
        this.router.disconnect();
    }

    // Process a navigation event bubbling up from descendent components.
    // The getRouteFromUrl function is provided for convenience.
    // This example stops propagation if the route id is 'no__good'.
    handleNavigation(input, options, getRouteFromUrl) {
        const route = typeof input === 'string' ? getRouteFromUrl(input).route : input;
        return route.id !== 'no__good';
    }

    // Return null as the route instead of Simple Routes.
    // This turns off the Simple Route feature.
    // The errorNavigate hook listeners are run when { route: null }
    getRouteFromUrl(url, defaultImpl) {
        const defaultInfo = defaultImpl(url);
        return defaultInfo.route.type === 'standard__simpleRoute'
            ? { route: null, data: null }
            : defaultInfo;
    }

    // Set a sticky query parameter on every URL.
    getUrlFromRoute(route, defaultImpl) {
        const { url, routeDef } = defaultImpl(route);
        return {
            url: url ? `${url}?new=param` : null,
            data: routeDef,
        };
    }
}

Nesting routers

Multiple routers can be nested, with each router having up to 1 child. Each router is responsible for parsing part of the URL path, and providing the associated route to its descendent subscribers.

webruntime-router

<webruntime-router
    routes="{routes}"
    base-path="/base"
    no-history
    case-sensitive
    onprenavigate="{preNavigate}"
    onpostnavigate="{postNavigate}"
    onerrornavigate="{errorNavigate}"
    onhandlenavigation="{handleNavigation}"
></webruntime-router>

Create a child router declaratively with this LWC. Pass the config options to the router via @api properties:

  • routes
  • base-path
  • no-history
  • case-sensitive

Attach hooks with event listeners:

  • onprenavigate is cancelable
  • onpostnavigate
  • onerrornavigate
  • onhandlenavigation is cancelable

Usage examples

Create and customize a webruntime-router

<template>
    <webruntime-router
        routes="{routes}"
        base-path="/demo"
        onprenavigate="{preNavigate}"
        onpostnavigate="{postNavigate}"
        onerrornavigate="{errorNavigate}"
        onhandlenavigation="{handleNavigation}"
    >
        <template if:true="{message}"><error>{message}</error></template>
        <webruntime-link path="/a/place">I'm inside the child navigation context</webruntime-link>
        <view></view>
    </webruntime-router>
</template>
import { track, LightningElement } from 'lwc';
import ROUTE_DEFINITIONS from './routeDefinitions';
import { user } from '../services/user';
import { getData } from '../services/xhr';

export default class Example extends LightningElement {
    @track errorMessage = null;
    routes = ROUTE_DEFINITIONS;

    // Add lifecycle hook events.
    preNavigate(e) {
        e.stopPropagation();
        // If any pre-navigate hook returns false, cancel the event.
        if (!this.authorization(e.detail) || !this.unmountCheck(e.detail)) {
            e.preventDefault();
        }
    }
    postNavigate(e) {
        e.stopPropagation();
        this.logNav(e.detail);
        this.getData(e.detail);
    }
    errorNavigate(e) {
        e.stopPropagation();
        this.showError(e.detail);
        this.getData(e.detail);
    }
    handleNavigation(e) {
        e.stopPropagation();
        const { input } = e.detail;
        console.log('Bubbling navigation event: ', input);
    }

    // Do not allow navigation to private pages for guest users.
    authorization(transaction) {
        if (user.isGuest() && transaction.next.route.id === 'private-page') {
            return false;
        }
        return true;
    }

    // Warn users before leaving the current page.
    // !transaction.current.route indicates a first time page load.
    unmountCheck(transaction) {
        return !transaction.current.route
            ? true
            : new Promise((resolve) => {
                  resolve(confirm('Are you sure you want to leave?'));
              });
    }

    // Log navigation events that just completed.
    // Clear the error message.
    logNav(transaction) {
        console.log(
            `postNav -> Current: %o. Previous: %o`,
            transaction.current.route,
            transaction.previous.route
        );
        this.errorMessage = null;
    }

    // Get some XHR data before the subscribers are notified of this navigation event.
    getData(transaction) {
        return getData(transaction.current.route.id).then((data) => doSomething(data));
    }

    // Show navigation errors to the user.
    showError(e) {
        this.errorMessage = `There was a problem during navigation: ${e.message}`;
    }
}

Root router and webruntime-router relationship

When a child router is added, there are two navigation context providers present in the application. Consider a root router with these properties:

basePath: '/lightning';
routes: [
    {
        id: 'app',
        path: '/app/:appName',
        exact: false, // This allows the child router at this route definition
    },
];

a child router with:

basePath: '';
routes: [
    {
        id: 'page',
        path: '/page/:name',
    },
];

and a URL like this: /lightning/app/cat+app/page/siamese?cute=yes

A successful navigation event looks like this:

  1. A navigation event is detected, and the preNavigate hooks are fired from root -> leaf router node.
  2. The root router matches the first 3 segments of the path and produces this route:
{
    "id": "app",
    "attributes": {
        "appName": "cat app"
    },
    "state": {
        "cute": "yes"
    }
}
  1. The route is sent to the subscribers of the root router navigation context.
  2. The root router delegates to the child router by sending it the absolute path of the URL.
  3. The child router matches the last 2 segments of the path and produces this route:
{
    "id": "page",
    "attributes": {
        "name": "siamese"
    },
    "state": {
        "cute": "yes"
    }
}
  1. The postNavigate hooks are fired on the root and its subscribers are notified.
  2. The postNavigate hooks are fired on the child and its subscribers are notified.

Note: Subscribers receive different data depending on their navigation context, provided by the closest ancestor router.

Using Router Views

The LWR Routing Service allows a lwc to be specified for each route definition. LWR also provides a component which automatically displays the lwc associated with each route. To use the Routing Service, add it to the services array in webruntime-app.config.js:

const { RoutingService } = require('@webruntime/navigation');
module.exports = {
    services: [RoutingService],
};

Setup route definitions

Create one or more JSON files in a LWR project to hold route defintion data:

projectRoot
├── routes/
│   └── cooking.json    // route set id = "cooking"
│   └── child.json      // route set id = "child"

Note: Multiple files are created to support nested routers.

Each file holds a "route set" with an ID matching the filename. The route set JSON looks like this:

{
    "home": {
        "path": "/",
        "component": "x/home"
    },
    "recipe": {
        "path": "/recipes/:title",
        "component": "x/recipeItem"
    }
}

Note: The route definition IDs are keys in this object. This automatically supports ID uniqueness across the route set.

Generate a router

The LWR Routing Service generates a router component, given a route set ID:

<!-- x/app template -->
<template>
    <!-- Add a router to the application template
         This router receives the data from cooking.json above -->
    <webruntime-router-cooking></webruntime-router-cooking>
</template>

The webruntime-router-{setID} components support the same properties and events as the webruntime-router component, besides routes which is passed automatically given the route set ID.

Add a router outlet

The route sets contain a component for each route definition. Add a webruntime-outlet component under a router to automatically display the component on route change:

<!-- x/app template -->
<template>
    <!-- Add a router to the application template
         This router receives the data from cooking.json above -->
    <webruntime-router-cooking>
        <!-- Add an outlet as a child to the router
             This renders the current component view -->
        <webruntime-outlet>
            <span slot="error">
                <x-error></x-error>
            </span>
            <span slot="404">
                <x-404></x-404>
            </span>
        </webruntime-outlet>
    </webruntime-router-cooking>
</template>

The webruntime-outlet component contains:

  • slots:
    • "error": Shows when there is an error loading the component for the route.
    • "404": Shows when the component could not be resolved.
  • properties:
    • refocus-off: The outlet automatically puts focus on the component when it is loaded, for accessibility. To turn this feature off, add the refocus-off property to webruntime-outlet

The attributes for the current route are automatically passed into the corresponding component as public properties (@api). Given this route:

{
    "id": "recipe",
    "attributes": {
        "title": "bread"
    }
}

and the "x/recipeItem" component:

import { LightningElement, api } from 'lwc';
export default class XRecipeItem extends LightningElement {
    @api title;
}

the router outlet would pass in "bread" for the title property:

<x-recipe-item title="bread"></x-recipe-item>

Lightning Navigation

The webruntime/lightningNavigation package provides implementations for the NavigationMixin and CurrentPageReference wire adapter from lightning/navigation.

import { NavigationMixin, CurrentPageReference } from 'webruntime/lightningNavigation';

CurrentPageReference

A wire adapter that gives a component access to the current page reference (relative to its navigation context).

NavigationMixin

A JavaScript class mixin that provides functions to navigate or generate a URL:

  • this[NavigationMixin.Navigate](url | pageRef): Programmatically navigate to a string URL or page reference.
  • this[NavigationMixin.GenerateUrl](pageRef) => Promise<string>: Translate a page reference into an absolute path.

Usage examples

NavigationMixin and CurrentPageReference

This example is analogous to the webruntime/navigation API example.

import { wire, LightningElement } from 'lwc';
import { NavigationMixin, CurrentPageReference } from 'webruntime/lightningNavigation';

const aPageRef = {
    type: 'page',
    attributes: {
        name: 'sample',
    },
};

export default class Example extends NavigationMixin(LightningElement) {
    // Subscribe to updates on the current state.
    @wire(CurrentPageReference)
    currentPageRef;

    // Use the current page reference.
    @track
    get name() {
        return this.currentPageRef ? this.currentPageRef.attributes.name : '';
    }

    // Navigate by URL.
    navUrl() {
        this[NavigationMixin.Navigate]('/some/path');
    }

    // Navigate by page reference.
    navPageRef() {
        this[NavigationMixin.Navigate](aPageRef);
    }

    // Generate a URL.
    getUrl() {
        this[NavigationMixin.GenerateUrl](aPageRef).then((url) => console.log(url));
    }
}

Advanced topics

provideContext()

provideContext(context, node)

Using the createRouter() API will create a Router with navigation context automatically. To create custom navigation context, call the provideContext() API with the following arguments:

  • context: The provided context must have exactly these properties:
    • navigate: An implementation of the navigate() function.
    • generateUrl: An implementation of the generateUrl() function.
    • subscribe: An implementation of the subscribe() function.

An object is returned containing these properties:

  • id: An identifier for the navigation context. This is the value returned over the NavigationContext wire adapter.
  • update(value): Update the context object to a new value. Subscribers will be updated with the new API implementations.
  • disconnect(): Stop the navigation context from being detected by descendant components.

Hardcoding context

In a case where an application only has one router, the application developers can consider providing their users with Navigation APIs which are locked to the single navigation context. Then users do not need to pull in the navigation context themselves.

import {
    navigate as webruntimeNavigate,
    generateUrl as webruntimeGenerateUrl,
    subscribe as webruntimeSubscribe,
    createRouter
} from 'webruntime/navigation';

// Create and start the router.
const router = createRouter({...});

// Navigate programmatically.
export function navigate(loc, options) {
    webruntimeNavigate(router.id, loc, options);
}

// Generate a URL for the given route.
export function generateUrl(route) {
    return webruntimeGenerateUrl(router.id, route);
}

// Subscribe to navigation state changes.
export function subscribe(callback) {
    webruntimeSubscribe(router.id, callback);
}

Setup

Install

yarn add @webruntime/navigation

Build

Build tasks can be run in this repository from the /packages/@webruntime/navigation directory with:

yarn clean
yarn build

Tests

Test tasks can be run in this repository from the /packages/@webruntime/navigation directory with:

yarn test     // run jest tests
yarn coverage // test with coverage output
yarn lint     // lint the code

Architecture

Static analysis

A routing goal is to be able to statically analyze by URL path. Meaning, the code needed to display a component/view/page for a given path can be known ahead of runtime. Route definitions should contain the metadata needed for a builder plugin to do the analysis. A different builder plugin is written to analyze different styles of route definition metadata. Example route definitions:

  • Basic id-based routing:
{
    "id": "home",
    "path": "/home",
    "component": "our-home"
}
  • Lightning Navigation page-based:
{
    "path": "/case/:recordId",
    "page": {
        "type": "standard__recordPage",
        "attributes": {
            "objectApiName": "Case"
        }
    }
}
  • Webruntime view-based, with Lightning Navigation support:
{
    "id": "path-detail",
    "path": "/case/:recordId",
    "view": "caseDetail",
    "label": "Case Detail",
    "page": {
        "type": "standard__recordPage",
        "attributes": {
            "objectApiName": "Case"
        }
    }
}

Route definition matching

Matching a route to a route definition is relatively straight forward, since the ids are unique. Here is the criteria:

  • route.id === routeDef.id
  • each non-optional parameterized path segment (e.g.: /:recordId) in the route definition path, must match a key in the route attributes

Here is an example:

// route definition
{
    "id": "home",
    "path": "/app/:appName"
}
// matching route
{
    "id": "home",
    "attributes": {
        "appName": "awesome"
    }
}
// NOT matching route
{
    "id": "home"
}

Matching a page reference is more complex. Here is the criteria:

  • pageRef.type === routeDef.page.type
  • each non-optional parameterized path segment (e.g.: /:recordId) in the route definition path, must match a key in the page reference attributes
  • each key/value pair in the route definition page.attributes, must match a key/value pair in the page reference attributes
  • if there is more than 1 route definition match for a page reference, pick the first one

Here are some examples:

  • simplest case, no attributes:
// page reference
{
    "type": "home"
}
// matching route definition
{
    "path": "/home",
    "page": {
        "type": "home"
    }
}
// NOT matching route definition
{
    "path": "/old/home",
    "page": {
        "type": "home"
    }
}
  • route definition with parameterized path:
// page reference
{
    "type": "standard__recordPage",
    "attributes": {
        "recordId": "0D50M00004NgNxtSAF"
    }
}
// matching route definition
{
    "path": "/record/:recordId",
    "page": {
        "type": "standard__recordPage"
    }
}
  • route definition with parameterized path containing optional parameters:
// page reference
{
    "type": "standard__recordPage",
    "attributes": {
        "recordId": "0D50M00004NgNxtSAF"
    }
}
// matching route definition
{
    "path": "/record/:recordId/:recordName?",
    "page": {
        "type": "standard__recordPage"
    }
}
  • route definition has page.attributes:
// page reference
{
    "type": "standard__objectPage",
    "attributes": {
        "objectApiName": "Case"
    }
}
// matching route definition
{
    "path": "/cases",
    "page": {
        "type": "standard__objectPage",
        "attributes": {
            "objectApiName": "Case"
        }
    }
}
  • route definition has both a parameterized path and page.attributes:
// page reference
{
    "type": "standard__recordPage",
    "attributes": {
        "objectApiName": "Case",
        "recordId": "0D50M00004NgNxtSAF"
    }
}
// matching route definition
{
    "path": "/case/:recordId",
    "page": {
        "type": "standard__recordPage",
        "attributes": {
            "objectApiName": "Case"
        }
    }
}

Diagrams

Programmatic navigation events flow up from their source node through each navigation context. If not stopped, they eventually reach the root router, which processes the event.

The URL is then parsed starting at the root router and flowing down through any child routers. First the preNavigate hook listeners are run, from root to leaf. If all return true, the postNavigate hook listeners are run, then each router hydrates their subscribers with a new route/page reference.

navigation event flow diagram