├── examples ├── index.js ├── simple.css ├── master-detail.js ├── redux.js ├── animated.js ├── dynamic.js ├── auth-flow.js ├── server.js ├── tables.js ├── counter.js ├── sandbox.js └── simple.js ├── tests ├── prelude.js ├── server.js └── index.js ├── .babelrc ├── .eslintrc ├── .gitignore ├── karma.conf.js ├── LICENSE.txt ├── todo.md ├── sandbox.js ├── package.json ├── README.md ├── src └── index.js └── lib └── index.js /examples/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/simple.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/prelude.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(); 2 | -------------------------------------------------------------------------------- /examples/master-detail.js: -------------------------------------------------------------------------------- 1 | // standard master detail view 2 | -------------------------------------------------------------------------------- /examples/redux.js: -------------------------------------------------------------------------------- 1 | // integrate with redux-simple-router 2 | -------------------------------------------------------------------------------- /examples/animated.js: -------------------------------------------------------------------------------- 1 | // animate while transitioning between urls 2 | -------------------------------------------------------------------------------- /examples/dynamic.js: -------------------------------------------------------------------------------- 1 | // load modules dynamically via require.ensure/webpack 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "plugins": ["transform-decorators-legacy"] 4 | } -------------------------------------------------------------------------------- /examples/auth-flow.js: -------------------------------------------------------------------------------- 1 | // shirt circuit transition to a url, change url, resolve a task, and then resume transition to previous url 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-rackt", 3 | "rules": { 4 | "valid-jsdoc": 2, 5 | "react/jsx-uses-react": 1, 6 | "react/jsx-no-undef": 2, 7 | "react/jsx-uses-vars": 2, 8 | "jsx-quotes": 0 9 | }, 10 | "plugins": [ 11 | "react" 12 | ] 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # osx noise 2 | .DS_Store 3 | profile 4 | 5 | # xcode noise 6 | build/* 7 | *.mode1 8 | *.mode1v3 9 | *.mode2v3 10 | *.perspective 11 | *.perspectivev3 12 | *.pbxuser 13 | *.xcworkspace 14 | xcuserdata 15 | 16 | # svn & cvs 17 | .svn 18 | CVS 19 | node_modules 20 | coverage 21 | -------------------------------------------------------------------------------- /examples/server.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { renderToString } from 'react-dom/server' 3 | import { Router, Route, Link } from '../src' 4 | 5 | class App extends Component { 6 | render() { 7 | return
8 | { 9 | () =>
foo
10 | }
11 | 12 |
13 | } 14 | } 15 | 16 | function Bar() { 17 | return
bar
18 | } 19 | 20 | console.log(renderToString()) 21 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // this doesn't pass through babel, so no fancy js pls 2 | module.exports = function(config){ 3 | config.set({ 4 | browsers: ['Chrome'], 5 | files: ['./tests/index.js'], 6 | reporters: ['mocha', 'coverage'], 7 | mochaReporter: { 8 | output: 'autowatch' 9 | }, 10 | preprocessors: { 11 | '**/src/*.js': ['coverage'], 12 | './tests/index.js': ['webpack'], 13 | }, 14 | webpack: { 15 | module: { 16 | loaders: [{ 17 | test: /\.js$/, 18 | exclude: /node_modules/, 19 | loader: 'babel' 20 | }, { 21 | test: /\.js$/, 22 | include: require('path').resolve('src/'), 23 | loader: 'isparta' 24 | }] 25 | } 26 | }, 27 | webpackMiddleware: { 28 | noInfo: true 29 | }, 30 | frameworks: ['mocha', 'expect'] 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /examples/tables.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { Route, Router } from '../lib' 3 | 4 | function times(n, fn) { 5 | let arr = [] 6 | for (let i = 0; i < n; i++) { 7 | arr.push(fn(i)) 8 | } 9 | return arr 10 | } 11 | 12 | let ctr = 0 13 | 14 | class App extends Component { 15 | static contextTypes = { 16 | history: PropTypes.object 17 | } 18 | componentDidMount() { 19 | setInterval(() => { 20 | this.context.history.push('/' + ctr) 21 | ctr++ 22 | }, 10) 23 | } 24 | render() { 25 | return 26 | 27 | {times(20, i => 28 | 29 | {times(20, j => 30 | )} 35 | )} 36 | 37 |
31 | {loc => 32 | {i}:{j}:{loc.pathname} 33 | } 34 |
38 | } 39 | } 40 | 41 | 42 | export default 43 | -------------------------------------------------------------------------------- /examples/counter.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { render } from 'react-dom' 3 | import { Router, Route, Link } from '../src' 4 | 5 | const active = { 6 | border: '1px solid red' 7 | } 8 | 9 | class Counter extends Component { 10 | state = { 11 | count: 0 12 | } 13 | onClick = () => { 14 | this.setState({ 15 | count: this.state.count + 1 16 | }) 17 | } 18 | render() { 19 | return
20 | 25 | {location => 26 |
27 | clicked {this.state.count} times
28 | you are at {location.pathname} 29 |
} 30 |
31 |
32 | } 33 | } 34 | 35 | render(, document.getElementById('app')) 36 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 Sunil Pai 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | - properly 'nest' urls 2 | - examples 3 | - comparisons should/shouldn't include querystring? 4 | - webpack dev flow 5 | - handle multiple redirects on the same render tree 6 | - cache path regexps 7 | - a11y 8 | - test query string matching etc 9 | - perfffff 10 | 11 | (via rr) 12 | - stringifyQuery 13 | - parseQuery 14 | - expose history helpers 15 | 16 | - ~ transitions / leave / enter, onBeforeUnload * todo: authWare flow 17 | - ~ animations between urls * use hooks/springs? 18 | - ~ scroll states * use rackt/scroll-behavior, after historyV2 compat 19 | - ~ redirects * todo: server side handling 20 | - ~ testsssss 21 | 22 | - ✓ expose context.routah 23 | - ✓ dynamic routing * require.ensure should just work 24 | - ✓ redux-simple-router * it just works! 25 | 26 | - ✓ default browser history object 27 | - ✓ default node history object 28 | - ✓ path-to-regexp for matching 29 | - ✓ server side rendering 30 | - ✓ `` via RouteStack 31 | - ✓ overwrite context.history for a subtree 32 | 33 | - x ``? * not needed, just use a `` 34 | - x async data * not a concern of this library (or fetch prop? or falcor?) 35 | - x activeClass / activeStyle - removed - wasn't 'right' 36 | -------------------------------------------------------------------------------- /examples/sandbox.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { render } from 'react-dom' 3 | import { Router, Link, Route, Redirect } from '../src' 4 | 5 | 6 | class App extends Component { 7 | static contextTypes = { 8 | routah: PropTypes.object 9 | } 10 | render() { 11 | return console.log('enter', l.pathname) || cb() } onLeave={(l, cb) => console.log('leave', l.pathname) || cb()}>{ 12 | location => { 13 | let dest1 = Math.round(Math.random() * 1000) 14 | return
15 | you're at {location.pathname}
16 | { 17 | () => inside 18 | } 19 | console.log('enter', l.pathname) || cb() } onLeave={(l, cb) => console.log('leave', l.pathname) || cb()} onUnload={() => 'are you sure'}>{ 20 | () =>
matched!
21 | }
22 | /{dest1}
23 | or else console.log(this, 'this') || this.context.routah.history.push(`/secret`)}>secret
24 | and here's a secret link 25 |
26 | } 27 | }
28 | } 29 | } 30 | 31 | 32 | render(, document.getElementById('app')) 33 | -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | // x - active links 2 | // breadcrumbs 3 | // passing props to children 4 | // query params 5 | // sidebar 6 | 7 | import React, { Component } from 'react' 8 | import { render } from 'react-dom' 9 | import { Router, Route, Link, RouteStack, Redirect } from '../src' 10 | 11 | 12 | class App extends Component { 13 | render() { 14 | return
15 |
    16 |
  • about
  • 17 |
  • inbox
  • 18 |
  • home
  • 19 |
