├── .gitignore ├── assets └── apps │ ├── achive │ └── message.js │ ├── inbox │ ├── message.js │ └── index.js │ ├── app.js │ ├── about │ └── index.js │ ├── home │ └── index.js │ ├── async-loader.js │ └── index.js ├── webpack.config.js ├── package.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /assets/apps/achive/message.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | export default class Message extends Component { 4 | render() { 5 | return ( 6 |
7 |

achive message

8 |

{this.props.params.id}

9 |
10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: './assets/apps/index.js', 3 | output: { 4 | path: './build', 5 | filename: 'bundle.js' 6 | }, 7 | module: { 8 | loaders: [ 9 | {test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader?stage=0'} 10 | ] 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /assets/apps/inbox/message.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | 4 | export default class Message extends Component { 5 | render() { 6 | return ( 7 |
8 |

message

9 |

10 | {this.props.params.id} 11 |

12 |
13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /assets/apps/app.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {RouteHandler} from 'react-router'; 3 | 4 | 5 | export default class App extends Component{ 6 | render() { 7 | return ( 8 |
9 |

React-router is awesome!

10 | 11 |
12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /assets/apps/about/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Link} from 'react-router'; 3 | 4 | 5 | /** 6 | * About App 7 | */ 8 | export default class About extends Component { 9 | render() { 10 | return ( 11 |
12 |

react-async-router demo

13 |

14 | webpack + bundle-loader + react-router 15 |

16 | back to home 17 |
18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /assets/apps/home/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Link} from 'react-router'; 3 | 4 | 5 | /** 6 | * home 7 | */ 8 | export default class Home extends Component { 9 | render() { 10 | return ( 11 |
12 |

Home

13 | 18 |
19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /assets/apps/inbox/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Link, RouteHandler} from 'react-router'; 3 | 4 | 5 | /** 6 | * inbox app 7 | */ 8 | export default class Inbox extends Component { 9 | render() { 10 | return ( 11 |
12 |

Inbox

13 | 18 | 19 | back to home 20 |
21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-async-router", 3 | "version": "1.0.0", 4 | "description": "react-router async loading handler demo", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "react-router", 11 | "async" 12 | ], 13 | "author": "hufeng", 14 | "license": "ISC", 15 | "dependencies": { 16 | "bundle-loader": "^0.5.4", 17 | "react": "^0.13.3", 18 | "react-router": "^0.13.3" 19 | }, 20 | "devDependencies": { 21 | "babel-core": "^5.4.7", 22 | "babel-loader": "^5.1.3", 23 | "node-libs-browser": "^0.5.2", 24 | "webpack": "^1.9.9" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /assets/apps/async-loader.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | 4 | /** 5 | * 异步的组件 6 | */ 7 | module.exports = function asyncLoader(component) { 8 | return React.createClass({ 9 | getInitialState() { 10 | return { 11 | Component: null 12 | } 13 | }, 14 | 15 | 16 | componentDidMount() { 17 | //模拟出loading的效果 18 | // setTimeout(() => { 19 | // component((Component) => { 20 | // this.setState({ 21 | // Component: Component 22 | // }); 23 | // }); 24 | // }, 1000); 25 | component((Component) => { 26 | this.setState({ 27 | Component: Component 28 | }); 29 | }); 30 | }, 31 | 32 | 33 | render() { 34 | var Component = this.state.Component; 35 | 36 | if (Component) { 37 | return 38 | } else { 39 | return
Loading...
; 40 | } 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /assets/apps/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Route, DefaultRoute, HashLocation, run} from 'react-router'; 3 | import asyncLoader from './async-loader'; 4 | import App from './app'; 5 | import Home from './home'; 6 | import About from 'bundle?lazy!./about'; 7 | import Inbox from 'bundle?lazy!./inbox'; 8 | import Message from 'bundle?lazy!./inbox/message'; 9 | import AchiveMessage from 'bundle?lazy!./achive/message'; 10 | 11 | 12 | let routes = ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | 23 | 24 | run(routes, HashLocation, (Root) => { 25 | React.render(, document.body); 26 | }); 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 胡锋 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 all 13 | 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 THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-async-router 2 | react-router async loading component 3 | 4 | react-router为前端的SPA应用提供一种非常优雅的路由方案,但是还么有一个完善的异步方案,虽然已有些眉目[看这里](https://github.com/rackt/react-router/issues/755)。 5 | 6 | 因为router的handler是一个react的组件,如果我们在代码中大量的通过require或者import(es6)的方式, 7 | 这样会带来两个缺点: 8 | 9 | 1. 所有的模块阻塞的方式加载 10 | 2. 所有的模块都会合并在一个chunk中。(:) 是的细心的您已经猜到我们使用的是webpack了)。 11 | 12 | 我们希望我们的应用的chunk(js)的加载是**按需的异步的加载**,那怎么才能愉快的玩耍呢? 13 | 14 | 首先要解决的是handler需要的是一个react组件对象,这里我们会想到react常用的一招portal方案。 15 | 这个办法常用来整合第三方的库如jquery的插件,如果插件会去改变我们的react生成的dom, 16 | 这样会导致react的diff算法出现问题。通常的解决办法就是portal。 17 | 18 | 思路,先用react渲染一个空div,等到jquery的渲染完后,获取节点dom,再次用react重新re-render即可。 19 | 20 | 21 | 好了下一个问题,怎么解决异步按需呢? 22 | webpack为我们提供了两种常用异步的方式: 23 | 24 | 1. AMD style, require([], function() {}) 25 | 2. commonjs style, require.ensure([], function(require) {}) 26 | 27 | 这两种都有一个相同的问题,就是参数不能是表达式,也就是说, 28 | 29 | ```javascript 30 | //yes 31 | require(['./dashboard'], function(dashboard) {}) 32 | 33 | //oops 34 | //1,webpack will give warning info. 35 | //2. webpack会默认的递归(RegExp would be /^\.\/.*\.js$/)的把整个当前目录的文件都会扫描出来放进一个map变量中, 36 | ///***/ function(module, exports, __webpack_require__) { 37 | // 38 | // var map = { 39 | // "./a": 1, 40 | // "./a.js": 1, 41 | // "./b": 2, 42 | // "./b.js": 2, 43 | // "./c": 3, 44 | // "./c.js": 3 45 | // }; 46 | //然后把所有这些文件放进一个chunk中,会导致这个chunk文件巨大。 47 | var module = './dashboard'; 48 | require([module], function(dashboard) {}); 49 | 50 | //require.ensure同样的问题 51 | ``` 52 | 完整的解释[猛戳这里](https://github.com/webpack/webpack/issues/118) 53 | 54 | 当然上面的问题可以通过改变webpack的上下文的默认的正则的规则来解决,但是还是不直观而且更加的复杂。因为依赖的模块可能分散在多个目录中比如node_modules, web_modules. 55 | 56 | 我们希望做到简单一点就是一个页面模块就是一个chunk,然后按需加载就可以了。 57 | 通过不断看webpack文档,发现另外一个神奇loader,[bundler—loader](https://github.com/webpack/bundle-loader)可以实现我们想要的功能。 58 | 59 | bundle-loader 对异步的支持, 60 | ```javascript 61 | // The chunk is requested, when you require the bundle 62 | var waitForChunk = require("bundle!./file.js"); 63 | 64 | // To wait until the chunk is available (and get the exports) 65 | // you need to async wait for it. 66 | waitForChunk(function(file) { 67 | // use file like is was required with 68 | // var file = require("./file.js"); 69 | }); 70 | // wraps the require in a require.ensure block 71 | ``` 72 | The file is requested when you require the bundle loader. If you want it to request it lazy, use: 73 | ```javascript 74 | var load = require("bundle?lazy!./file.js"); 75 | 76 | // The chunk is not requested until you call the load function 77 | load(function(file) { 78 | 79 | }); 80 | ``` 81 | You may set name for bundle (name query parameter). See documentation. 82 | ```javascript 83 | require("bundle?lazy&name=my-chunk!./file.js"); 84 | ``` 85 | 86 | bundle-loader可以实现我们对于异步的渴求,又可以使webpack在打包的时候把模块单独放进一个chunk。 87 | 88 | 89 | webpack核心的思想是bundler,打包一切可以打包的东西。在编译期确定所有的依赖等等。但是异步带来了很多的不确定性,这个让webpack稍微有点为难,然后就默认的去做一些递归的扫描。 90 | 91 | 92 | demo: 93 | ```javascript 94 | import React from 'react'; 95 | import {Route, DefaultRoute, HashLocation, run} from 'react-router'; 96 | import asyncLoader from './async-loader'; 97 | import App from './app'; 98 | import Home from './home'; 99 | import About from 'bundle?lazy!./about'; 100 | import Inbox from 'bundle?lazy!./inbox'; 101 | 102 | 103 | let routes = ( 104 | 105 | 106 | 107 | 108 | 109 | ); 110 | 111 | 112 | run(routes, HashLocation, (Root) => { 113 | React.render(, document.body); 114 | }); 115 | 116 | ``` 117 | 118 | 体验下这个demo: 119 | npm install 120 | 121 | webpack-dev-server --port 3000 -w 122 | 123 | http://localhost:3000/bundle 124 | 125 | 主要看浏览器中脚本的加载顺序以及名字,有惊喜。 126 | 127 | 128 | 129 | Oh, Sorry,还忘记了一点,React-Router的TestLocation不兼容IE8,但是你可以这样。 130 | https://github.com/rackt/react-router/issues/1475 131 | --------------------------------------------------------------------------------