Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API RFC #1

Open
blittle opened this issue Feb 1, 2020 · 4 comments
Open

API RFC #1

blittle opened this issue Feb 1, 2020 · 4 comments

Comments

@blittle
Copy link
Member

blittle commented Feb 1, 2020

API Proposal

Requirements

  1. Declarative route definition
  2. Nested routes
  3. Server side rendering
  4. Code splits

Examples

<!--- Root app.html --->
<lwce-router>
  <ul>
    <li><lwce-link href="/">Home</lwce-link></li>
    <li><lwce-link href="/settings">Settings</lwce-link></li>
    <li><lwce-link href="/users/1234">Profile</lwce-link></li>
  </ul>
  <lwce-route path="/">
    <commerce-home></commerce-home>
  </lwce-route>
  <lwce-route path="/cart">
    <commerce-cart></commerce-cart>
  </lwce-route>
  <lwce-route path="/products/:productId">
    <commerce-product-details></commerce-product-details>
  </lwce-route>
</lwce-router>

<!--- Inside cart.html --->
<div>
  <lwce-link href="checkout">Checkout</lwce-link>
  <lwce-route path="">
    <commerce-cart-details></commerce-cart-details>
  </lwce-route>
  <lwce-route path="checkout">
    <commerce-cart-checkout></commerce-cart-checkout>
  </lwce-route>
</div>

Accessing route parameters:

import { LightningElement, wire } from 'lwc';
import { routeParams } from '@lwce/router';

export default class ProductDetails extends LightningElement {
  
  @wire(routeParams, {})
  params = {}
}
<template>
  <div>Details for {params.productId}</div>
</template>

Web component prior art

https://vaadin.com/router

  • Imperative
  • Route params are automatically set as a property on the component being mounted by the router
  • Lazy loading bundles also defined in the router declaration
  • Animation support with classes added and removed from the outer element the router mounts into

Example:

const router = new Router(document.getElementById('outlet'));
router.setRoutes([
    {path: '/', component: 'x-home-view'},
    {
      path: '/user/:id',
      bundle: '/user.bundle.js',
      component: 'x-user-js-bundle-view' // <-- defined in the bundle
    },
  ]);

https://www.webcomponents.org/element/@appnest/web-router

  • Imperative/declarative - must query the dom for the "router slot" element and call a method on the element to add routes.
  • The component definition can be a reference to a component class, a component instance, or a factory function which returns a promise that resolves to one of the prior (lazy loading).
  • Includes a "router link" component that builds an anchor tag and sets an "active" attribute when the path is active.
  • A setup method is available in the route definition which will run before the component mounts, allowing you to set properties on the component, including route params.
  • Or it provides a function for you to call within the mounted component that will give you params if the component is active
  • No nested support

Example:

// add to your html:     <router-slot></router-slot>

const routerSlot = document.querySelector("router-slot");
await routerSlot.add([
  {
    path: "login",
    component: () => import("./path/to/login/component") // Lazy loaded
  },
  {
    path: "users/:userId",
    setup: (component: UserComponent, info: RoutingInfo) => {
      component.userId = info.match.params.userId;
    },
    component: UserComponent // Not lazy loaded
  }
]);

https://github.com/ryansolid/webcomponent-router

  • The API supports deep nesting and is intriguing, but uses the is= web component syntax
  • Routes are named and nested with . notation
  • Supports lazy loading

Example

const router = new Router(document.getElementById('main'));

router.map(r => {
  r.index(() => ['user', { userId: 123 }]);
  r.notFound(() => ['index']);
  r.route('user', { path: '/users/:userId', tag: 'pane-user' }, r => {
    r.index(() => ['sets']);
    r.route('sets', {
      path: '/sets', tag: 'pane-sets',
      // dynamic import for code splitting
      onEnter: () => import("../components/pane-sets")
    });
    r.route('set', { path: '/sets/:setId', tag: 'pane-set' });
  });
});

router.start();

router.transitionTo('user.set', {setId: 49});

Non-web component prior art

Some have route components along with link components. Reach router is interesting in that there is no "route" component, all children of a router component take a prop of "path" and the router could mount any of these components based upon the URL state. This mean "routes" are just any old component.

@blittle
Copy link
Member Author

blittle commented Feb 1, 2020

@priandsf a few points that I'd like your thoughts on:

  1. How do you feel about the nested routes supporting relative paths? Notice in my example above, the <commerce-cart></commerce-cart> component has routes defined within it, and those routes have relative paths. So for example, the <commerce-cart-checkout></commerce-cart-checkout> component is actually on route /cart/checkout. How do you feel about that?
  2. Any component (whether it is the component that is mounted by the router or not) can @wire the currently matched route parameters.
  3. For lazy loading a component, I'd like to make a more generic component, see https://github.com/orgs/LWC-Essentials/projects/1#card-32462222 but maybe it could look something like:
<lwce-route path="/cart">
  <lwce-lazy path="path-to-bundle.js" element="commerce-cart"></lwce-lazy>
</lwce-route>

@priandsf
Copy link

priandsf commented Feb 1, 2020

  1. +1 - I love nested route supporting relative paths. It helps reusing the same components in different route base contexts.
  2. How does the @wire access the current route? Is it using events? If so, we need to find a way to retrieve the component to dispatch them.
  3. You need to experiment with this, as I foresee a few issues: the factory to create the element, how the LWC code is split...

@blittle
Copy link
Member Author

blittle commented Feb 3, 2020

How does the @wire access the current route? Is it using events? If so, we need to find a way to retrieve the component to dispatch them.

It could possibly be an event, where a component at the bottom of the tree @wires and within that wire initialization there is an event fired. The event detail could itself be a callback event listener. The router would call that even listener with url param data (and subsequent times when the route params change). Then when the component unmounts, it would fire an event "unsubscribing" from future param data. So, using events to register and unregister another layer of events?

You need to experiment with this, as I foresee a few issues: the factory to create the element, how the LWC code is split.

Yes, I need to understand this better. How LWC compiles the code, the automatic element creation, etc is a bit of a black box to me. I need to learn more about it, so doing this would probably help. I've asked Caridy about the custom elements and shadow roots, and got a cryptic response

@vsanse
Copy link

vsanse commented Apr 20, 2020

Hey @blittle ,
Just one doubt, how would I access query string e.g ?foo=bar via routeParams or is there any other way to do it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants