├── .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 |
14 | - #about
15 | - #inbox
16 | - #achive message
17 |
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 |
14 | - #1
15 | - #2
16 | - #3
17 |
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 |
--------------------------------------------------------------------------------