├── .eslintignore
├── .babelrc
├── .gitignore
├── .github
└── preview.gif
├── examples
├── main
│ ├── Layout.jsx
│ ├── Login.jsx
│ ├── Home.jsx
│ ├── Account.jsx
│ ├── index.css
│ └── index.js
└── index.html
├── .travis.yml
├── test
├── helper.js
└── functional
│ ├── Route_spec.js
│ ├── index_spec.js
│ └── util_spec.js
├── rollup.config.js
├── CHANGELOG.md
├── src
├── Route.jsx
├── util.js
└── index.js
├── .eslintrc
├── webpack.config.js
├── Guids.md
├── package.json
├── README.md
└── dist
├── react-rainie-router.min.js
├── react-rainie-router.min.js.map
├── react-rainie-router.js
└── react-rainie-router.js.map
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/*
2 | node_modules/*
3 | test/*
4 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "presets": ["es2015-loose", "stage-0", "react"]
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | examples/__build__/
3 | .nyc_output/
4 | coverage/
5 |
--------------------------------------------------------------------------------
/.github/preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lanjingling0510/react-rainie-router/HEAD/.github/preview.gif
--------------------------------------------------------------------------------
/examples/main/Layout.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from '../../src/index.js';
3 |
4 |
5 | export default function Layout({path, children}) {
6 | return children;
7 | }
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - stable
5 | cache:
6 | directories:
7 | - node_modules
8 | before_install:
9 | - export CHROME_BIN=chromium-browser
10 | - export DISPLAY=:99.0
11 | - sh -e /etc/init.d/xvfb start
12 | after_success: npm run coverage
13 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/main/Login.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from '../../src/index.js';
3 |
4 |
5 | export default function Login({url}) {
6 | return (
7 |
8 | Welcome to my login
9 | current link: {url}
10 | go homepage
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/examples/main/Home.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from '../../src/index.js';
3 |
4 |
5 | export default function Home({url, title}) {
6 | return (
7 |
8 | Welcome to my {title}
9 | current link: {url}
10 | go account
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/examples/main/Account.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from '../../src/index.js';
3 |
4 |
5 | export default function Account({url, matches, name}) {
6 | return (
7 |
8 | Welcome to my Account
9 | my ID is : {matches.id}
10 | my name is : {name}
11 | current link: {url}
12 | go homepage
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/test/helper.js:
--------------------------------------------------------------------------------
1 | require('babel-polyfill');
2 | var jsdom = require('jsdom').jsdom;
3 | var exposedProperties = ['window', 'navigator', 'document'];
4 |
5 | global.document = jsdom('');
6 | global.window = document.defaultView;
7 | Object.keys(document.defaultView).forEach((property) => {
8 | if (typeof global[property] === 'undefined') {
9 | exposedProperties.push(property);
10 | global[property] = document.defaultView[property];
11 | }
12 | });
13 |
14 | global.navigator = {
15 | userAgent: 'node.js'
16 | };
17 |
--------------------------------------------------------------------------------
/examples/main/index.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | .home, .account {
7 | position: absolute;
8 | top: 0;
9 | bottom: 0;
10 | left: 0;
11 | right: 0;
12 | display: flex;
13 | flex-direction: column;
14 | align-items: center;
15 | justify-content: center;
16 | text-align: center;
17 | }
18 |
19 | h2 {
20 | color: #fff;
21 | }
22 |
23 | .home {
24 | background: #00BCD4;
25 | }
26 |
27 | .home .active {
28 | color: red;
29 | }
30 |
31 | .account {
32 | background: #68cf52;
33 | }
34 |
--------------------------------------------------------------------------------
/examples/main/index.js:
--------------------------------------------------------------------------------
1 | import './index.css';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import Router, { Link, listenBefore, Route } from '../../src/index.js';
5 | import Login from './Login';
6 | import Layout from './Layout';
7 | /** Stateless app */
8 | const App = () => (
9 |
10 |
11 |
12 |
15 |
16 | {
20 | return new Promise(function(resolve, reject) {
21 | require.ensure([], (require) => {
22 | const Home = require('./Home').default;
23 | resolve(Home);
24 | }, 'home');
25 | });
26 | }} />
27 | {
31 | return new Promise(function(resolve, reject) {
32 | require.ensure([], (require) => {
33 | const Account = require('./Account').default;
34 | resolve(Account);
35 | }, 'account');
36 | });
37 | }} />
38 |
39 |
40 |
41 |
42 |
43 |
44 | );
45 |
46 | ReactDOM.render( , document.getElementById('react-container'));
47 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import json from 'rollup-plugin-json';
3 | import memory from 'rollup-plugin-memory';
4 | import resolve from 'rollup-plugin-node-resolve';
5 | import cjs from 'rollup-plugin-commonjs';
6 | import fs from 'fs';
7 |
8 | var babelRc = JSON.parse(fs.readFileSync('.babelrc','utf8')); // eslint-disable-line
9 |
10 | export default {
11 | entry: 'src/index.js',
12 | exports: 'default',
13 | plugins: [
14 | json(),
15 | memory({
16 | path: 'src/index',
17 | contents: "export { default } from './index'"
18 | }),
19 | babel({
20 | babelrc: false,
21 | presets: ['es2015-minimal-rollup'].concat(babelRc.presets.slice(1)),
22 | plugins: babelRc.plugins,
23 | exclude: 'node_modules/**'
24 | }),
25 | // cjs({
26 | // exclude: 'node_modules/process-es6/**',
27 | // include: [
28 | // 'node_modules/fbjs/**',
29 | // 'node_modules/object-assign/**',
30 | // 'node_modules/react/**',
31 | // 'node_modules/react-dom/**'
32 | // ]
33 | // }),
34 | // resolve({
35 | // jsnext: true,
36 | // main: true
37 | // })
38 | ],
39 | };
40 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | v1.1.0 - Mon, 05 Dec 2016 11:41:35 GMT
2 | --------------------------------------
3 |
4 | -
5 |
6 |
7 | v1.0.9 - Mon, 05 Dec 2016 11:37:17 GMT
8 | --------------------------------------
9 |
10 | -
11 |
12 |
13 | v1.0.8 - Mon, 05 Dec 2016 10:09:55 GMT
14 | --------------------------------------
15 |
16 | -
17 |
18 |
19 | v1.0.6 - Mon, 05 Sep 2016 08:33:24 GMT
20 | --------------------------------------
21 |
22 | -
23 |
24 |
25 | v1.0.5 - Mon, 05 Sep 2016 08:31:50 GMT
26 | --------------------------------------
27 |
28 | -
29 |
30 |
31 | v1.0.5 - Mon, 05 Sep 2016 08:25:41 GMT
32 | --------------------------------------
33 |
34 | -
35 |
36 |
37 | v1.0.4 - Mon, 05 Sep 2016 08:25:25 GMT
38 | --------------------------------------
39 |
40 | -
41 |
42 |
43 | v1.0.4 - Mon, 05 Sep 2016 08:24:43 GMT
44 | --------------------------------------
45 |
46 | -
47 |
48 |
49 | v1.0.3 - Mon, 05 Sep 2016 05:41:30 GMT
50 | --------------------------------------
51 |
52 | - [150f351](../../commit/150f351) [added] ✅ add Link property of "activeClassName"
53 |
54 |
55 | v1.0.2 - Mon, 05 Sep 2016 03:22:52 GMT
56 | --------------------------------------
57 |
58 | - [6b53bf9](../../commit/6b53bf9) [added] ✅ add Link property of "to"
59 |
60 |
61 | v1.0.1 - Sun, 04 Sep 2016 08:41:38 GMT
62 | --------------------------------------
63 |
64 | -
--------------------------------------------------------------------------------
/src/Route.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function isPromise(obj) {
4 | return !!obj &&
5 | (typeof obj === 'object' || typeof obj === 'function') &&
6 | typeof obj.then === 'function';
7 | }
8 |
9 |
10 | class Route extends React.Component {
11 | static PropTypes = {
12 | component: React.PropTypes.node,
13 | getComponent: React.PropTypes.func,
14 | }
15 |
16 | state = {
17 | SplitComponent: undefined,
18 | }
19 |
20 | componentWillMount() {
21 | const { component, getComponent} = this.props;
22 | if (!component && getComponent) {
23 | const promise = getComponent();
24 | if (isPromise(promise)) {
25 | promise.then(SplitComponent => {
26 | this.setState({ SplitComponent });
27 | });
28 | }
29 | }
30 | }
31 |
32 | render () {
33 | const {
34 | component: RoutedComponent,
35 | getComponent,
36 | ..._props,
37 | } = this.props;
38 |
39 | const { SplitComponent } = this.state;
40 |
41 |
42 | if (RoutedComponent)
43 | return ;
44 |
45 | return SplitComponent ?
46 | : null;
47 | }
48 | }
49 |
50 | export default Route;
51 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "parser": "babel-eslint",
4 | "extends": "eslint:recommended",
5 | "plugins": [
6 | "react"
7 | ],
8 | "env": {
9 | "browser": true,
10 | "mocha": true,
11 | "es6": true,
12 | "node": true
13 | },
14 | "parserOptions": {
15 | "ecmaFeatures": {
16 | "modules": true,
17 | "jsx": true
18 | }
19 | },
20 | "globals": {
21 | "sinon": true,
22 | "expect": true
23 | },
24 | "rules": {
25 | "react/jsx-uses-react": 2,
26 | "react/jsx-uses-vars": 2,
27 | "no-unused-vars": [1, { "varsIgnorePattern": "^h$" }],
28 | "no-cond-assign": 1,
29 | "no-empty": 0,
30 | "no-console": 1,
31 | "semi": 2,
32 | "camelcase": 0,
33 | "comma-style": 2,
34 | "comma-dangle": 0,
35 | "indent": [2, 4, {"SwitchCase": 1}],
36 | "no-mixed-spaces-and-tabs": [2, "smart-tabs"],
37 | "no-trailing-spaces": [2, { "skipBlankLines": true }],
38 | "max-nested-callbacks": [2, 5],
39 | "no-eval": 2,
40 | "no-implied-eval": 2,
41 | "no-new-func": 2,
42 | "guard-for-in": 2,
43 | "eqeqeq": 0,
44 | "no-else-return": 2,
45 | "no-redeclare": 2,
46 | "no-dupe-keys": 2,
47 | "radix": 2,
48 | "strict": [2, "never"],
49 | "no-shadow": 0,
50 | "callback-return": [1, ["callback", "cb", "next", "done"]],
51 | "no-delete-var": 2,
52 | "no-undef-init": 2,
53 | "no-shadow-restricted-names": 2,
54 | "handle-callback-err": 0,
55 | "no-lonely-if": 2,
56 | "keyword-spacing": 2,
57 | "constructor-super": 2,
58 | "no-this-before-super": 2,
59 | "no-dupe-class-members": 2,
60 | "no-const-assign": 2,
61 | "prefer-spread": 2,
62 | "no-useless-concat": 2,
63 | "no-var": 2,
64 | "object-shorthand": 2,
65 | "prefer-arrow-callback": 2
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | var path = require('path');
3 | var fs = require('fs');
4 | var webpack = require('webpack');
5 | var HtmlWebpackPlugin = require('html-webpack-plugin');
6 | var EXAMPLES_DIR = path.resolve(__dirname, 'examples');
7 |
8 |
9 | module.exports = {
10 | entry: buildEntries(),
11 | resolve: {
12 | extensions: ['', '.js', '.jsx']
13 | },
14 | module: {
15 | loaders: [{
16 | test: /\.jsx?$/,
17 | loaders: ['babel'],
18 | exclude: /node_modules/,
19 | }, {
20 | test: /\.css$/,
21 | loader: 'style!css'
22 | }],
23 | },
24 | output: {
25 | path: 'examples/__build__',
26 | publicPath: '/',
27 | filename: '[name].js',
28 | chunkFilename: "[name].chunk.js"
29 | },
30 | devTool: 'eval-source-map',
31 | devServer: {
32 | historyApiFallback: true,
33 | hot: true,
34 | inline: true,
35 | progress: true,
36 | host: '0.0.0.0',
37 | },
38 | plugins: [
39 | new HtmlWebpackPlugin({
40 | title: 'example',
41 | template: 'examples/index.html', // Load a custom template
42 | inject: 'body' // Inject all scripts into the body
43 | }),
44 | new webpack.DefinePlugin({
45 | 'process.env.NODE_ENV': '"development"'
46 | }),
47 | new webpack.HotModuleReplacementPlugin(),
48 | ]
49 | };
50 |
51 |
52 | function buildEntries() {
53 | return fs.readdirSync(EXAMPLES_DIR).reduce(function (a, b) {
54 | if (b === '__build__') {
55 | return a;
56 | }
57 |
58 | var isDraft = b.charAt(0) === '_';
59 |
60 | if (!isDraft && isDirectory(path.join(EXAMPLES_DIR, b))) {
61 | a[b] = path.join(EXAMPLES_DIR, b, 'index.js');
62 | }
63 |
64 | return a;
65 | }, {});
66 | }
67 |
68 |
69 | function isDirectory(dir) {
70 | return fs.lstatSync(dir).isDirectory();
71 | }
72 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | const EMPTY = {};
2 |
3 | export function exec(url, route, opts = EMPTY) {
4 | let reg = /(?:\?([^#]*))?(#.*)?$/,
5 | c = url.match(reg),
6 | matches = {},
7 | ret;
8 | if (c && c[1]) {
9 | let p = c[1].split('&');
10 | for (let i = 0; i < p.length; i++) {
11 | let r = p[i].split('=');
12 | matches[decodeURIComponent(r[0])] = decodeURIComponent(r.slice(1).join('='));
13 | }
14 | }
15 | url = segmentize(url.replace(reg, ''));
16 | route = segmentize(route || '');
17 | let max = Math.max(url.length, route.length);
18 | for (let i = 0; i < max; i++) {
19 | if (route[i] && route[i].charAt(0) === ':') {
20 | let param = route[i].replace(/(^\:|[+*?]+$)/g, ''),
21 | flags = (route[i].match(/[+*?]+$/) || EMPTY)[0] || '',
22 | plus = ~flags.indexOf('+'),
23 | star = ~flags.indexOf('*'),
24 | val = url[i] || '';
25 | if (!val && !star && (flags.indexOf('?') < 0 || plus)) {
26 | ret = false;
27 | break;
28 | }
29 | matches[param] = decodeURIComponent(val);
30 | if (plus || star) {
31 | matches[param] = url.slice(i).map(decodeURIComponent).join('/');
32 | break;
33 | }
34 | } else if (route[i] !== url[i]) {
35 | ret = false;
36 | break;
37 | }
38 | }
39 | if (opts.default !== true && ret === false) return false;
40 | return matches;
41 | }
42 |
43 |
44 | export function pathRankSort(a, b) {
45 | let aAttr = a.props || EMPTY,
46 | bAttr = b.props || EMPTY;
47 | if (aAttr.default) return 1;
48 | if (bAttr.default) return -1;
49 | let diff = rank(aAttr.path) - rank(bAttr.path);
50 | return diff || (aAttr.path.length - bAttr.path.length);
51 | }
52 |
53 | export function segmentize(url) {
54 | return strip(url).split('/');
55 | }
56 |
57 | export function rank(url) {
58 | return (strip(url).match(/\/+/g) || '').length;
59 | }
60 |
61 | export function strip(url) {
62 | return url.replace(/(^\/+|\/+$)/g, '');
63 | }
64 |
--------------------------------------------------------------------------------
/Guids.md:
--------------------------------------------------------------------------------
1 |
2 | ## Router
3 | Primary component of React Router. It keeps your UI and the URL in sync.
4 | ### Props
5 |
6 | ##### `url`
7 | if not set, using `location.pathname + location.search`.
8 |
9 | ## Router.listenBefore
10 |
11 | There is an opportunity to prevent or delay routing Jump
12 | ```js
13 |
14 | // prevent routing navigate
15 | Router.listenBefore(() => {
16 | return Promise.reject();
17 | })
18 |
19 | // delay routing navigate
20 | Router.listenBefore(() => {
21 | return new Promise(function(resolve, reject) {
22 | setTimeout(() => {
23 | resolve();
24 | }, 500)
25 | });
26 | })
27 |
28 | ```
29 |
30 | ## Router.route
31 |
32 | dynamic routing navigate
33 |
34 | ```js
35 | const App = () => (
36 |
42 | );
43 |
44 | ReactDOM.render( , document.getElementById('react-container'));
45 | Router.route('/account/123');
46 |
47 | ```
48 |
49 |
50 | ## Router.Link
51 | The primary way to allow users to navigate around your application. will render a fully accessible anchor tag with the proper href.
52 |
53 | ```js
54 |
55 | // Given a route like :
56 | {user.name}
57 | ```
58 |
59 | ## Router.Route
60 | A is used to declaratively map routes to your application's component hierarchy.
61 |
62 | ### Props
63 |
64 | ##### `path`
65 | The path used in the URL.
66 |
67 | ##### `component`
68 | A single component to be rendered when the route matches the URL.
69 |
70 | ##### `getComponent(callback)`
71 | Same as component but asynchronous, useful for code-splitting.
72 |
73 | ```js
74 |
75 | {
79 | return new Promise(function(resolve, reject) {
80 | require.ensure([], (require) => {
81 | const Account = require('./Account').default;
82 | resolve(Account);
83 | }, 'account');
84 | });
85 | }} />
86 | ```
87 |
--------------------------------------------------------------------------------
/test/functional/Route_spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { assert, expect } from 'chai';
3 | import sinon from 'sinon';
4 | import { mount, shallow } from 'enzyme';
5 | import Route from '../../src/Route.jsx';
6 |
7 | describe('Route', () => {
8 |
9 | it('should render RoutedComponent', () => {
10 | const Component = (props) => (
11 | component
12 | );
13 |
14 | const router = mount(
15 |
18 | );
19 | expect(router.find('Component').first().prop('path')).to.equal('/path');
20 | expect(router.find('.routed-component')).to.have.length(1);
21 | });
22 |
23 | it ('should render null', () => {
24 | const Component = (props) => (
25 | component
26 | );
27 |
28 | const router = mount(
29 | {
32 | return new Promise(function(resolve, reject) {
33 | require.ensure([], (require) => {
34 | setTimeout(() => {
35 | resolve(Component);
36 | }, 100);
37 | }, 'account');
38 | });
39 | }} />
40 | );
41 |
42 | expect(router.find('.routed-component')).to.have.length(0);
43 | });
44 |
45 | it('should render SplitComponent', () => {
46 | const Component = (props) => (
47 | component
48 | );
49 |
50 | const router = mount(
51 | {
55 | return new Promise(function(resolve, reject) {
56 | setTimeout(() => {
57 | resolve(Component);
58 | }, 100);
59 | });
60 | }} />
61 | );
62 |
63 | return new Promise(function(resolve, reject) {
64 | setTimeout(() => {
65 | resolve(router.find('.routed-component'));
66 | }, 200);
67 | }).then((component => {
68 | expect(component).to.have.length(1);
69 | expect(router.find('Component').prop('path')).to.equal('/path/:id');
70 | expect(router.find('Component').prop('name')).to.equal('rainie');
71 | }));
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/test/functional/index_spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { assert, expect } from 'chai';
3 | import sinon from 'sinon';
4 | import { mount, shallow } from 'enzyme';
5 | import Router, { Link, listenBefore, Route, route } from '../../src/index.js';
6 |
7 |
8 | describe('Router', () => {
9 | let Home,
10 | Account,
11 | Default,
12 | App,
13 | router;
14 |
15 | before (function () {
16 | Home = ({url, title}) => {
17 | return (
18 |
19 | Welcome to my {title}
20 | current link: {url}
21 | go account
22 |
23 | );
24 | }
25 |
26 | Account = ({url, matches, name}) => {
27 | return (
28 |
29 | Account: {matches.id}
30 | my name is : {name}
31 | current link: {url}
32 | go homepage
33 |
34 | );
35 | }
36 |
37 | Default = () => (
38 |
39 |
default
40 |
go homepage
41 |
42 | )
43 |
44 |
45 |
46 | router = mount(
47 |
49 |
50 |
51 |
52 |
53 | );
54 | });
55 |
56 |
57 | it('should jump to another default route', () => {
58 | expect(router.find('.default')).to.have.length(1);
59 | });
60 |
61 |
62 | it('should jump to home router', () => {
63 | const spyFunction1 = sinon.spy(Router.prototype, 'shouldComponentUpdate');
64 | router.instance().routeTo('/');
65 | sinon.assert.calledOnce(spyFunction1);
66 | spyFunction1.restore();
67 | expect(router.find('.home')).to.have.length(1);
68 | });
69 |
70 | it('should jump to account router, and get param id', () => {
71 | router.instance().routeTo('/account/123');
72 | expect(router.find('Account')).to.have.length(1);
73 | expect(router.find('Account').props().matches).to.eql({ id: '123' });
74 | });
75 |
76 | it ('should not jump to home router', () => {
77 | listenBefore(() => {
78 | return Promise.reject();
79 | });
80 | route('/');
81 | expect(router.find('.home')).to.have.length(0);
82 | })
83 |
84 |
85 | });
86 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-rainie-router",
3 | "version": "1.1.0",
4 | "description": "",
5 | "amdName": "reactRainieRouter",
6 | "main": "dist/react-rainie-router.js",
7 | "minified:main": "dist/react-rainie-router.min.js",
8 | "scripts": {
9 | "clean": "rm -rf dist/",
10 | "build": "npm-run-all clean transpile minify size",
11 | "transpile": "rollup -c rollup.config.js -m ${npm_package_main}.map -f umd -n $npm_package_amdName -o $npm_package_main",
12 | "minify": "uglifyjs $npm_package_main -cm -o $npm_package_minified_main -p relative --in-source-map ${npm_package_main}.map --source-map ${npm_package_minified_main}.map",
13 | "size": "size=$(gzip-size $npm_package_minified_main | pretty-bytes) && echo \"gzip size: $size\"",
14 | "start": "webpack-dev-server",
15 | "release": "npm run build && node_modules/.bin/release",
16 | "preview-release": "changelog -t preview -s",
17 | "mocha": "./node_modules/.bin/mocha --compilers js:babel-core/register --require ./test/helper.js --recursive",
18 | "test": "./node_modules/.bin/nyc npm run mocha",
19 | "coverage": "./node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls"
20 | },
21 | "author": "",
22 | "license": "ISC",
23 | "devDependencies": {
24 | "babel-core": "^6.14.0",
25 | "babel-eslint": "^6.1.2",
26 | "babel-loader": "^6.2.4",
27 | "babel-polyfill": "^6.13.0",
28 | "babel-preset-es2015": "^6.14.0",
29 | "babel-preset-es2015-loose": "^7.0.0",
30 | "babel-preset-es2015-minimal-rollup": "^2.0.0",
31 | "babel-preset-react": "^6.11.1",
32 | "babel-preset-stage-0": "^6.5.0",
33 | "chai": "^3.5.0",
34 | "coveralls": "^2.11.9",
35 | "css-loader": "^0.24.0",
36 | "enzyme": "^2.4.1",
37 | "eslint": "^3.3.1",
38 | "eslint-config-airbnb": "^10.0.1",
39 | "eslint-plugin-import": "^1.13.0",
40 | "eslint-plugin-jsx-a11y": "^2.1.0",
41 | "eslint-plugin-react": "^6.1.2",
42 | "gzip-size-cli": "^1.0.0",
43 | "html-webpack-plugin": "^2.21.0",
44 | "jsdom": "^9.8.3",
45 | "mocha": "2.3.3",
46 | "mocha-lcov-reporter": "^1.2.0",
47 | "npm-run-all": "^3.0.0",
48 | "nyc": "^8.1.0",
49 | "pretty-bytes-cli": "^2.0.0",
50 | "react": "^15.3.1",
51 | "react-addons-test-utils": "^15.3.1",
52 | "react-dom": "^15.3.1",
53 | "rf-release": "^0.4.0",
54 | "rollup": "^0.36.4",
55 | "rollup-plugin-babel": "^2.6.1",
56 | "rollup-plugin-commonjs": "^3.3.1",
57 | "rollup-plugin-json": "^2.0.1",
58 | "rollup-plugin-memory": "^2.0.0",
59 | "rollup-plugin-node-globals": "^1.0.6",
60 | "rollup-plugin-node-resolve": "2.0.0",
61 | "rollup-plugin-replace": "^1.1.1",
62 | "sinon": "^1.17.4",
63 | "style-loader": "^0.13.1",
64 | "uglify-js": "^2.7.3",
65 | "webpack": "^1.13.1",
66 | "webpack-dev-server": "^1.14.1"
67 | }
68 | }
--------------------------------------------------------------------------------
/test/functional/util_spec.js:
--------------------------------------------------------------------------------
1 | import { exec, pathRankSort, segmentize, rank, strip } from '../../src/util';
2 | import { expect } from 'chai';
3 |
4 | describe('util', () => {
5 | describe('strip', () => {
6 | it('should strip preceeding slashes', () => {
7 | expect(strip('')).to.equal('');
8 | expect(strip('/')).to.equal('');
9 | expect(strip('/a')).to.equal('a');
10 | expect(strip('//a')).to.equal('a');
11 | expect(strip('//a/')).to.equal('a');
12 | });
13 |
14 | it('should strip trailing slashes', () => {
15 | expect(strip('')).to.equal('');
16 | expect(strip('/')).to.equal('');
17 | expect(strip('a/')).to.equal('a');
18 | expect(strip('/a//')).to.equal('a');
19 | });
20 | });
21 |
22 | describe('rank', () => {
23 | it('should return number of path segments', () => {
24 | expect(rank('')).to.equal(0);
25 | expect(rank('/')).to.equal(0);
26 | expect(rank('//')).to.equal(0);
27 | expect(rank('a/b/c')).to.equal(2);
28 | expect(rank('/a/b/c/')).to.equal(2);
29 | });
30 | });
31 |
32 | describe('segmentize', () => {
33 | it('should split path on slashes', () => {
34 | expect(segmentize('')).to.eql(['']);
35 | expect(segmentize('/')).to.eql(['']);
36 | expect(segmentize('//')).to.eql(['']);
37 | expect(segmentize('a/b/c')).to.eql(['a','b','c']);
38 | expect(segmentize('/a/b/c/')).to.eql(['a','b','c']);
39 | });
40 | });
41 |
42 | describe('pathRankSort', () => {
43 | it('should sort by segment count', () => {
44 | let paths = arr => arr.map( path => ({props:{path}}) );
45 |
46 | expect(
47 | paths(['/a/b/','/a/b','/','b']).sort(pathRankSort)
48 | ).to.eql(
49 | paths(['/','b','/a/b','/a/b/'])
50 | );
51 | });
52 |
53 | it('should return default routes last', () => {
54 | let paths = arr => arr.map( path => ({props:{path}}) );
55 |
56 | let defaultPath = {props:{default:true}};
57 | let p = paths(['/a/b/','/a/b','/','b']);
58 | p.splice(2,0,defaultPath);
59 |
60 | expect(
61 | p.sort(pathRankSort)
62 | ).to.eql(
63 | paths(['/','b','/a/b','/a/b/']).concat(defaultPath)
64 | );
65 | });
66 | });
67 |
68 | describe('exec', () => {
69 | it('should match explicit equality', () => {
70 | expect(exec('/','/')).to.eql({});
71 | expect(exec('/a','/a')).to.eql({});
72 | expect(exec('/a','/b')).to.eql(false);
73 | expect(exec('/a/b','/a/b')).to.eql({});
74 | expect(exec('/a/b','/a/a')).to.eql(false);
75 | expect(exec('/a/b','/b/b')).to.eql(false);
76 | });
77 |
78 | it('should match param segments', () => {
79 | expect(exec('/', '/:foo')).to.eql(false);
80 | expect(exec('/bar', '/:foo')).to.eql({ foo:'bar' });
81 | });
82 |
83 | it('should match optional param segments', () => {
84 | expect(exec('/', '/:foo?')).to.eql({ foo:'' });
85 | expect(exec('/bar', '/:foo?')).to.eql({ foo:'bar' });
86 | expect(exec('/', '/:foo?/:bar?')).to.eql({ foo:'', bar:'' });
87 | expect(exec('/bar', '/:foo?/:bar?')).to.eql({ foo:'bar', bar:'' });
88 | expect(exec('/bar', '/:foo?/bar')).to.eql(false);
89 | expect(exec('/foo/bar', '/:foo?/bar')).to.eql({ foo:'foo' });
90 | });
91 |
92 | it('should match splat param segments', () => {
93 | expect(exec('/', '/:foo*')).to.eql({ foo:'' });
94 | expect(exec('/a', '/:foo*')).to.eql({ foo:'a' });
95 | expect(exec('/a/b', '/:foo*')).to.eql({ foo:'a/b' });
96 | expect(exec('/a/b/c', '/:foo*')).to.eql({ foo:'a/b/c' });
97 | });
98 |
99 | it('should match required splat param segments', () => {
100 | expect(exec('/', '/:foo+')).to.eql(false);
101 | expect(exec('/a', '/:foo+')).to.eql({ foo:'a' });
102 | expect(exec('/a/b', '/:foo+')).to.eql({ foo:'a/b' });
103 | expect(exec('/a/b/c', '/:foo+')).to.eql({ foo:'a/b/c' });
104 | });
105 | });
106 | });
107 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-rainie-router
2 |
3 | [![Travis][build-badge]][build] [![npm package][npm-badge]][npm] [![Coveralls][coveralls-badge]][coveralls]
4 |
5 | **It has a similar and simple features for react-router,react-rainie-router is a fast, 2kb alternative to react-router.**
6 |
7 | react-rainie-router provides a component that conditionally renders its children when the URL matches their path. It also automatically wires up elements up to the router.
8 |
9 |
10 |
11 |
12 |
13 |
14 | ## Features
15 |
16 | - It is only 2k!, We only use our frequently used functions. Including Route Mathing, Nesting, default routing, Link, dynamic routing...
17 | - We can delay or prevent routing navigate by `listenBefore` before routing jump to another. And `listenBefore` should return a promise object.
18 | - It's no limit to the number of `Router` component nested other component.
19 | - Support coding Spliting by `getComponent` from `Route` component.
20 | - It does not depend on `history`, but using html5 history API.
21 |
22 | ## Docs & Help
23 |
24 | * [Guides and Api docs](Guids.md)
25 | * [Changelog](CHANGELOG.md)
26 |
27 |
28 |
29 | ## Getting Started
30 | ### Install
31 |
32 | Using [npm](https://www.npmjs.com/):
33 |
34 | $ npm install react-rainie-router --save
35 |
36 | ### Import what you need
37 |
38 | The following guide assumes you have some sort of ES2015 build set up using babel and/or webpack/browserify/gulp/grunt/etc.
39 |
40 | ```js
41 | import React from 'react';
42 | import ReactDOM from 'react-dom';
43 | import Router, { Link, listenBefore, Route } from 'react-rainie-router';
44 | ```
45 |
46 | ### Usage Example
47 |
48 | ```js
49 |
50 | /** Stateless app */
51 |
52 | function Home({url, title}) {
53 | return (
54 |
55 | Welcome to my {title}
56 | current link: {url}
57 | go account
58 |
59 | );
60 | }
61 |
62 | function Account({url, matches, name}) {
63 | return (
64 |
65 | Account: {matches.id}
66 | my name is : {name}
67 | current link: {url}
68 | go homepage
69 |
70 | );
71 | }
72 |
73 | const App = () => (
74 |
80 | );
81 |
82 | ReactDOM.render( , document.getElementById('react-container'));
83 |
84 | ```
85 |
86 | ## How to Contribute
87 |
88 | Anyone and everyone is welcome to contribute to this project. The best way to
89 | start is by checking our [open issues](https://github.com/lanjingling0510/react-rainie-router/issues),
90 | [submit a new issues](https://github.com/lanjingling0510/react-rainie-router/issues/new?labels=bug) or
91 | [feature request](https://github.com/lanjingling0510/react-rainie-router/issues/new?labels=enhancement),
92 | participate in discussions, upvote or downvote the issues you like or dislike.
93 |
94 |
95 |
96 | [npm-badge]: https://img.shields.io/npm/v/react-rainie-router.svg?style=flat-square
97 | [npm]: https://www.npmjs.com/package/react-rainie-router
98 | [build-badge]: https://img.shields.io/travis/lanjingling0510/react-rainie-router/master.svg?style=flat-square
99 | [build]: https://travis-ci.org/lanjingling0510/react-rainie-router
100 | [coveralls-badge]: https://img.shields.io/coveralls/lanjingling0510/react-rainie-router.svg?style=flat-square
101 | [coveralls]: https://coveralls.io/github/lanjingling0510/react-rainie-router
102 |
--------------------------------------------------------------------------------
/dist/react-rainie-router.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("react")):"function"==typeof define&&define.amd?define(["react"],e):t.reactRainieRouter=e(t.React)}(this,function(t){"use strict";function e(t,e){var n=arguments.length<=2||void 0===arguments[2]?v:arguments[2],r=/(?:\?([^#]*))?(#.*)?$/,i=t.match(r),u={},a=void 0;if(i&&i[1])for(var p=i[1].split("&"),c=0;c=0||Object.prototype.hasOwnProperty.call(t,o)&&(n[o]=t[o]);return n},O=function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e},R=function(e){function n(){var t,o,r;y(this,n);for(var i=arguments.length,u=Array(i),a=0;a func(url)))
38 | .then(() => {
39 | if (typeof url !== 'string' && url.url) {
40 | replace = url.replace;
41 | url = url.url;
42 | }
43 | setUrl(url, replace
44 | ? 'replace'
45 | : 'push');
46 | return routeTo(url);
47 | }).catch(() => {});
48 | }
49 |
50 | function routeTo(url) {
51 | let didRoute = false;
52 | ROUTERS.forEach(router => {
53 | if (router.routeTo(url) === true) {
54 | didRoute = true;
55 | }
56 | });
57 | return didRoute;
58 | }
59 |
60 | function routeFromLink(node) {
61 | // only valid elements
62 | if (!node || !node.getAttribute)
63 | return;
64 |
65 | let href = node.getAttribute('href') || node.getAttribute('to'),
66 | target = node.getAttribute('target');
67 |
68 | // ignore links with targets and non-path URLs
69 | if (!href || !href.match(/^\//g) || (target && !target.match(/^_?self$/i)))
70 | return;
71 |
72 | // attempt to route, if no match simply cede control to browser
73 | return route(href);
74 | }
75 |
76 | function handleLinkClick(delay) {
77 | return e => _handleLinkClick(e, delay);
78 | }
79 |
80 | function _handleLinkClick(e, delay) {
81 | const target = e.currentTarget || e.target || this;
82 | if (e.button !== 0)
83 | return;
84 |
85 | if (typeof delay === 'number') {
86 | // access the event properties in an asynchronous way
87 | e.persist();
88 | setTimeout(() => routeFromLink(target), delay);
89 | return prevent(e);
90 | }
91 |
92 | routeFromLink(target);
93 | return prevent(e);
94 | }
95 |
96 | function prevent(e) {
97 | if (e) {
98 | if (e.stopImmediatePropagation)
99 | e.stopImmediatePropagation();
100 | if (e.stopPropagation)
101 | e.stopPropagation();
102 | e.preventDefault();
103 | }
104 | return false;
105 | }
106 |
107 | if (typeof addEventListener === 'function') {
108 | addEventListener('popstate', () => routeTo(getCurrentUrl()));
109 | }
110 |
111 | class Router extends React.Component {
112 | constructor(props) {
113 | super(props);
114 | if (props.history) {
115 | customHistory = props.history;
116 | }
117 |
118 | this.state = {
119 | url: this.props.url || getCurrentUrl()
120 | };
121 | }
122 |
123 | shouldComponentUpdate(props, state) {
124 | return props.url !== this.props.url ||
125 | props.onChange !== this.props.onChange ||
126 | state.url !== this.state.url;
127 | }
128 |
129 | componentWillMount() {
130 | ROUTERS.push(this);
131 | }
132 |
133 | componentWillUnmount() {
134 | ROUTERS.splice(ROUTERS.indexOf(this), 1);
135 | }
136 |
137 | routeTo(url) {
138 | this._didRoute = false;
139 | this.setState({url});
140 | return this._didRoute;
141 | }
142 |
143 | render() {
144 | const {children, onChange} = this.props;
145 | const {url} = this.state;
146 | let routeElement = null;
147 |
148 | const childrenElements = React.Children
149 | .toArray(children)
150 | .sort(pathRankSort);
151 |
152 | for (let i = 0; i < childrenElements.length; i++) {
153 | let child = childrenElements[i],
154 | props = child.props,
155 | path = props.path,
156 | matches = exec(url, path, props);
157 | if (matches) {
158 | routeElement = React.cloneElement(
159 | child,
160 | {...child.props, url, matches}
161 | );
162 |
163 | this._didRoute = true;
164 | break;
165 | }
166 | }
167 |
168 |
169 | let previous = this.previousUrl;
170 | if (url !== previous) {
171 | this.previousUrl = url;
172 | if (typeof onChange === 'function') {
173 | onChange({ url, previous, router: this });
174 | }
175 | }
176 | return routeElement;
177 | }
178 | }
179 |
180 | const Link = ({
181 | children,
182 | activeClassName,
183 | delay,
184 | ...props
185 | }) => (
186 | {children}
193 | );
194 |
195 | Router.listenBefore = listenBefore;
196 | Router.route = route;
197 | Router.Router = Router;
198 | Router.Route = Route;
199 | Router.Link = Link;
200 |
201 | export {route, Router, Route, Link, listenBefore};
202 | export default Router;
203 |
--------------------------------------------------------------------------------
/dist/react-rainie-router.min.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["../src/util.js","../src/Route.jsx","../src/index.js"],"names":["exec","url","route","opts","arguments","length","undefined","EMPTY","reg","c","match","matches","ret","p","split","i","r","decodeURIComponent","slice","join","segmentize","replace","max","Math","charAt","param","flags","plus","indexOf","star","val","map","default","pathRankSort","a","b","aAttr","props","bAttr","diff","rank","path","strip","isPromise","obj","then","setUrl","type","customHistory","history","getCurrentUrl","getCurrentLocation","location","pathname","search","listenBefore","func","push","Promise","all","LISTENRS","routeTo","catch","didRoute","forEach","router","routeFromLink","node","getAttribute","href","target","handleLinkClick","delay","e","_handleLinkClick","currentTarget","this","button","persist","prevent","stopImmediatePropagation","stopPropagation","preventDefault","Route","state","componentWillMount","component","_props2","getComponent","promise","SplitComponent","setState","render","RoutedComponent","_props3","_props","objectWithoutProperties","React","createElement","Component","PropTypes","ROUTERS","addEventListener","Router","_React$Component","call","_this","shouldComponentUpdate","onChange","componentWillUnmount","splice","_didRoute","children","routeElement","childrenElements","Children","toArray","sort","child","cloneElement","_extends","previous","previousUrl","Link","_ref","activeClassName","to","className"],"mappings":"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","file":"react-rainie-router.min.js","sourcesContent":["const EMPTY = {};\n\nexport function exec(url, route, opts = EMPTY) {\n let reg = /(?:\\?([^#]*))?(#.*)?$/,\n c = url.match(reg),\n matches = {},\n ret;\n if (c && c[1]) {\n let p = c[1].split('&');\n for (let i = 0; i < p.length; i++) {\n let r = p[i].split('=');\n matches[decodeURIComponent(r[0])] = decodeURIComponent(r.slice(1).join('='));\n }\n }\n url = segmentize(url.replace(reg, ''));\n route = segmentize(route || '');\n let max = Math.max(url.length, route.length);\n for (let i = 0; i < max; i++) {\n if (route[i] && route[i].charAt(0) === ':') {\n let param = route[i].replace(/(^\\:|[+*?]+$)/g, ''),\n flags = (route[i].match(/[+*?]+$/) || EMPTY)[0] || '',\n plus = ~flags.indexOf('+'),\n star = ~flags.indexOf('*'),\n val = url[i] || '';\n if (!val && !star && (flags.indexOf('?') < 0 || plus)) {\n ret = false;\n break;\n }\n matches[param] = decodeURIComponent(val);\n if (plus || star) {\n matches[param] = url.slice(i).map(decodeURIComponent).join('/');\n break;\n }\n } else if (route[i] !== url[i]) {\n ret = false;\n break;\n }\n }\n if (opts.default !== true && ret === false) return false;\n return matches;\n}\n\n\nexport function pathRankSort(a, b) {\n let aAttr = a.props || EMPTY,\n bAttr = b.props || EMPTY;\n if (aAttr.default) return 1;\n if (bAttr.default) return -1;\n let diff = rank(aAttr.path) - rank(bAttr.path);\n return diff || (aAttr.path.length - bAttr.path.length);\n}\n\nexport function segmentize(url) {\n return strip(url).split('/');\n}\n\nexport function rank(url) {\n return (strip(url).match(/\\/+/g) || '').length;\n}\n\nexport function strip(url) {\n return url.replace(/(^\\/+|\\/+$)/g, '');\n}\n","import React from 'react';\n\nfunction isPromise(obj) {\n return !!obj &&\n (typeof obj === 'object' || typeof obj === 'function') &&\n typeof obj.then === 'function';\n}\n\n\nclass Route extends React.Component {\n static PropTypes = {\n component: React.PropTypes.node,\n getComponent: React.PropTypes.func,\n }\n\n state = {\n SplitComponent: undefined,\n }\n\n componentWillMount() {\n const { component, getComponent} = this.props;\n if (!component && getComponent) {\n const promise = getComponent();\n if (isPromise(promise)) {\n promise.then(SplitComponent => {\n this.setState({ SplitComponent });\n });\n }\n }\n }\n\n render () {\n const {\n component: RoutedComponent,\n getComponent,\n ..._props,\n } = this.props;\n\n const { SplitComponent } = this.state;\n\n\n if (RoutedComponent)\n return ;\n\n return SplitComponent ?\n : null;\n }\n}\n\nexport default Route;\n","import React from 'react';\nimport {exec, pathRankSort} from './util';\nimport Route from './Route.jsx';\n\nlet customHistory = null;\n\nconst ROUTERS = [];\nconst EMPTY = {};\nconst LISTENRS = [];\n\nfunction setUrl(url, type = 'push') {\n if (customHistory && customHistory[type]) {\n customHistory[type](url);\n } else if (typeof history !== 'undefined' && history[type + 'State']) {\n history[type + 'State'](null, null, url);\n }\n}\n\nfunction getCurrentUrl() {\n let url;\n if (customHistory && customHistory.getCurrentLocation) {\n url = customHistory.getCurrentLocation();\n } else {\n url = typeof location !== 'undefined'\n ? location\n : EMPTY;\n }\n return `${url.pathname || ''}${url.search || ''}`;\n}\n\nfunction listenBefore(func) {\n LISTENRS.push(func);\n}\n\n\nfunction route(url, replace = false) {\n return Promise.all(LISTENRS.map(func => func(url)))\n .then(() => {\n if (typeof url !== 'string' && url.url) {\n replace = url.replace;\n url = url.url;\n }\n setUrl(url, replace\n ? 'replace'\n : 'push');\n return routeTo(url);\n }).catch(() => {});\n}\n\nfunction routeTo(url) {\n let didRoute = false;\n ROUTERS.forEach(router => {\n if (router.routeTo(url) === true) {\n didRoute = true;\n }\n });\n return didRoute;\n}\n\nfunction routeFromLink(node) {\n // only valid elements\n if (!node || !node.getAttribute)\n return;\n\n let href = node.getAttribute('href') || node.getAttribute('to'),\n target = node.getAttribute('target');\n\n // ignore links with targets and non-path URLs\n if (!href || !href.match(/^\\//g) || (target && !target.match(/^_?self$/i)))\n return;\n\n // attempt to route, if no match simply cede control to browser\n return route(href);\n}\n\nfunction handleLinkClick(delay) {\n return e => _handleLinkClick(e, delay);\n}\n\nfunction _handleLinkClick(e, delay) {\n const target = e.currentTarget || e.target || this;\n if (e.button !== 0)\n return;\n\n if (typeof delay === 'number') {\n // access the event properties in an asynchronous way\n e.persist();\n setTimeout(() => routeFromLink(target), delay);\n return prevent(e);\n }\n\n routeFromLink(target);\n return prevent(e);\n}\n\nfunction prevent(e) {\n if (e) {\n if (e.stopImmediatePropagation)\n e.stopImmediatePropagation();\n if (e.stopPropagation)\n e.stopPropagation();\n e.preventDefault();\n }\n return false;\n}\n\nif (typeof addEventListener === 'function') {\n addEventListener('popstate', () => routeTo(getCurrentUrl()));\n}\n\nclass Router extends React.Component {\n constructor(props) {\n super(props);\n if (props.history) {\n customHistory = props.history;\n }\n\n this.state = {\n url: this.props.url || getCurrentUrl()\n };\n }\n\n shouldComponentUpdate(props, state) {\n return props.url !== this.props.url ||\n props.onChange !== this.props.onChange ||\n state.url !== this.state.url;\n }\n\n componentWillMount() {\n ROUTERS.push(this);\n }\n\n componentWillUnmount() {\n ROUTERS.splice(ROUTERS.indexOf(this), 1);\n }\n\n routeTo(url) {\n this._didRoute = false;\n this.setState({url});\n return this._didRoute;\n }\n\n render() {\n const {children, onChange} = this.props;\n const {url} = this.state;\n let routeElement = null;\n\n const childrenElements = React.Children\n .toArray(children)\n .sort(pathRankSort);\n\n for (let i = 0; i < childrenElements.length; i++) {\n let child = childrenElements[i],\n props = child.props,\n path = props.path,\n matches = exec(url, path, props);\n if (matches) {\n routeElement = React.cloneElement(\n child,\n {...child.props, url, matches}\n );\n\n this._didRoute = true;\n break;\n }\n }\n\n\n let previous = this.previousUrl;\n if (url !== previous) {\n this.previousUrl = url;\n if (typeof onChange === 'function') {\n onChange({ url, previous, router: this });\n }\n }\n return routeElement;\n }\n}\n\nconst Link = ({\n children,\n activeClassName,\n delay,\n ...props\n}) => (\n {children} \n);\n\nRouter.listenBefore = listenBefore;\nRouter.route = route;\nRouter.Router = Router;\nRouter.Route = Route;\nRouter.Link = Link;\n\nexport {route, Router, Route, Link, listenBefore};\nexport default Router;\n"]}
--------------------------------------------------------------------------------
/dist/react-rainie-router.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('react')) :
3 | typeof define === 'function' && define.amd ? define(['react'], factory) :
4 | (global.reactRainieRouter = factory(global.React));
5 | }(this, (function (React) { 'use strict';
6 |
7 | React = 'default' in React ? React['default'] : React;
8 |
9 | var EMPTY$1 = {};
10 |
11 | function exec(url, route) {
12 | var opts = arguments.length <= 2 || arguments[2] === undefined ? EMPTY$1 : arguments[2];
13 |
14 | var reg = /(?:\?([^#]*))?(#.*)?$/,
15 | c = url.match(reg),
16 | matches = {},
17 | ret = void 0;
18 | if (c && c[1]) {
19 | var p = c[1].split('&');
20 | for (var i = 0; i < p.length; i++) {
21 | var r = p[i].split('=');
22 | matches[decodeURIComponent(r[0])] = decodeURIComponent(r.slice(1).join('='));
23 | }
24 | }
25 | url = segmentize(url.replace(reg, ''));
26 | route = segmentize(route || '');
27 | var max = Math.max(url.length, route.length);
28 | for (var _i = 0; _i < max; _i++) {
29 | if (route[_i] && route[_i].charAt(0) === ':') {
30 | var param = route[_i].replace(/(^\:|[+*?]+$)/g, ''),
31 | flags = (route[_i].match(/[+*?]+$/) || EMPTY$1)[0] || '',
32 | plus = ~flags.indexOf('+'),
33 | star = ~flags.indexOf('*'),
34 | val = url[_i] || '';
35 | if (!val && !star && (flags.indexOf('?') < 0 || plus)) {
36 | ret = false;
37 | break;
38 | }
39 | matches[param] = decodeURIComponent(val);
40 | if (plus || star) {
41 | matches[param] = url.slice(_i).map(decodeURIComponent).join('/');
42 | break;
43 | }
44 | } else if (route[_i] !== url[_i]) {
45 | ret = false;
46 | break;
47 | }
48 | }
49 | if (opts.default !== true && ret === false) return false;
50 | return matches;
51 | }
52 |
53 | function pathRankSort(a, b) {
54 | var aAttr = a.props || EMPTY$1,
55 | bAttr = b.props || EMPTY$1;
56 | if (aAttr.default) return 1;
57 | if (bAttr.default) return -1;
58 | var diff = rank(aAttr.path) - rank(bAttr.path);
59 | return diff || aAttr.path.length - bAttr.path.length;
60 | }
61 |
62 | function segmentize(url) {
63 | return strip(url).split('/');
64 | }
65 |
66 | function rank(url) {
67 | return (strip(url).match(/\/+/g) || '').length;
68 | }
69 |
70 | function strip(url) {
71 | return url.replace(/(^\/+|\/+$)/g, '');
72 | }
73 |
74 | var classCallCheck = function (instance, Constructor) {
75 | if (!(instance instanceof Constructor)) {
76 | throw new TypeError("Cannot call a class as a function");
77 | }
78 | };
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | var _extends = Object.assign || function (target) {
89 | for (var i = 1; i < arguments.length; i++) {
90 | var source = arguments[i];
91 |
92 | for (var key in source) {
93 | if (Object.prototype.hasOwnProperty.call(source, key)) {
94 | target[key] = source[key];
95 | }
96 | }
97 | }
98 |
99 | return target;
100 | };
101 |
102 | var get = function get(object, property, receiver) {
103 | if (object === null) object = Function.prototype;
104 | var desc = Object.getOwnPropertyDescriptor(object, property);
105 |
106 | if (desc === undefined) {
107 | var parent = Object.getPrototypeOf(object);
108 |
109 | if (parent === null) {
110 | return undefined;
111 | } else {
112 | return get(parent, property, receiver);
113 | }
114 | } else if ("value" in desc) {
115 | return desc.value;
116 | } else {
117 | var getter = desc.get;
118 |
119 | if (getter === undefined) {
120 | return undefined;
121 | }
122 |
123 | return getter.call(receiver);
124 | }
125 | };
126 |
127 | var inherits = function (subClass, superClass) {
128 | if (typeof superClass !== "function" && superClass !== null) {
129 | throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
130 | }
131 |
132 | subClass.prototype = Object.create(superClass && superClass.prototype, {
133 | constructor: {
134 | value: subClass,
135 | enumerable: false,
136 | writable: true,
137 | configurable: true
138 | }
139 | });
140 | if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
141 | };
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 | var objectWithoutProperties = function (obj, keys) {
152 | var target = {};
153 |
154 | for (var i in obj) {
155 | if (keys.indexOf(i) >= 0) continue;
156 | if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
157 | target[i] = obj[i];
158 | }
159 |
160 | return target;
161 | };
162 |
163 | var possibleConstructorReturn = function (self, call) {
164 | if (!self) {
165 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
166 | }
167 |
168 | return call && (typeof call === "object" || typeof call === "function") ? call : self;
169 | };
170 |
171 |
172 |
173 | var set = function set(object, property, value, receiver) {
174 | var desc = Object.getOwnPropertyDescriptor(object, property);
175 |
176 | if (desc === undefined) {
177 | var parent = Object.getPrototypeOf(object);
178 |
179 | if (parent !== null) {
180 | set(parent, property, value, receiver);
181 | }
182 | } else if ("value" in desc && desc.writable) {
183 | desc.value = value;
184 | } else {
185 | var setter = desc.set;
186 |
187 | if (setter !== undefined) {
188 | setter.call(receiver, value);
189 | }
190 | }
191 |
192 | return value;
193 | };
194 |
195 | function isPromise(obj) {
196 | return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
197 | }
198 |
199 | var Route = function (_React$Component) {
200 | inherits(Route, _React$Component);
201 |
202 | function Route() {
203 | var _temp, _this, _ret;
204 |
205 | classCallCheck(this, Route);
206 |
207 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
208 | args[_key] = arguments[_key];
209 | }
210 |
211 | return _ret = (_temp = (_this = possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.state = {
212 | SplitComponent: undefined
213 | }, _temp), possibleConstructorReturn(_this, _ret);
214 | }
215 |
216 | Route.prototype.componentWillMount = function componentWillMount() {
217 | var _this2 = this;
218 |
219 | var _props2 = this.props;
220 | var component = _props2.component;
221 | var getComponent = _props2.getComponent;
222 |
223 | if (!component && getComponent) {
224 | var promise = getComponent();
225 | if (isPromise(promise)) {
226 | promise.then(function (SplitComponent) {
227 | _this2.setState({ SplitComponent: SplitComponent });
228 | });
229 | }
230 | }
231 | };
232 |
233 | Route.prototype.render = function render() {
234 | var _props3 = this.props;
235 | var RoutedComponent = _props3.component;
236 | var getComponent = _props3.getComponent;
237 |
238 | var _props = objectWithoutProperties(_props3, ['component', 'getComponent']);
239 |
240 | var SplitComponent = this.state.SplitComponent;
241 |
242 |
243 | if (RoutedComponent) return React.createElement(RoutedComponent, _props);
244 |
245 | return SplitComponent ? React.createElement(SplitComponent, _props) : null;
246 | };
247 |
248 | return Route;
249 | }(React.Component);
250 |
251 | Route.PropTypes = {
252 | component: React.PropTypes.node,
253 | getComponent: React.PropTypes.func
254 | };
255 |
256 | var customHistory = null;
257 |
258 | var ROUTERS = [];
259 | var EMPTY = {};
260 | var LISTENRS = [];
261 |
262 | function setUrl(url) {
263 | var type = arguments.length <= 1 || arguments[1] === undefined ? 'push' : arguments[1];
264 |
265 | if (customHistory && customHistory[type]) {
266 | customHistory[type](url);
267 | } else if (typeof history !== 'undefined' && history[type + 'State']) {
268 | history[type + 'State'](null, null, url);
269 | }
270 | }
271 |
272 | function getCurrentUrl() {
273 | var url = void 0;
274 | if (customHistory && customHistory.getCurrentLocation) {
275 | url = customHistory.getCurrentLocation();
276 | } else {
277 | url = typeof location !== 'undefined' ? location : EMPTY;
278 | }
279 | return '' + (url.pathname || '') + (url.search || '');
280 | }
281 |
282 | function listenBefore(func) {
283 | LISTENRS.push(func);
284 | }
285 |
286 | function route(url) {
287 | var replace = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
288 |
289 | return Promise.all(LISTENRS.map(function (func) {
290 | return func(url);
291 | })).then(function () {
292 | if (typeof url !== 'string' && url.url) {
293 | replace = url.replace;
294 | url = url.url;
295 | }
296 | setUrl(url, replace ? 'replace' : 'push');
297 | return routeTo(url);
298 | }).catch(function () {});
299 | }
300 |
301 | function routeTo(url) {
302 | var didRoute = false;
303 | ROUTERS.forEach(function (router) {
304 | if (router.routeTo(url) === true) {
305 | didRoute = true;
306 | }
307 | });
308 | return didRoute;
309 | }
310 |
311 | function routeFromLink(node) {
312 | // only valid elements
313 | if (!node || !node.getAttribute) return;
314 |
315 | var href = node.getAttribute('href') || node.getAttribute('to'),
316 | target = node.getAttribute('target');
317 |
318 | // ignore links with targets and non-path URLs
319 | if (!href || !href.match(/^\//g) || target && !target.match(/^_?self$/i)) return;
320 |
321 | // attempt to route, if no match simply cede control to browser
322 | return route(href);
323 | }
324 |
325 | function handleLinkClick(delay) {
326 | return function (e) {
327 | return _handleLinkClick(e, delay);
328 | };
329 | }
330 |
331 | function _handleLinkClick(e, delay) {
332 | var target = e.currentTarget || e.target || this;
333 | if (e.button !== 0) return;
334 |
335 | if (typeof delay === 'number') {
336 | // access the event properties in an asynchronous way
337 | e.persist();
338 | setTimeout(function () {
339 | return routeFromLink(target);
340 | }, delay);
341 | return prevent(e);
342 | }
343 |
344 | routeFromLink(target);
345 | return prevent(e);
346 | }
347 |
348 | function prevent(e) {
349 | if (e) {
350 | if (e.stopImmediatePropagation) e.stopImmediatePropagation();
351 | if (e.stopPropagation) e.stopPropagation();
352 | e.preventDefault();
353 | }
354 | return false;
355 | }
356 |
357 | if (typeof addEventListener === 'function') {
358 | addEventListener('popstate', function () {
359 | return routeTo(getCurrentUrl());
360 | });
361 | }
362 |
363 | var Router = function (_React$Component) {
364 | inherits(Router, _React$Component);
365 |
366 | function Router(props) {
367 | classCallCheck(this, Router);
368 |
369 | var _this = possibleConstructorReturn(this, _React$Component.call(this, props));
370 |
371 | if (props.history) {
372 | customHistory = props.history;
373 | }
374 |
375 | _this.state = {
376 | url: _this.props.url || getCurrentUrl()
377 | };
378 | return _this;
379 | }
380 |
381 | Router.prototype.shouldComponentUpdate = function shouldComponentUpdate(props, state) {
382 | return props.url !== this.props.url || props.onChange !== this.props.onChange || state.url !== this.state.url;
383 | };
384 |
385 | Router.prototype.componentWillMount = function componentWillMount() {
386 | ROUTERS.push(this);
387 | };
388 |
389 | Router.prototype.componentWillUnmount = function componentWillUnmount() {
390 | ROUTERS.splice(ROUTERS.indexOf(this), 1);
391 | };
392 |
393 | Router.prototype.routeTo = function routeTo(url) {
394 | this._didRoute = false;
395 | this.setState({ url: url });
396 | return this._didRoute;
397 | };
398 |
399 | Router.prototype.render = function render() {
400 | var _props = this.props;
401 | var children = _props.children;
402 | var onChange = _props.onChange;
403 | var url = this.state.url;
404 |
405 | var routeElement = null;
406 |
407 | var childrenElements = React.Children.toArray(children).sort(pathRankSort);
408 |
409 | for (var i = 0; i < childrenElements.length; i++) {
410 | var child = childrenElements[i],
411 | props = child.props,
412 | path = props.path,
413 | matches = exec(url, path, props);
414 | if (matches) {
415 | routeElement = React.cloneElement(child, _extends({}, child.props, { url: url, matches: matches }));
416 |
417 | this._didRoute = true;
418 | break;
419 | }
420 | }
421 |
422 | var previous = this.previousUrl;
423 | if (url !== previous) {
424 | this.previousUrl = url;
425 | if (typeof onChange === 'function') {
426 | onChange({ url: url, previous: previous, router: this });
427 | }
428 | }
429 | return routeElement;
430 | };
431 |
432 | return Router;
433 | }(React.Component);
434 |
435 | var Link = function (_ref) {
436 | var children = _ref.children;
437 | var activeClassName = _ref.activeClassName;
438 | var delay = _ref.delay;
439 | var props = objectWithoutProperties(_ref, ['children', 'activeClassName', 'delay']);
440 | return React.createElement(
441 | 'a',
442 | _extends({}, props, {
443 | className: exec(props.href || props.to, getCurrentUrl()) ? activeClassName + ' ' + props.className : props.className,
444 | onClick: handleLinkClick(delay) }),
445 | children
446 | );
447 | };
448 |
449 | Router.listenBefore = listenBefore;
450 | Router.route = route;
451 | Router.Router = Router;
452 | Router.Route = Route;
453 | Router.Link = Link;
454 |
455 | return Router;
456 |
457 | })));
458 | //# sourceMappingURL=react-rainie-router.js.map
459 |
--------------------------------------------------------------------------------
/dist/react-rainie-router.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":null,"sources":["../src/util.js","../src/Route.jsx","../src/index.js"],"sourcesContent":["const EMPTY = {};\n\nexport function exec(url, route, opts = EMPTY) {\n let reg = /(?:\\?([^#]*))?(#.*)?$/,\n c = url.match(reg),\n matches = {},\n ret;\n if (c && c[1]) {\n let p = c[1].split('&');\n for (let i = 0; i < p.length; i++) {\n let r = p[i].split('=');\n matches[decodeURIComponent(r[0])] = decodeURIComponent(r.slice(1).join('='));\n }\n }\n url = segmentize(url.replace(reg, ''));\n route = segmentize(route || '');\n let max = Math.max(url.length, route.length);\n for (let i = 0; i < max; i++) {\n if (route[i] && route[i].charAt(0) === ':') {\n let param = route[i].replace(/(^\\:|[+*?]+$)/g, ''),\n flags = (route[i].match(/[+*?]+$/) || EMPTY)[0] || '',\n plus = ~flags.indexOf('+'),\n star = ~flags.indexOf('*'),\n val = url[i] || '';\n if (!val && !star && (flags.indexOf('?') < 0 || plus)) {\n ret = false;\n break;\n }\n matches[param] = decodeURIComponent(val);\n if (plus || star) {\n matches[param] = url.slice(i).map(decodeURIComponent).join('/');\n break;\n }\n } else if (route[i] !== url[i]) {\n ret = false;\n break;\n }\n }\n if (opts.default !== true && ret === false) return false;\n return matches;\n}\n\n\nexport function pathRankSort(a, b) {\n let aAttr = a.props || EMPTY,\n bAttr = b.props || EMPTY;\n if (aAttr.default) return 1;\n if (bAttr.default) return -1;\n let diff = rank(aAttr.path) - rank(bAttr.path);\n return diff || (aAttr.path.length - bAttr.path.length);\n}\n\nexport function segmentize(url) {\n return strip(url).split('/');\n}\n\nexport function rank(url) {\n return (strip(url).match(/\\/+/g) || '').length;\n}\n\nexport function strip(url) {\n return url.replace(/(^\\/+|\\/+$)/g, '');\n}\n","import React from 'react';\n\nfunction isPromise(obj) {\n return !!obj &&\n (typeof obj === 'object' || typeof obj === 'function') &&\n typeof obj.then === 'function';\n}\n\n\nclass Route extends React.Component {\n static PropTypes = {\n component: React.PropTypes.node,\n getComponent: React.PropTypes.func,\n }\n\n state = {\n SplitComponent: undefined,\n }\n\n componentWillMount() {\n const { component, getComponent} = this.props;\n if (!component && getComponent) {\n const promise = getComponent();\n if (isPromise(promise)) {\n promise.then(SplitComponent => {\n this.setState({ SplitComponent });\n });\n }\n }\n }\n\n render () {\n const {\n component: RoutedComponent,\n getComponent,\n ..._props,\n } = this.props;\n\n const { SplitComponent } = this.state;\n\n\n if (RoutedComponent)\n return ;\n\n return SplitComponent ?\n : null;\n }\n}\n\nexport default Route;\n","import React from 'react';\nimport {exec, pathRankSort} from './util';\nimport Route from './Route.jsx';\n\nlet customHistory = null;\n\nconst ROUTERS = [];\nconst EMPTY = {};\nconst LISTENRS = [];\n\nfunction setUrl(url, type = 'push') {\n if (customHistory && customHistory[type]) {\n customHistory[type](url);\n } else if (typeof history !== 'undefined' && history[type + 'State']) {\n history[type + 'State'](null, null, url);\n }\n}\n\nfunction getCurrentUrl() {\n let url;\n if (customHistory && customHistory.getCurrentLocation) {\n url = customHistory.getCurrentLocation();\n } else {\n url = typeof location !== 'undefined'\n ? location\n : EMPTY;\n }\n return `${url.pathname || ''}${url.search || ''}`;\n}\n\nfunction listenBefore(func) {\n LISTENRS.push(func);\n}\n\n\nfunction route(url, replace = false) {\n return Promise.all(LISTENRS.map(func => func(url)))\n .then(() => {\n if (typeof url !== 'string' && url.url) {\n replace = url.replace;\n url = url.url;\n }\n setUrl(url, replace\n ? 'replace'\n : 'push');\n return routeTo(url);\n }).catch(() => {});\n}\n\nfunction routeTo(url) {\n let didRoute = false;\n ROUTERS.forEach(router => {\n if (router.routeTo(url) === true) {\n didRoute = true;\n }\n });\n return didRoute;\n}\n\nfunction routeFromLink(node) {\n // only valid elements\n if (!node || !node.getAttribute)\n return;\n\n let href = node.getAttribute('href') || node.getAttribute('to'),\n target = node.getAttribute('target');\n\n // ignore links with targets and non-path URLs\n if (!href || !href.match(/^\\//g) || (target && !target.match(/^_?self$/i)))\n return;\n\n // attempt to route, if no match simply cede control to browser\n return route(href);\n}\n\nfunction handleLinkClick(delay) {\n return e => _handleLinkClick(e, delay);\n}\n\nfunction _handleLinkClick(e, delay) {\n const target = e.currentTarget || e.target || this;\n if (e.button !== 0)\n return;\n\n if (typeof delay === 'number') {\n // access the event properties in an asynchronous way\n e.persist();\n setTimeout(() => routeFromLink(target), delay);\n return prevent(e);\n }\n\n routeFromLink(target);\n return prevent(e);\n}\n\nfunction prevent(e) {\n if (e) {\n if (e.stopImmediatePropagation)\n e.stopImmediatePropagation();\n if (e.stopPropagation)\n e.stopPropagation();\n e.preventDefault();\n }\n return false;\n}\n\nif (typeof addEventListener === 'function') {\n addEventListener('popstate', () => routeTo(getCurrentUrl()));\n}\n\nclass Router extends React.Component {\n constructor(props) {\n super(props);\n if (props.history) {\n customHistory = props.history;\n }\n\n this.state = {\n url: this.props.url || getCurrentUrl()\n };\n }\n\n shouldComponentUpdate(props, state) {\n return props.url !== this.props.url ||\n props.onChange !== this.props.onChange ||\n state.url !== this.state.url;\n }\n\n componentWillMount() {\n ROUTERS.push(this);\n }\n\n componentWillUnmount() {\n ROUTERS.splice(ROUTERS.indexOf(this), 1);\n }\n\n routeTo(url) {\n this._didRoute = false;\n this.setState({url});\n return this._didRoute;\n }\n\n render() {\n const {children, onChange} = this.props;\n const {url} = this.state;\n let routeElement = null;\n\n const childrenElements = React.Children\n .toArray(children)\n .sort(pathRankSort);\n\n for (let i = 0; i < childrenElements.length; i++) {\n let child = childrenElements[i],\n props = child.props,\n path = props.path,\n matches = exec(url, path, props);\n if (matches) {\n routeElement = React.cloneElement(\n child,\n {...child.props, url, matches}\n );\n\n this._didRoute = true;\n break;\n }\n }\n\n\n let previous = this.previousUrl;\n if (url !== previous) {\n this.previousUrl = url;\n if (typeof onChange === 'function') {\n onChange({ url, previous, router: this });\n }\n }\n return routeElement;\n }\n}\n\nconst Link = ({\n children,\n activeClassName,\n delay,\n ...props\n}) => (\n {children} \n);\n\nRouter.listenBefore = listenBefore;\nRouter.route = route;\nRouter.Router = Router;\nRouter.Route = Route;\nRouter.Link = Link;\n\nexport {route, Router, Route, Link, listenBefore};\nexport default Router;\n"],"names":["EMPTY","exec","url","route","opts","reg","c","match","matches","ret","p","split","i","length","r","decodeURIComponent","slice","join","segmentize","replace","max","Math","charAt","param","flags","plus","indexOf","star","val","map","default","pathRankSort","a","b","aAttr","props","bAttr","diff","rank","path","strip","isPromise","obj","then","Route","state","undefined","componentWillMount","component","getComponent","promise","setState","SplitComponent","render","RoutedComponent","_props","React","Component","PropTypes","node","func","customHistory","ROUTERS","LISTENRS","setUrl","type","history","getCurrentUrl","getCurrentLocation","location","pathname","search","listenBefore","push","Promise","all","routeTo","catch","didRoute","forEach","router","routeFromLink","getAttribute","href","target","handleLinkClick","delay","_handleLinkClick","e","currentTarget","button","persist","prevent","stopImmediatePropagation","stopPropagation","preventDefault","addEventListener","Router","shouldComponentUpdate","onChange","componentWillUnmount","splice","_didRoute","children","routeElement","childrenElements","Children","toArray","sort","child","cloneElement","previous","previousUrl","Link","activeClassName","to","className"],"mappings":"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"}
--------------------------------------------------------------------------------