lucid-router

1.5.0 • Public • Published

lucid-router

A simple (lucid) html5 history-aware router.

  • ideal for universal/isomorphic apps
  • tiny (adds less than 5kb to your gzipped bundle)
  • progressive enhancement
    • fall back on normal redirect behavior when the history API isn't supported, not hash routing
    • routes can be added and removed at any time, so load the heavier sections of your app later when the user is idle -- if they navigate to it before the bundle loads it'll just redirect like normal

More interested in the philosophy than the configuration? It's at the bottom.

Want to dive straight into some examples?

Configuration

Require the router anywhere it will be used and add routes. Routes are stored inside the lucid-router module, so it is safe to destructure or pass the router's functions around without any binding (see below).

import * as router from 'lucid-router'

router.addRoutes([
  {name: 'profile', path: '/profiles/:profileId'}, // required param
  {name: 'home',    path: '(/:category)'}          // optional param
])

(Note: * as router is required because lucid-router does not have a default export. Most of the time you will only import the parts your module actually needs -- see the ES2015 section below)

You can also set an external property on the route definition. This flag can be a boolean or a function which takes an object containing the route matching info and returns a boolean. false will allow the normal route transition to occur, while true will cancel the location change event and instead navigate the browser directly to the url for a full page load. This setting is mainly for hybrid classic/SPA apps.

router.addRoutes([
  {
    name:     'elsewhere',
    path:     '/non-spa-route',
    external: true | false | (matchInfo => true | false)
  }
])

Also note that routes can be added or removed (see router.removeRoute(name)) at any time, allowing different parts of a larger application to asynchronously inject SPA features into an app as it loads. If a navigation occurs before the necessary route exists, a normal browser redirect will occur.

getLocation returns a RouterLocation object, or null if no routes match. See the Flow types for more info. Calling getLocation with no parameters will look for a window object to pull location info from. If window isn't available (Node.js server) or you need location data for a route you are not currently on, pass the path to match on into getLocation.

router.getLocation()
router.getLocation(path)

Subscribing to location changes

This is where the magic happens. Use this function to call React.render or wrap it with a Flux store so your components can subscribe to changes. Better yet, reserve a spot for it in your global app state and make your whole app a function from state -> UI!

router.register(location => console.log(location))

Callbacks can be registered and un-registered at any time, during an app's lifecycle (think dynamically loaded sections of a large app). Unregister by calling the callback returned from router.register.

Navigating

Use navigate to perform an immediate transition to a given url (a string). Use navigatorFor to build a callback bound to a particular path (equivalent to e => navigate(path, e)):

router.navigate('/path', e)     // => pass the event if you want it cancelled for you
router.navigatorFor('/path')(e) // => same as above, curried style (useful for event binding)

Use navigateToRoute to navigate using a route name and params object:

router.navigateToRoute('profile', {profileId: 5}, e)
router.navigatorForRoute('profile', {profileId: 5})(e)

This functionality is also available without performing a navigation:

router.pathFor('profile', {profileId: 5}) // => returns '/profiles/5'

ES2015 module imports

This is safe and convenient:

import {pathFor, navigatorFor} from 'lucid-router'

const link = pathFor('route-name')
const navigator = navigatorFor(link)

React

Nothing about lucid-router is specific to React, but they make a great pair! I've included a helper component for building anchor tags which you can import and use like so:

import Link from 'lucid-router/link'

class Nav extends React.Component {
  render() {
    return (
      <nav>
        <Link to="my-route" params={{id: 9}}>My Route</Link>
        <Link href="/somewhere/else">Somewhere else</Link>
      </nav>
    )
  }
}

The first becomes an <a> with an href and onClick which defer to pathFor and navigatorForRoute. The second just sets the href with the value provided and calls navigate when clicked. Link is just a shortcut for the most common use cases, so you can use it, ignore it, or make your own!

Philosophy

Another router?? Yup. I see two problems with the status quo. Routers are too specialized and they're treated as some separate, almost magical part of getting a 'real' application built.

Too specialized? What's that mean?

  • Angular Router
  • Angular UI Router
  • Ember Router
  • React Router
  • Express Router
  • Koa Router
  • etc...

Every framework has at least one router dedicated to just that library, plus a few more from the community. I wanted a tool for abstracting the details from routing away from my app, not a tool that depends on the specific library I've chosen to build my app.

The second problem is a little harder to quantify, and it comes from the Rails/Django/MVC patterns of views and layouts. This system tends to create a separation of technology rather than a separation of concerns. A folder for every controller? A folder for every view? No wonder those folders got too big and we needed to abstract common layouts files...

What if we organized it by concern: a folder for accounts, a folder for orders, etc. Then we have a central "app" that decides which of these to sub-apps to hand off to at any given time. What if that central hub of your app didn't have to care about routing logic? At first glance, this may not appear to give you much benefit. You'll still have code somewhere in your app to decide what to render given the current location information.

The difference is that your application now controls this logic, not a router. You can render each screen or state of your app without a router. You can tramsform urls into snapshots of state without an app. You can render the same way on the server and the client. This is the power of abstraction -- simplicity and composability. The "single tool for everything" solutions above sure look powerful at first glance, but in the long run they lock you in to their rules and quirks.

Think of it this way: if you design your app on my router and then decide it sucks, you can replace it with another one or write your own, provided you can convert the replacement's output to simple data.

: ]

Thanks

Thanks to url-pattern for all of the route/pattern work!

Misc

What about router5?

It looks great! There are three reasons I wrote this router anyway:

  1. I didn't know router5 existed when I initially wrote this for a work app, but even if I had...
  2. router5 is more complicated. I love keeping things simple.
  3. I needed really smooth fallback on regular redirects when html5 history isn't supported, not hash routing fallbacks. lucid-router can also be used on non-SPA apps just fine without losing the routing info.. just mark all your routes as external!

Package Sidebar

Install

npm i lucid-router

Weekly Downloads

7

Version

1.5.0

License

MIT

Last publish

Collaborators

  • spicydonuts