Last active
August 29, 2015 14:16
-
-
Save steos/0b24fde4eaf6965c0b67 to your computer and use it in GitHub Desktop.
bidirectional declarative microrouter (ES6)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
import {first, rest, last, trimLeft, partial, partialRight, omit, dropRight, | |
zipObject, isObject, assign, ary, map, template} from 'lodash'; | |
const placeHolderRegex = /:([a-zA-Z_][a-zA-Z0-9_]*)/g; | |
var trimLeadingColon = ary(partialRight(trimLeft, ':'), 1); | |
function assembleRoute(segment, routes) { | |
var renderPath = template(segment, {interpolate: placeHolderRegex}); | |
return assign(renderPath, assemble(routes || {}, segment)) | |
} | |
function assemble(routes, prefix = '') { | |
return zipObject(map(routes, (val, key) => { | |
let name = isObject(val) ? val.name : val; | |
return [name, assembleRoute(prefix + key, isObject(val) ? val.routes : null)]; | |
})); | |
} | |
function match(routes, path) { | |
for (var segment in routes) { | |
if (!routes.hasOwnProperty(segment)) continue; | |
let spec = routes[segment]; | |
let name = isObject(spec) ? spec.name : spec; | |
let payload = isObject(spec) ? omit(spec, ['name', 'routes']) : {}; | |
let subRoutes = isObject(spec) ? spec.routes : null; | |
let paramKeys = (segment.match(placeHolderRegex) || []).map(trimLeadingColon); | |
let re = new RegExp('^' + segment.replace(placeHolderRegex, '([^/]+)') + '(/.+)?$'); | |
let matched = path.match(re); | |
if (matched) { | |
let remainingPath = last(matched); | |
let params = zipObject(paramKeys, dropRight(rest(matched))); | |
if (subRoutes && remainingPath) { | |
let subMatch = match(subRoutes, remainingPath); | |
if (subMatch) { | |
let [subName, subParams, subRemaining, subPayload] = subMatch; | |
return [[name].concat(subName), assign(params, subParams), subRemaining, subPayload]; | |
} | |
} | |
return [[name], params, remainingPath, payload]; | |
} | |
} | |
return null; | |
} | |
export default function(routes) { | |
let router = assemble(routes); | |
function resolve(path) { | |
let matched = match(routes, path); | |
if (matched) { | |
let [keys, params, remaining, payload] = matched; | |
let route = keys.reduce((obj, key) => obj[key], router); | |
let result = {params, remaining, route, name: keys.join('.')}; | |
return isObject(payload) ? assign(payload, result) : result; | |
} | |
return null; | |
} | |
return assign(resolve, router); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import router from './router'; | |
let route = router({ | |
'/blub': { // key is the path to match | |
name: 'blub', // must have name for bidirectional routing (must be unique among siblings) | |
myValue: blub, // properties other than "name" and "routes" are for user data | |
routes: { // "routes" property contains nested subroutes. can nest infinitely. | |
'/edit/:id': { // can use placeholders to capture params | |
name: 'edit', | |
myValue: blubEdit | |
} | |
} | |
}, | |
'/edit/:foo': { | |
name: 'edit', | |
myValue: edit | |
} | |
}); | |
route.blub(); // => "/blub" | |
route.blub.edit({id: 42}); // => "/blub/edit/42" | |
route.edit({foo: 'bar'}); // => "/edit/bar" | |
route('/blub'); // => {myValue: blub, params: {}, name: 'blub', remaining: ''} | |
route('/blub/edit/23'); // => {myValue: blubEdit, params: {id: '23'}, name: 'blub.edit', remaining: ''} | |
route('/edit/bar'); // => {myValue: edit, params: {foo: 'bar'}, name: 'edit', remaining: ''} | |
route('/blub/blah'); // => {... name: 'blub', remaining: '/blah'} | |
route('/blub/edit/23/foo/bar'); // => {... name: 'blub.edit', params: {id: '23'}, remaining: '/foo/bar'} | |
route('/blubb'); // => null | |
route('/edit/bar/'); // => null | |
route('/foo'); // => null | |
// captured params will all be merged into one object | |
router({ | |
'/get/:what/:id': { | |
name: 'foo', | |
routes: { | |
'/bar/:barId': 'bar' | |
} | |
} | |
})('/get/foo/42/bar/23'); // => {... params: {what: 'foo', id: '42', barId: '23'}, name: 'foo.bar' } | |
// child route params overwrite parents | |
router({ | |
'/foo/:id': { | |
name: 'foo', | |
routes: { | |
'/bar/:id': 'bar' | |
} | |
} | |
})('/foo/1/bar/2'); // => {... params: {id: '2'}, name: 'foo.bar' } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment