├── .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 |
--------------------------------------------------------------------------------