Create a gist now

Instantly share code, notes, and snippets.

Define a series of routes as a stream in order to consolidate route logic outside of the remit of individual endpoints
import m from 'mithril'
import stream from 'mithril/stream/stream'
export default function routeStream(
defaultRoute = '/',
routes = ['/:path...']
){
const routeStream = stream()
m.route(
document.createDocumentFragment(),
defaultRoute,
routes.reduce((map, route) => {
map[route] = {
onmatch(...output){
routeStream(output)
}
}
return map
}, {})
)
return routeStream
}

m route stream

Mithril's route API is predicated on the notion that all logic and view concerns are specific to a given route endpoint. In any significant application, this leads to a lot of flaky code duplication: most views share a lot of markup; some persistent application logic is route-agnostic; some logic needs to be run on every route. In these scenarios it makes more sense to treat the route as a data source, and mount a single top level component with a consolidated lifecycle that is guaranteed to have access to all route data.

mRouteStream offers an interface concerned solely with the business of routing, allowing you to provide a list of routes and handle Mithril view logic separately. The return value of mRouteStream is a stream which emits an array that matches the signature of onmatch.

Signature:

const route = mRouteStream(defaultRoute, routes)
Argument Type Required Default Description
defaultRoute String No '/' The default route - maps to the second argument of Mithril's m.route
routes Array No [':path...'] The routes, without endpoint mapping - the equivalent of running Object.keys on the third argument of Mithril's m.route
returns Stream A stream that emits an array conforming to Mithril's RouteResolver onmatch signature

Example:

const route = mRouteStream( '/', [
  '/', 
  '/about', 
  '/post/:postId',
  '/:404...'
])

m.mount(document.body, {
  oninit(){
    // Centralised route parsing logic
    route.map(([args, requestedPath]) => {
      // Bind route data to state
      Object.assign(this, {args, requestedPath})
      
      // Flag a route-based redraw
      this.routing = true
    })
  },

  // Centralised route-based lifecycle logic
  onupdate(){
    // Infer route-based redraw
    if(this.routing){
      scrollTo(0, 0)
      
      this.routing = false
    }
  },

  // Single comprehensive view
  view : vnode =>
      vnode.state.args[404] 
    ? m(NotFound, requestedPath) // High-level fork
    : m(Layout, // Common elements defined once rather than copy-pasted across route endpoints
        m(Header, vnode.state ), // Passing down route data for high-level components
         
          /^(\/|\/about)$/.test(vnode.state.requestedPath)
        ? m(Page, vnode.state)
        : vnode.state.args.post 
        ? m(Post, Object.assign(
            // Route data can be abstracted at any point
            { editing : /edit$/.test(vnode.state.requestedPath) },
            vnode.state
          ))
      )
})
if(typeof require == 'function'){
var m = require('mithril')
var stream = require('mithril/stream/stream')
}
function routeStream(defaultRoute, routes){
var routeStream = stream()
m.route(
document.createDocumentFragment(),
defaultRoute || '/',
(routes || ['/:path...']).reduce(function(map, route){
map[route] = {
onmatch(){
routeStream([].slice.call(arguments))
}
}
return map
}, {})
)
return routeStream
}
if(typeof module == 'object')
module.exports = routeStream
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment