├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── npm-debug.log.334376515 ├── package.json └── src ├── index.js └── react-router ├── ParentRouteMixin.js ├── RoutedViewListMixin.js ├── generator.js ├── index.js └── render.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /react-router 4 | /index.js -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nate Wienert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## reapp-routes 2 | 3 | A small library for generating a tree representing routes that also map to paths. 4 | 5 | This does two things: saves code and enforces consistency. 6 | 7 | **Before reapp-routes** 8 | 9 | ```js 10 | var App = require('./components/App'); 11 | var Sub = require('./components/app/Sub'); 12 | var OtherSub = require('./components/app/OtherSub'); 13 | 14 | module.exports = 15 | 16 | 17 | 18 | 19 | ``` 20 | 21 | **With reapp-routes** 22 | 23 | ```js 24 | module.exports = routes(require, 25 | route('app', 26 | route('sub'), 27 | route('otherSub') 28 | ) 29 | ) 30 | ``` 31 | 32 | The `routes` method reads in the object tree generated by `route` and determines 33 | the path correspondingly. You can even customize it using the `dir` property on routes. 34 | In the end, you end up with consistent directory structures that map to your routes, 35 | less requires, less code and a simple routes file. 36 | 37 | It does require Webpack or a bundle system that handles dynamic requires. 38 | 39 | ### Examples 40 | 41 | Using react-router helpers: 42 | 43 | ```js 44 | var { route, routes } = require('reapp-routes/react-router/generator'); 45 | 46 | module.exports = routes(require, 47 | route('app', '/', { dir: '' }, 48 | route('kitchen', '/', 49 | route('controls'), 50 | route('modals'), 51 | route('popovers'), 52 | route('forms') 53 | ), 54 | route('viewer') 55 | ) 56 | ); 57 | ``` 58 | 59 | Rolling your own: 60 | 61 | ```js 62 | var React = require('react'); 63 | var { Route, DefaultRoute } = require('react-router'); 64 | var { route, routes } = require('react-router-generator'); 65 | 66 | module.exports = generate(routes( 67 | { dir: 'components/' }, 68 | route({ name: 'app', path: '/', dir: '' }, 69 | route('kitchen', 70 | route('controls'), 71 | route('modals'), 72 | route('popovers') 73 | ) 74 | ) 75 | )); 76 | 77 | function generate(props) { 78 | props.children = props.children ? props.children.map(generate) : null; 79 | props.handler = require(props.handlerPath); 80 | 81 | return props.defaultRoute ? 82 | : 83 | ; 84 | } 85 | ``` 86 | 87 | Corresponding file tree. Notice how `dir` affects nesting: 88 | 89 | ```text 90 | /components 91 | /kitchen 92 | Controls.jsx 93 | Modals.jsx 94 | Popovers.jsx 95 | Kitchen.jsx 96 | App.jsx 97 | ``` 98 | 99 | ### Todo 100 | - Document, tests 101 | 102 | See the index.js for more in-code documentation. 103 | -------------------------------------------------------------------------------- /npm-debug.log.334376515: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reapp/reapp-routes/0906725ea3c879891960c0d9cc679bd41a5177e8/npm-debug.log.334376515 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reapp-routes", 3 | "version": "0.10.14", 4 | "description": "helper for consistently requiring routes to filenames", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "babel src --out-dir . --stage=0", 8 | "watch": "babel src --out-dir . --stage=0 --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "github.com/reapp/reapp-route-tree" 13 | }, 14 | "author": "Nate Wienert", 15 | "license": "MIT", 16 | "dependencies": { 17 | "react": "0.14.x", 18 | "react-dom": "^0.14.0", 19 | "reapp-platform": "*", 20 | "react-router": "0.13.x", 21 | "reapp-object-assign": "^1.0.0" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/reapp/reapp-route-tree/issues" 25 | }, 26 | "homepage": "https://github.com/reapp/reapp-route-tree", 27 | "devDependencies": { 28 | "babel": "^5.0.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require('reapp-object-assign'); 2 | 3 | // reapp-routes is two helper functions that help your manage routes 4 | 5 | // Why? To have a generic way of defining routes that you could then 6 | // tie to a custom generator. This allows two things: 7 | 8 | // 1. It's a DSL: don't tie your route's to one specific implementation. 9 | // routes() event takes a generator so you can tie into route generation. 10 | 11 | // 2. It allows dynamic manipulation: 12 | // route() just returns an object tree, much like React components 13 | 14 | var defined = x => typeof x !== 'undefined'; 15 | var pick = (a, b) => typeof a !== 'undefined' ? a : b; 16 | 17 | // todo: allow people to choose their naming scheme 18 | var capitalize = s => s[0].toUpperCase() + s.slice(1); 19 | var proper = name => name.split('-').map(capitalize).join(''); 20 | 21 | // route is just an object with keys 22 | // name: string, path: string, isRoute: bool, children: array 23 | function route(name, ...children) { 24 | var path, props, isRoute = true; 25 | 26 | if (!children || !children.length) 27 | return { name, isRoute }; 28 | 29 | if (children[0].isRoute) 30 | return { name, children, isRoute }; 31 | 32 | if (typeof children[0] === 'string') 33 | path = children.shift(); 34 | 35 | if (children[0] && !children[0].isRoute) 36 | props = children.shift(); 37 | 38 | if (!children.length) 39 | children = null; 40 | 41 | return Object.assign({ name, path, children, isRoute }, props); 42 | } 43 | 44 | var _requirer, _generator; 45 | 46 | /** 47 | * Recurse over route tree and apply a generator. 48 | * @param {func} generator - function to be run and passed route on creation 49 | * @param {obj} opts - extra properties to be added to a route 50 | * @param {func} requirer - helper for a common use case, passing in require func 51 | * @param {obj} route - the tree of routes you are generating 52 | */ 53 | function routes(generator, opts, requirer, route) { 54 | // todo: having this tied to dir creation is bad coupling 55 | 56 | // opts is optional, shift if necessary 57 | if (!route) { 58 | route = requirer; 59 | requirer = opts; 60 | opts = { dir: 'components' }; 61 | 62 | // requirer optional :/ 63 | if (!route) { 64 | route = requirer; 65 | requirer = null; 66 | } 67 | } 68 | 69 | _requirer = requirer; 70 | _generator = generator; 71 | 72 | // we go through from top to bottom, to set the 73 | // parent path for the require's 74 | var routeTree = makeTree(route, defined(opts.dir) ? opts.dir + '/' : ''); 75 | 76 | // then we go again from bottom to top to require 77 | return makeRoutes(routeTree); 78 | } 79 | 80 | // once you've made your tree of routes, you'll want to do something 81 | // with them. This is a helper to recurse and call your generator 82 | function makeRoutes(route) { 83 | route.children = route.children ? route.children.map(makeRoutes) : null; 84 | return _generator(route, _requirer); 85 | } 86 | 87 | // makes the tree of routes, but adds a handlerPath prop 88 | // handlerPath is determined by parents dir attr, or name, 89 | // and is used later to require components 90 | function makeTree(route, parentsPath) { 91 | var children; 92 | 93 | if (route.children) 94 | children = route.children.map(child => { 95 | var childSubDir = pick(route.dir, route.name); 96 | var childParentsPath = parentsPath + (childSubDir ? childSubDir + '/' : ''); 97 | return makeTree(child, childParentsPath); 98 | }); 99 | 100 | var handlerPath = './' + parentsPath + proper(route.name); 101 | 102 | return { 103 | ...route, 104 | handlerPath, 105 | children 106 | }; 107 | } 108 | 109 | module.exports = { route, routes }; -------------------------------------------------------------------------------- /src/react-router/ParentRouteMixin.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var assign = require('react/lib/Object.assign'); 3 | 4 | var REF_NAME = '__routeHandler__'; 5 | 6 | module.exports = { 7 | 8 | contextTypes: { 9 | routeDepth: React.PropTypes.number.isRequired, 10 | router: React.PropTypes.func.isRequired 11 | }, 12 | 13 | childContextTypes: { 14 | routeDepth: React.PropTypes.number.isRequired 15 | }, 16 | 17 | getChildContext() { 18 | return { 19 | routeDepth: this.context.routeDepth + 1 20 | }; 21 | }, 22 | 23 | componentDidMount() { 24 | this._updateRouteComponent(this.refs[REF_NAME]); 25 | }, 26 | 27 | componentDidUpdate() { 28 | this._updateRouteComponent(this.refs[REF_NAME]); 29 | }, 30 | 31 | componentWillUnmount() { 32 | this._updateRouteComponent(null); 33 | }, 34 | 35 | _updateRouteComponent(component) { 36 | this.context.router.setRouteComponentAtDepth(this.getRouteDepth(), component); 37 | }, 38 | 39 | getRouteDepth() { 40 | return this.context.routeDepth; 41 | }, 42 | 43 | createChildRouteHandler(props) { 44 | var route = this.context.router.getRouteAtDepth(this.getRouteDepth()); 45 | var el = route ? React.createElement(route.handler, assign({}, props || this.props, { ref: REF_NAME })) : null; 46 | return el; 47 | } 48 | 49 | }; 50 | -------------------------------------------------------------------------------- /src/react-router/RoutedViewListMixin.js: -------------------------------------------------------------------------------- 1 | var ParentRouteMixin = require('./ParentRouteMixin'); 2 | 3 | // mixin for viewlists 4 | // works with react-router and gives some helper functions 5 | // to manage viewLists 6 | 7 | module.exports = Object.assign({}, ParentRouteMixin, { 8 | 9 | routedViewListProps(props) { 10 | return { 11 | scrollToStep: this.scrollToStep(), 12 | onViewEntered: (i) => { 13 | if (props && props.onViewEntered) 14 | props.onViewEntered(i); 15 | 16 | this._handleViewEntered(i); 17 | } 18 | }; 19 | }, 20 | 21 | scrollToStep() { 22 | return this.numActiveRoutes() - this.getRouteDepth(); 23 | }, 24 | 25 | childRouteHandler(props) { 26 | return this.createChildRouteHandler(props); 27 | }, 28 | 29 | // todo: debug why this is called more than it should be 30 | _handleViewEntered(i) { 31 | if (i === 0 && this.numActiveRoutes() > this.getRouteDepth()) { 32 | var r = this.context.router.getCurrentRoutes().reverse(); 33 | r.shift(); 34 | setTimeout(() => { 35 | this.context.router.transitionTo(r[0].path) 36 | }); 37 | } 38 | }, 39 | 40 | numActiveRoutes() { 41 | return this.context.router.getCurrentRoutes().length; 42 | }, 43 | 44 | hasChildRoute() { 45 | return this.numActiveRoutes() > this.getRouteDepth(); 46 | }, 47 | 48 | subRouteKey() { 49 | return this.context.router.getCurrentRoutes().reverse()[0].name 50 | + this.context.router.getCurrentParams().id; 51 | } 52 | }); -------------------------------------------------------------------------------- /src/react-router/generator.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { route, routes } = require('../index'); 3 | var { Route, DefaultRoute } = require('react-router'); 4 | 5 | // this generator ties together react-router with reapp-routes 6 | 7 | // reapp-routes has two main functions, routes and route. 8 | // routes takes a route() tree and builds it recursively with a generator 9 | // here our generator maps our route tree to react-router routes. 10 | 11 | function generator(route, requirer) { 12 | if (!route.handler) 13 | route.handler = requirer(route.handlerPath); 14 | 15 | return route.default ? 16 | : 17 | ; 18 | } 19 | 20 | module.exports = { 21 | route, 22 | 23 | // we are just currying the first argument 24 | // of routes to be this generator 25 | routes: routes.bind(null, generator) 26 | }; -------------------------------------------------------------------------------- /src/react-router/index.js: -------------------------------------------------------------------------------- 1 | var generator = require('./generator'); 2 | var render = require('./render'); 3 | var Env = require('reapp-platform/src/Env'); 4 | 5 | function run(routes, opts, cb) { 6 | var generatedRoutes = routes(generator); 7 | opts = opts || {}; 8 | 9 | if (Env.CLIENT) 10 | render.async(generatedRoutes, opts, cb); 11 | else 12 | return function(opts, cb) { 13 | render.sync(generatedRoutes, opts, cb); 14 | }; 15 | } 16 | 17 | // add mixins 18 | run.ParentRouteMixin = require('./ParentRouteMixin'); 19 | run.RoutedViewListMixin = require('./RoutedViewListMixin'); 20 | 21 | module.exports = run; -------------------------------------------------------------------------------- /src/react-router/render.js: -------------------------------------------------------------------------------- 1 | // uses react router to run an app, with two options (sync or async) 2 | var React = require('react'); 3 | var ReactDOM = require('react-dom'); 4 | var ReactDOMServer = require('react-dom/server'); 5 | var Router = require('react-router'); 6 | 7 | // look at statics key "fetchData" on the Handler component to get data 8 | function fetchAllData(routes, params) { 9 | var promises = routes 10 | .filter(route => route.handler.fetchData) 11 | .reduce((promises, route) => { 12 | promises[route.name] = route.handler.fetchData(params); 13 | return promises; 14 | }, {}); 15 | 16 | if (!Object.keys(promises).length) 17 | return Promise.resolve({}); 18 | 19 | const resolveAllOnObject = Promise.props || Promise.all || function() {}; 20 | return resolveAllOnObject(promises); 21 | } 22 | 23 | function renderToDocument(Handler, props, context) { 24 | return ReactDOM.render( 25 | , 26 | document.getElementById('app') 27 | ); 28 | } 29 | 30 | function renderToString(Handler, data) { 31 | return ReactDOMServer.renderToString(); 32 | } 33 | 34 | module.exports = { 35 | // sync will fetch all data *before* returning 36 | // ideal for running from server 37 | sync(routes, opts, cb) { 38 | opts = opts || {}; 39 | var render = opts.render || renderToString; 40 | var loc = opts.location || Router.HistoryLocation; 41 | 42 | Router.run(routes, loc, (Handler, state) => { 43 | fetchAllData(state.routes, state.params).then(data => { 44 | var out = render(Handler, { data }, opts.context); 45 | 46 | if (cb) 47 | cb(out, data); 48 | }); 49 | }); 50 | }, 51 | 52 | // async will render *first* without data, then fetch data and re-render 53 | // ideal for running from the client 54 | async(routes, opts, cb) { 55 | opts = opts || {}; 56 | var render = opts.render || renderToDocument; 57 | var loc = typeof opts.location === 'undefined' ? 58 | Router.HistoryLocation : 59 | opts.location; 60 | 61 | // cordova shouldn't use HistoryLocation 62 | if (process.env.PLATFORM === 'ios' || process.env.PLATFORM === 'android') 63 | loc = null; 64 | 65 | Router.run(routes, loc, (Handler, state) => { 66 | render(Handler, { state }, opts.context); 67 | fetchAllData(state.routes, state.params).then(data => { 68 | // only re-render if we fetched data 69 | if (Object.keys(data).length) { 70 | var out = render(Handler, { data }, opts.context); 71 | 72 | if (cb) 73 | cb(out, data); 74 | } 75 | }); 76 | }); 77 | } 78 | }; 79 | --------------------------------------------------------------------------------