20 |
21 | { 22 | () =>
about us
23 | }
24 | { 25 | () =>
inbox
26 | }
27 | { 28 | () =>
dashboard
29 | }
30 | { 31 | () => 32 | } 33 | 34 | {() =>
stack about
}
35 | {() =>
stack inbox
}
36 | {() =>
stack bababooey
}
37 | {() =>
stack default
}
38 |
39 |
40 |
41 | } 42 | } 43 | 44 | render(, document.getElementById('app')) 45 | -------------------------------------------------------------------------------- /sandbox.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | import {render} from 'react-dom'; 3 | import {Router, Link, Route, Redirect} from './src'; 4 | 5 | 6 | class App extends Component{ 7 | static contextTypes = { 8 | routah: PropTypes.object 9 | }; 10 | render(){ 11 | console.log(this.context); 12 | return console.log('enter', l.pathname) || cb() } onLeave={(l, cb) => console.log('leave', l.pathname) || cb()}>{ 13 | location => { 14 | let dest1 = Math.round(Math.random() * 1000); 15 | return
16 | you're at {location.pathname}
17 | { 18 | loc => inside 19 | } 20 | console.log('enter', l.pathname) || cb() } onLeave={(l, cb) => console.log('leave', l.pathname) || cb()} onUnload={() => 'are you sure'}>{ 21 | () =>
matched!
22 | }
23 | /{dest1}
24 | or else console.log(this, 'this') || this.context.routah.history.push(`/secret`)}>secret
25 | and here's a secret link 26 |
; 27 | } 28 | }
; 29 | } 30 | } 31 | 32 | 33 | render(, document.getElementById('app')); 34 | -------------------------------------------------------------------------------- /tests/server.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | import React, {Component, PropTypes} from 'react'; 3 | import {Router, Route} from '../src'; 4 | import {renderToString, renderToStaticMarkup} from 'react-dom/server'; 5 | import {createMemoryHistory} from 'history'; 6 | 7 | import expect from 'expect'; 8 | import expectJSX from 'expect-jsx'; 9 | expect.extend(expectJSX); 10 | 11 | describe('server side routah', () => { 12 | it('prop: history', done => { 13 | class App extends Component{ 14 | static contextTypes = { 15 | history: PropTypes.object 16 | }; 17 | componentWillMount(){ 18 | this.context.history.listen(l => expect(l.pathname).toEqual('/xyz'))(); 19 | done(); 20 | } 21 | render(){ 22 | return null; 23 | } 24 | } 25 | 26 | renderToString(); 27 | }); 28 | it('prop: url', done => { 29 | class App extends Component{ 30 | static contextTypes = { 31 | history: PropTypes.object 32 | }; 33 | componentWillMount(){ 34 | this.context.history.listen(l => expect(l.pathname).toEqual('/xyz'))(); 35 | done(); 36 | } 37 | render(){ 38 | return null; 39 | } 40 | } 41 | 42 | renderToString(); 43 | 44 | }); 45 | it('should render to string/markup'); 46 | it('should catch redirects'); 47 | 48 | 49 | }); 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "routah", 3 | "version": "1.3.3", 4 | "description": "simple routing for react ", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "build": "babel src -d lib", 8 | "dev": " ./node_modules/.bin/babel-node ./node_modules/.bin/karma start karma.conf.js", 9 | "test": " ./node_modules/.bin/babel-node ./node_modules/.bin/karma start karma.conf.js --single-run", 10 | "test:server": "./node_modules/.bin/mocha tests/server.js --require tests/prelude.js" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "babel-cli": "^6.7.5", 16 | "babel-core": "^6.7.6", 17 | "babel-eslint": "^6.0.2", 18 | "babel-loader": "^6.2.4", 19 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 20 | "babel-preset-es2015": "^6.6.0", 21 | "babel-preset-react": "^6.5.0", 22 | "babel-preset-stage-0": "^6.5.0", 23 | "babel-register": "^6.7.2", 24 | "css-loader": "^0.23.1", 25 | "eslint": "^2.8.0", 26 | "eslint-config-rackt": "^1.1.1", 27 | "eslint-plugin-react": "^5.0.1", 28 | "expect": "^1.18.0", 29 | "expect-jsx": "^2.5.1", 30 | "isparta-loader": "^2.0.0", 31 | "karma": "^0.13.22", 32 | "karma-chrome-launcher": "^0.2.3", 33 | "karma-coverage": "^0.5.5", 34 | "karma-expect": "^1.1.2", 35 | "karma-mocha": "^0.2.2", 36 | "karma-mocha-reporter": "^2.0.1", 37 | "karma-webpack": "^1.7.0", 38 | "mocha": "^2.4.5", 39 | "react": "^15.0.1", 40 | "react-dom": "^15.0.1", 41 | "style-loader": "^0.13.1", 42 | "webpack": "^1.13.0" 43 | }, 44 | "dependencies": { 45 | "history": "^2.1.0", 46 | "path-to-regexp": "^1.2.1" 47 | }, 48 | "peerDependencies": { 49 | "react": "*" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | routah 2 | --- 3 | 4 | ** DEPRECATED - USE REACT ROUTER v4 ** 5 | 6 | `npm install routah --save` 7 | 8 | - heavily inspired by [react-router](https://github.com/rackt/react-router) and [react-motion](https://github.com/chenglou/react-motion) 9 | - render `` elements anywhere in your app 10 | - [express](http://expressjs.com/)-style pattern matching 11 | - server-side friendly 12 | - tests 13 | - more! 14 | 15 | ```jsx 16 | 17 | import {Router, Route, Link, Redirect} from 'routah'; 18 | 19 | function App(){ 20 | return
21 |
    22 | {/* link across the app */} 23 |
  • Page 1
  • 24 |
  • Page 2
  • 25 |
  • Page 3
  • 26 |
  • Page 4
  • 27 |
28 | 29 | {/* renders when the browser url is /1 */} 30 | 31 | {/* and similarly when /2 */} 32 | 33 | 34 | {/* match across paths */} 35 | { 36 | location => // you can use a render callback 37 |
38 | 39 | {/* render routes wherever */} 40 | 42 |
43 | }
44 | 45 | {/* you can also redirect to other portions of the app */} 46 | { 47 | location => // triggers a `history.push` 48 | } 49 | 50 | {/* read the docs/examples for more! */} 51 |
; 52 | } 53 | 54 | ReactDOM.render(, document.body) 55 | ``` 56 | 57 | 58 | Router 59 | --- 60 | 61 | Wrap your application in a `` element to start the router 62 | ```jsx 63 | render(, element); 64 | ``` 65 | 66 | - `history` - (optional) [history](https://github.com/rackt/history) object 67 | 68 | Route 69 | --- 70 | 71 | A `` element renders only when the current url matches the `path` expression. 72 | ```jsx 73 | // you can use a render-callback 74 | { 75 | () =>
About Us
76 | }
77 | 78 | // or pass the component and optionally props 79 | 80 | ``` 81 | 82 | - `path` - an [express-style](https://github.com/pillarjs/path-to-regexp) path matcher 83 | - `path` - an array of the above 84 | - render via `children (location, history)` - a [render-callback](https://discuss.reactjs.org/t/children-as-a-function-render-callbacks/626) 85 | - render via `component` - a `React.Component` which will receive *{location, history}* as props 86 | - `passProps` - additional props to transfer when using `component` 87 | - `onMount (location)` 88 | - `onEnter (location, callback)` 89 | - `onLeave (location, callback)` 90 | - `onUnload (location)` 91 | - `notFound (location)` - a render-callback when `path` doesn't match. defaults to `() => null` 92 | 93 | Link 94 | --- 95 | 96 | A `` is a replacement for `` elements 97 | ```jsx 98 | message {id} 99 | ``` 100 | 101 | - `to` - url 102 | - `to` - a [location descriptor](https://github.com/rackt/history/blob/master/docs/Glossary.md#locationdescriptor) 103 | - `onClick`, `className`, `style` - analogous to ReactDOM props 104 | 105 | Redirect 106 | --- 107 | 108 | A `` triggers a redirect to `to` whenever/wherever rendered. 109 | ```jsx 110 | { 111 | () => 112 | } 113 | ``` 114 | 115 | - `to` - url 116 | - `to` - a [location descriptor](https://github.com/rackt/history/blob/master/docs/Glossary.md#locationdescriptor) 117 | 118 | RouteStack 119 | --- 120 | 121 | This emulates a behavior from react-router - given one or more ``, render only the first matching element. This makes it easy to make Index/NotFound pages. eg - 122 | ```jsx 123 | }> 124 | 125 | 126 | 127 | 128 | ``` 129 | 130 | - `children` - one or more `` elements 131 | - `notFound (location)` - when no child matches. good for 404s! 132 | 133 | context.history 134 | --- 135 | 136 | The `history` object is passed via `context` to all its descendants. Use it to trigger actions on the url - 137 | 138 | - `push` - url 139 | - `push` - [location descriptor](https://github.com/rackt/history/blob/master/docs/Glossary.md#locationdescriptor) 140 | - `replace` - url 141 | - `replace` - [location descriptor](https://github.com/rackt/history/blob/master/docs/Glossary.md#locationdescriptor) 142 | - `go(n)` 143 | - `goBack()` 144 | - `goForward()` 145 | - [more](https://github.com/rackt/history/blob/master/docs/GettingStarted.md) 146 | 147 | 148 | differences from react-router 149 | --- 150 | 151 | - `Route` accepts a 'children as a function' [render-callback]([render-callback](https://discuss.reactjs.org/t/children-as-a-function-render-callbacks/626)) (as an alternative to `component`/`passProps` props) 152 | - `` elements can be rendered anywhere in the app 153 | - urls don't get 'nested', no activeClass/activeStyle - [issue #1](https://github.com/threepointone/routah/issues/1) 154 | - sibling `` elements don't depend on each other (use `` for similar behavior) 155 | - no async data/components/routes loading - consider using a lib like [AsyncProps](https://github.com/rackt/async-props), [react-resolver](http://ericclemmons.com/react-resolver/), etc 156 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, beforeEach, afterEach */ 2 | 3 | import React, { Component, PropTypes } from 'react' 4 | import { render, unmountComponentAtNode } from 'react-dom' 5 | import { findRenderedDOMComponentWithTag, Simulate } from 'react-addons-test-utils' 6 | import { Route, Router, Link, Redirect, RouteStack, connectHistory } from '../src' 7 | import { createMemoryHistory } from 'history' 8 | 9 | import expect from 'expect' 10 | import expectJSX from 'expect-jsx' 11 | expect.extend(expectJSX) 12 | 13 | 14 | describe('Router', () => { 15 | let node 16 | beforeEach(() => node = document.createElement('div')) 17 | afterEach(() => unmountComponentAtNode(node)) 18 | 19 | it('will introduce a `history` context', done => { 20 | class App extends Component { 21 | static contextTypes = { 22 | history: PropTypes.object 23 | } 24 | componentDidMount() { 25 | expect(this.context.history).toExist() 26 | done() 27 | } 28 | render() { 29 | return null 30 | } 31 | } 32 | 33 | render( 34 | 35 | , node) 36 | }) 37 | 38 | it('prop: default history object', () => { 39 | class App extends Component { 40 | static contextTypes = { 41 | history: PropTypes.object 42 | } 43 | componentDidMount() { 44 | this.context.history.listen(l => expect(l.pathname).toEqual(window.location.pathname))() 45 | } 46 | render() { 47 | return null 48 | } 49 | } 50 | 51 | render( 52 | 53 | , node) 54 | }) 55 | 56 | it('prop: custom history object', () => { 57 | class App extends Component { 58 | static contextTypes = { 59 | history: PropTypes.object 60 | } 61 | componentDidMount() { 62 | this.context.history.listen(l => expect(l.pathname).toEqual('/123'))() 63 | } 64 | render() { 65 | return null 66 | } 67 | } 68 | let h = createMemoryHistory('/123') 69 | render( 70 | 71 | , node) 72 | 73 | }) 74 | 75 | it('does not render multiple children', () => { 76 | function App() { 77 | return