From c5a24a5f54082708fc1e07e5fa1869938b4cec92 Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Mon, 16 Feb 2015 12:25:28 -0800 Subject: [PATCH] [added] Route/Match classes [changed] Route names are nested This commit formalizes and enhances two of the core primitives in the router: Route and Match. We get a few benefits from this: 1. Routes may now be created programmatically, as well as via JSX. This is useful in situations where it is desirable to assemble the route configuration using separate modules, instead of all at once. For example, in ApplicationRoute.js you could have: module.exports = Router.createRoute(); and in UserProfileRoute.js: var ApplicationRoute = require('./ApplicationRoute'); module.exports = Router.createRoute({ parentRoute: ApplicationRoute, path: 'users/:id' }); 2. may reference a Route object directly. 3. Route names may be re-used at different levels of the hierarchy. For example, you could have two different routes named "new" but nested inside different parent routes. Using this route configuration, you could or depending on which one you wanted. A side effect of this is that names of nested routes are no longer "global", so e.g. won't work because it is ambiguous, but will still work. --- modules/Match.js | 65 ++++++ modules/Route.js | 284 +++++++++++++++++++++++ modules/Routing.js | 158 ------------- modules/Transition.js | 13 +- modules/__tests__/Router-test.js | 69 +++--- modules/components/Link.js | 6 +- modules/createRouter.js | 58 +---- modules/createRoutesFromReactChildren.js | 88 +++++++ modules/index.js | 5 + 9 files changed, 495 insertions(+), 251 deletions(-) create mode 100644 modules/Match.js create mode 100644 modules/Route.js delete mode 100644 modules/Routing.js create mode 100644 modules/createRoutesFromReactChildren.js diff --git a/modules/Match.js b/modules/Match.js new file mode 100644 index 0000000000..93ae4b4457 --- /dev/null +++ b/modules/Match.js @@ -0,0 +1,65 @@ +/* jshint -W084 */ + +var Path = require('./utils/Path'); + +function Match(pathname, params, query, routes) { + this.pathname = pathname; + this.params = params; + this.query = query; + this.routes = routes; +} + +function deepSearch(route, pathname, query) { + // Check the subtree first to find the most deeply-nested match. + var childRoutes = route.childRoutes; + if (childRoutes) { + var match, childRoute; + for (var i = 0, len = childRoutes.length; i < len; ++i) { + childRoute = childRoutes[i]; + + if (childRoute.isDefault || childRoute.isNotFound) + continue; // Check these in order later. + + if (match = deepSearch(childRoute, pathname, query)) { + // A route in the subtree matched! Add this route and we're done. + match.routes.unshift(route); + return match; + } + } + } + + // No child routes matched; try the default route. + var defaultRoute = route.defaultRoute; + if (defaultRoute && (params = Path.extractParams(defaultRoute.path, pathname))) + return new Match(pathname, params, query, [ route, defaultRoute ]); + + // Does the "not found" route match? + var notFoundRoute = route.notFoundRoute; + if (notFoundRoute && (params = Path.extractParams(notFoundRoute.path, pathname))) + return new Match(pathname, params, query, [ route, notFoundRoute ]); + + // Last attempt: check this route. + var params = Path.extractParams(route.path, pathname); + if (params) + return new Match(pathname, params, query, [ route ]); + + return null; +} + +/** + * Attempts to match depth-first a route in the given route's + * subtree against the given path and returns the match if it + * succeeds, null if no match can be made. + */ +Match.findMatchForPath = function (routes, path) { + var pathname = Path.withoutQuery(path); + var query = Path.extractQuery(path); + var match = null; + + for (var i = 0, len = routes.length; match == null && i < len; ++i) + match = deepSearch(routes[i], pathname, query); + + return match; +}; + +module.exports = Match; diff --git a/modules/Route.js b/modules/Route.js new file mode 100644 index 0000000000..64ba778eee --- /dev/null +++ b/modules/Route.js @@ -0,0 +1,284 @@ +var assign = require('react/lib/Object.assign'); +var invariant = require('react/lib/invariant'); +var warning = require('react/lib/warning'); +var Path = require('./utils/Path'); + +function Route(name, path, ignoreScrollBehavior, isDefault, isNotFound, onEnter, onLeave, handler) { + this.name = name; + this.path = path; + this.paramNames = Path.extractParamNames(this.path); + this.ignoreScrollBehavior = !!ignoreScrollBehavior; + this.isDefault = !!isDefault; + this.isNotFound = !!isNotFound; + this.onEnter = onEnter; + this.onLeave = onLeave; + this.handler = handler; +} + +Route.prototype.toString = function () { + var string = '`; + + return string; +}; + +/** + * Appends the given route to this route's child routes. + */ +Route.prototype.appendChildRoute = function (route) { + invariant( + route instanceof Route, + 'route.appendChildRoute must use a valid Route' + ); + + if (!this.childRoutes) + this.childRoutes = []; + + if (route.name) { + invariant( + this.childRoutes.every(function (childRoute) { + return childRoute.name !== route.name; + }), + 'Route %s may not have more than one child route named "%s"', + this, route.name + ); + } + + this.childRoutes.push(route); +}; + +/** + * Allows looking up a child route using a "." delimited string, e.g.: + * + * route.appendChildRoute( + * Router.createRoute({ name: 'user' }, function () { + * Router.createRoute({ name: 'new' }); + * }) + * ); + * + * var NewUserRoute = route.lookupChildRoute('user.new'); + * + * See also Route.findRouteByName. + */ +Route.prototype.lookupChildRoute = function (names) { + if (!this.childRoutes) + return null; + + return Route.findRouteByName(this.childRoutes, names); +}; + +/** + * Searches the given array of routes and returns the route that matches + * the given name. The name should be a . delimited string like "user.new" + * that specifies the names of nested routes. Routes in the hierarchy that + * do not have a name do not need to be specified in the search string. + * + * var routes = [ + * Router.createRoute({ name: 'user' }, function () { + * Router.createRoute({ name: 'new' }); + * }) + * ]; + * + * var NewUserRoute = Route.findRouteByName(routes, 'user.new'); + */ +Route.findRouteByName = function (routes, names) { + if (typeof names === 'string') + names = names.split('.'); + + var route, foundRoute; + for (var i = 0, len = routes.length; i < len; ++i) { + route = routes[i]; + + if (route.name === names[0]) { + if (names.length === 1) + return route; + + if (!route.childRoutes) + return null; + + return Route.findRouteByName(route.childRoutes, names.slice(1)); + } else if (route.name == null) { + // Transparently skip over unnamed routes in the tree. + foundRoute = route.lookupChildRoute(names); + + if (foundRoute != null) + return foundRoute; + } + } + + return null; +}; + +var _currentRoute; + +/** + * Creates and returns a new route. Options may be a URL pathname string + * with placeholders for named params or an object with any of the following + * properties: + * + * - name The name of the route. This is used to lookup a + * route relative to its parent route and should be + * unique among all child routes of the same parent + * - path A URL pathname string with optional placeholders + * that specify the names of params to extract from + * the URL when the path matches. Defaults to `/${name}` + * when there is a name given, or the path of the parent + * route, or / + * - ignoreScrollBehavior True to make this route (and all descendants) ignore + * the scroll behavior of the router + * - isDefault True to make this route the default route among all + * its siblings + * - isNotFound True to make this route the "not found" route among + * all its siblings + * - onEnter A transition hook that will be called when the + * router is going to enter this route + * - onLeave A transition hook that will be called when the + * router is going to leave this route + * - handler A React component that will be rendered when + * this route is active + * - parentRoute The parent route to use for this route. This option + * is automatically supplied when creating routes inside + * the callback to another invocation of createRoute. You + * only ever need to use this when declaring routes + * independently of one another to manually piece together + * the route hierarchy + * + * The callback may be used to structure your route hierarchy. Any call to + * createRoute, createDefaultRoute, createNotFoundRoute, or createRedirect + * inside the callback automatically uses this route as its parent. + */ +Route.createRoute = function (options, callback) { + options = options || {}; + + if (typeof options === 'string') + options = { path: options }; + + var parentRoute = _currentRoute; + + if (parentRoute) { + warning( + options.parentRoute == null || options.parentRoute === parentRoute, + 'You should not use parentRoute with createRoute inside another route\'s child callback; it is ignored' + ); + } else { + parentRoute = options.parentRoute; + } + + var name = options.name; + var path = options.path || name; + + if (path) { + if (Path.isAbsolute(path)) { + if (parentRoute) { + invariant( + parentRoute.paramNames.length === 0, + 'You cannot nest path "%s" inside "%s"; the parent requires URL parameters', + path, parentRoute.path + ); + } + } else if (parentRoute) { + // Relative paths extend their parent. + path = Path.join(parentRoute.path, path); + } else { + path = '/' + path; + } + } else { + path = parentRoute ? parentRoute.path : '/'; + } + + if (options.isNotFound && !(/\*$/).test(path)) + path += '*'; // Auto-append * to the path of not found routes. + + var route = new Route( + name, + path, + options.ignoreScrollBehavior, + options.isDefault, + options.isNotFound, + options.onEnter, + options.onLeave, + options.handler + ); + + if (parentRoute) { + if (route.isDefault) { + invariant( + parentRoute.defaultRoute == null, + '%s may not have more than one default route', + parentRoute + ); + + parentRoute.defaultRoute = route; + } else if (route.isNotFound) { + invariant( + parentRoute.notFoundRoute == null, + '%s may not have more than one not found route', + parentRoute + ); + + parentRoute.notFoundRoute = route; + } + + parentRoute.appendChildRoute(route); + } + + // Any routes created in the callback + // use this route as their parent. + if (typeof callback === 'function') { + var currentRoute = _currentRoute; + _currentRoute = route; + callback.call(route, route); + _currentRoute = currentRoute; + } + + return route; +}; + +/** + * Creates and returns a route that is rendered when its parent matches + * the current URL. + */ +Route.createDefaultRoute = function (options) { + return Route.createRoute( + assign({}, options, { isDefault: true }) + ); +}; + +/** + * Creates and returns a route that is rendered when its parent matches + * the current URL but none of its siblings do. + */ +Route.createNotFoundRoute = function (options) { + return Route.createRoute( + assign({}, options, { isNotFound: true }) + ); +}; + +/** + * Creates and returns a route that automatically redirects the transition + * to another route. In addition to the normal options to createRoute, this + * function accepts the following options: + * + * - from An alias for the `path` option. Defaults to * + * - to The path/route/route name to redirect to + * - params The params to use in the redirect URL. Defaults + * to using the current params + * - query The query to use in the redirect URL. Defaults + * to using the current query + */ +Route.createRedirect = function (options) { + return Route.createRoute( + assign({}, options, { + path: options.path || options.from || '*', + onEnter: function (transition, params, query) { + transition.redirect(options.to, options.params || params, options.query || query); + } + }) + ); +}; + +module.exports = Route; diff --git a/modules/Routing.js b/modules/Routing.js deleted file mode 100644 index 76394c16fb..0000000000 --- a/modules/Routing.js +++ /dev/null @@ -1,158 +0,0 @@ -/* jshint -W084 */ -var React = require('react'); -var invariant = require('react/lib/invariant'); -var DefaultRoute = require('./components/DefaultRoute'); -var NotFoundRoute = require('./components/NotFoundRoute'); -var Redirect = require('./components/Redirect'); -var Path = require('./utils/Path'); - -function createTransitionToHook(to, _params, _query) { - return function (transition, params, query) { - transition.redirect(to, _params || params, _query || query); - }; -} - -function createRoute(element, parentRoute, namedRoutes) { - var type = element.type; - var props = element.props; - - if (type.validateProps) - type.validateProps(props); - - var options = { - name: props.name, - ignoreScrollBehavior: !!props.ignoreScrollBehavior - }; - - if (type === Redirect.type) { - options.willTransitionTo = createTransitionToHook(props.to, props.params, props.query); - props.path = props.path || props.from || '*'; - } else { - options.handler = props.handler; - options.willTransitionTo = props.handler && props.handler.willTransitionTo; - options.willTransitionFrom = props.handler && props.handler.willTransitionFrom; - } - - var parentPath = (parentRoute && parentRoute.path) || '/'; - - if ((props.path || props.name) && type !== DefaultRoute.type && type !== NotFoundRoute.type) { - var path = props.path || props.name; - - // Relative paths extend their parent. - if (!Path.isAbsolute(path)) - path = Path.join(parentPath, path); - - options.path = Path.normalize(path); - } else { - options.path = parentPath; - - if (type === NotFoundRoute.type) - options.path += '*'; - } - - options.paramNames = Path.extractParamNames(options.path); - - // Make sure the route's path has all params its parent needs. - if (parentRoute && Array.isArray(parentRoute.paramNames)) { - parentRoute.paramNames.forEach(function (paramName) { - invariant( - options.paramNames.indexOf(paramName) !== -1, - 'The nested route path "%s" is missing the "%s" parameter of its parent path "%s"', - options.path, paramName, parentRoute.path - ); - }); - } - - var route = new Route(options); - - // Make sure the route can be looked up by s. - if (props.name) { - invariant( - namedRoutes[props.name] == null, - 'You cannot use the name "%s" for more than one route', - props.name - ); - - namedRoutes[props.name] = route; - } - - // Handle . - if (type === NotFoundRoute.type) { - invariant( - parentRoute, - ' must have a parent ' - ); - - invariant( - parentRoute.notFoundRoute == null, - 'You may not have more than one per ' - ); - - invariant( - React.Children.count(props.children) === 0, - ' must not have children' - ); - - parentRoute.notFoundRoute = route; - - return null; - } - - // Handle . - if (type === DefaultRoute.type) { - invariant( - parentRoute, - ' must have a parent ' - ); - - invariant( - parentRoute.defaultRoute == null, - 'You may not have more than one per ' - ); - - invariant( - React.Children.count(props.children) === 0, - ' must not have children' - ); - - parentRoute.defaultRoute = route; - - return null; - } - - route.routes = createRoutesFromReactChildren(props.children, route, namedRoutes); - - return route; -} - -/** - * Creates and returns an array of route objects from the given ReactChildren. - */ -function createRoutesFromReactChildren(children, parentRoute, namedRoutes) { - var routes = []; - - React.Children.forEach(children, function (child) { - // Exclude null values, s and s. - if (React.isValidElement(child) && (child = createRoute(child, parentRoute, namedRoutes))) - routes.push(child); - }); - - return routes; -} - -function Route(options) { - options = options || {}; - - this.name = options.name; - this.path = options.path || '/'; - this.paramNames = options.paramNames || Path.extractParamNames(this.path); - this.ignoreScrollBehavior = !!options.ignoreScrollBehavior; - this.willTransitionTo = options.willTransitionTo; - this.willTransitionFrom = options.willTransitionFrom; - this.handler = options.handler; -} - -module.exports = { - createRoutesFromReactChildren: createRoutesFromReactChildren, - Route: Route -}; diff --git a/modules/Transition.js b/modules/Transition.js index 2e01f459c1..f52d33a671 100644 --- a/modules/Transition.js +++ b/modules/Transition.js @@ -12,6 +12,7 @@ var Redirect = require('./Redirect'); function Transition(path, retry) { this.path = path; this.abortReason = null; + // TODO: Change this to router.retryTransition(transition) this.retry = retry.bind(this); } @@ -33,12 +34,12 @@ Transition.from = function (transition, routes, components, callback) { return function (error) { if (error || transition.abortReason) { callback(error); - } else if (route.willTransitionFrom) { + } else if (route.onLeave) { try { - route.willTransitionFrom(transition, components[index], callback); + route.onLeave(transition, components[index], callback); // If there is no callback in the argument list, call it automatically. - if (route.willTransitionFrom.length < 3) + if (route.onLeave.length < 3) callback(); } catch (e) { callback(e); @@ -55,12 +56,12 @@ Transition.to = function (transition, routes, params, query, callback) { return function (error) { if (error || transition.abortReason) { callback(error); - } else if (route.willTransitionTo) { + } else if (route.onEnter) { try { - route.willTransitionTo(transition, params, query, callback); + route.onEnter(transition, params, query, callback); // If there is no callback in the argument list, call it automatically. - if (route.willTransitionTo.length < 4) + if (route.onEnter.length < 4) callback(); } catch (e) { callback(e); diff --git a/modules/__tests__/Router-test.js b/modules/__tests__/Router-test.js index 9ecc27e98f..3046f9f9db 100644 --- a/modules/__tests__/Router-test.js +++ b/modules/__tests__/Router-test.js @@ -807,6 +807,39 @@ describe('Router', function () { }); +describe('Router.makePath', function () { + var router; + beforeEach(function () { + router = Router.create( + + + + + + ); + }); + + describe('when given an absolute path', function () { + it('returns that path', function () { + expect(router.makePath('/about')).toEqual('/about'); + }); + }); + + describe('when there is a route with the given name', function () { + it('returns the correct path', function () { + expect(router.makePath('home.users.user', { id: 6 })).toEqual('/home/users/6'); + }); + }); + + describe('when there is no route with the given name', function () { + it('throws an error', function () { + expect(function () { + router.makePath('not-found'); + }).toThrow(); + }); + }); +}); + describe('Router.run', function () { it('matches a root route', function (done) { @@ -1167,42 +1200,6 @@ describe('Router.run', function () { }); }); }); - - describe('makePath', function () { - var router; - beforeEach(function () { - router = Router.create({ - routes: [ - - - - - - ] - }); - }); - - describe('when given an absolute path', function () { - it('returns that path', function () { - expect(router.makePath('/about')).toEqual('/about'); - }); - }); - - describe('when there is a route with the given name', function () { - it('returns the correct path', function () { - expect(router.makePath('user', { id: 6 })).toEqual('/home/users/6'); - }); - }); - - describe('when there is no route with the given name', function () { - it('throws an error', function () { - expect(function () { - router.makePath('not-found'); - }).toThrow(); - }); - }); - }); - }); describe.skip('unmounting', function () { diff --git a/modules/components/Link.js b/modules/components/Link.js index 14c9072679..2e03620f50 100644 --- a/modules/components/Link.js +++ b/modules/components/Link.js @@ -4,6 +4,7 @@ var assign = require('react/lib/Object.assign'); var Navigation = require('../Navigation'); var State = require('../State'); var PropTypes = require('../PropTypes'); +var Route = require('../Route'); function isLeftClickEvent(event) { return event.button === 0; @@ -39,7 +40,10 @@ var Link = React.createClass({ propTypes: { activeClassName: PropTypes.string.isRequired, - to: PropTypes.string.isRequired, + to: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.instanceOf(Route) + ]), params: PropTypes.object, query: PropTypes.object, activeStyle: PropTypes.object, diff --git a/modules/createRouter.js b/modules/createRouter.js index 122551785a..ad4e74aad3 100644 --- a/modules/createRouter.js +++ b/modules/createRouter.js @@ -12,13 +12,15 @@ var StaticLocation = require('./locations/StaticLocation'); var NavigationContext = require('./NavigationContext'); var ScrollHistory = require('./ScrollHistory'); var StateContext = require('./StateContext'); -var createRoutesFromReactChildren = require('./Routing').createRoutesFromReactChildren; +var createRoutesFromReactChildren = require('./createRoutesFromReactChildren'); var isReactChildren = require('./isReactChildren'); var Transition = require('./Transition'); var PropTypes = require('./PropTypes'); var Redirect = require('./Redirect'); var History = require('./History'); var Cancellation = require('./Cancellation'); +var Match = require('./Match'); +var Route = require('./Route'); var supportsHistory = require('./utils/supportsHistory'); var Path = require('./utils/Path'); @@ -32,47 +34,6 @@ var DEFAULT_LOCATION = canUseDOM ? HashLocation : '/'; */ var DEFAULT_SCROLL_BEHAVIOR = canUseDOM ? ImitateBrowserBehavior : null; -function createMatch(route, params, pathname, query) { - return { - routes: [ route ], - params: params, - pathname: pathname, - query: query - }; -} - -function findMatch(routes, defaultRoute, notFoundRoute, pathname, query) { - var route, match, params; - - for (var i = 0, len = routes.length; i < len; ++i) { - route = routes[i]; - - // Check the subtree first to find the most deeply-nested match. - match = findMatch(route.routes, route.defaultRoute, route.notFoundRoute, pathname, query); - - if (match != null) { - match.routes.unshift(route); - return match; - } - - // No routes in the subtree matched, so check this route. - params = Path.extractParams(route.path, pathname); - - if (params) - return createMatch(route, params, pathname, query); - } - - // No routes matched, so try the default route if there is one. - if (defaultRoute && (params = Path.extractParams(defaultRoute.path, pathname))) - return createMatch(defaultRoute, params, pathname, query); - - // Last attempt: does the "not found" route match? - if (notFoundRoute && (params = Path.extractParams(notFoundRoute.path, pathname))) - return createMatch(notFoundRoute, params, pathname, query); - - return null; -} - function hasProperties(object, properties) { for (var propertyName in properties) if (properties.hasOwnProperty(propertyName) && object[propertyName] !== properties[propertyName]) @@ -173,9 +134,6 @@ function createRouter(options) { clearAllRoutes: function () { this.cancelPendingTransition(); - this.defaultRoute = null; - this.notFoundRoute = null; - this.namedRoutes = {}; this.routes = []; }, @@ -184,7 +142,7 @@ function createRouter(options) { */ addRoutes: function (routes) { if (isReactChildren(routes)) - routes = createRoutesFromReactChildren(routes, this, this.namedRoutes); + routes = createRoutesFromReactChildren(routes); this.routes.push.apply(this.routes, routes); }, @@ -204,7 +162,7 @@ function createRouter(options) { * match can be made. */ match: function (path) { - return findMatch(this.routes, this.defaultRoute, this.notFoundRoute, Path.withoutQuery(path), Path.extractQuery(path)); + return Match.findMatchForPath(this.routes, path); }, /** @@ -216,11 +174,11 @@ function createRouter(options) { if (Path.isAbsolute(to)) { path = Path.normalize(to); } else { - var route = this.namedRoutes[to]; + var route = (to instanceof Route) ? to : Route.findRouteByName(this.routes, to); invariant( - route, - 'Unable to find ', + route instanceof Route, + 'Cannot find a route named "%s"', to ); diff --git a/modules/createRoutesFromReactChildren.js b/modules/createRoutesFromReactChildren.js new file mode 100644 index 0000000000..b02ad5a4e3 --- /dev/null +++ b/modules/createRoutesFromReactChildren.js @@ -0,0 +1,88 @@ +/* jshint -W084 */ + +var React = require('react'); +var assign = require('react/lib/Object.assign'); +var warning = require('react/lib/warning'); +var DefaultRouteType = require('./components/DefaultRoute').type; +var NotFoundRouteType = require('./components/NotFoundRoute').type; +var RedirectType = require('./components/Redirect').type; +var Route = require('./Route'); + +function checkPropTypes(componentName, propTypes, props) { + componentName = componentName || 'UnknownComponent'; + + for (var propName in propTypes) { + if (propTypes.hasOwnProperty(propName)) { + var error = propTypes[propName](props, propName, componentName); + + if (error instanceof Error) + warning(false, error.message); + } + } +} + +function createRouteOptions(props) { + var options = assign({}, props); + var handler = options.handler; + + if (handler) { + options.onEnter = handler.willTransitionTo; + options.onLeave = handler.willTransitionFrom; + } + + return options; +} + +function createRouteFromReactElement(element) { + if (!React.isValidElement(element)) + return; + + var type = element.type; + var props = element.props; + + if (type.propTypes) + checkPropTypes(type.displayName, type.propTypes, props); + + if (type === DefaultRouteType) + return Route.createDefaultRoute(createRouteOptions(props)); + + if (type === NotFoundRouteType) + return Route.createNotFoundRoute(createRouteOptions(props)); + + if (type === RedirectType) + return Route.createRedirect(createRouteOptions(props)); + + return Route.createRoute(createRouteOptions(props), function () { + if (props.children) + createRoutesFromReactChildren(props.children); + }); +} + +/** + * Creates and returns an array of routes created from the given + * ReactChildren, all of which should be one of , , + * , or , e.g.: + * + * var { createRoutesFromReactChildren, Route, Redirect } = require('react-router'); + * + * var routes = createRoutesFromReactChildren( + * + * + * + * + * + * + * ); + */ +function createRoutesFromReactChildren(children) { + var routes = []; + + React.Children.forEach(children, function (child) { + if (child = createRouteFromReactElement(child)) + routes.push(child); + }); + + return routes; +} + +module.exports = createRoutesFromReactChildren; diff --git a/modules/index.js b/modules/index.js index 145be66a99..456f9cbf2e 100644 --- a/modules/index.js +++ b/modules/index.js @@ -18,6 +18,11 @@ exports.Navigation = require('./Navigation'); exports.RouteHandlerMixin = require('./RouteHandlerMixin'); exports.State = require('./State'); +exports.createRoute = require('./Route').createRoute; +exports.createDefaultRoute = require('./Route').createDefaultRoute; +exports.createNotFoundRoute = require('./Route').createNotFoundRoute; +exports.createRedirect = require('./Route').createRedirect; +exports.createRoutesFromReactChildren = require('./createRoutesFromReactChildren'); exports.create = require('./createRouter'); exports.run = require('./runRouter');