react-router动态路由与webpack分片thunks
对于大型应用来说,一个首当其冲的问题就是所需加载的 JavaScript 的大小。程序应当只加载当前渲染页所需的 JavaScript。有些开发者将这种方式称之为“代码分拆” — 将所有的代码分拆成多个小包,在用户浏览过程中按需加载。
对于底层细节的修改不应该需要它上面每一层级都进行修改。举个例子,为一个照片浏览页添加一个路径不应该影响到首页加载的 JavaScript 的大小。也不能因为多个团队共用一个大型的路由配置文件而造成合并时的冲突。
路由是个非常适于做代码分拆的地方:它的责任就是配置好每个 view。
如果你使用过单页应用的框架,那你可能应用过路由。路由可以让你的应用看起来好像有很多“页面”。用户可以通过 youdomain.com/about
来获取公司相关的介绍信息。
在这里使用双引号,是因为这并不是一个真正意义上的“页面”。
通过各种流行的构建工具的配置,所有的脚本最后都得到连成一片巨大的.js
文件。当你访问 youdomain.com/about
这个页面的时候,你会下载应用程序的所有内容。但用户可能根本不想,也不会去浏览这些内容。这是非常糟糕的,也会让你的应用变得越来越大。
对于一个不使用任何框架的普通网页,你首先是下载一个 .html
文件,然后是从缓存中获取脚本和样式文件。用户只下载他需要的内容,仅此而已。
你想让你的React.js
应用也按需加载吗?
前提条件
- 使用
react-router
做为路由解决方案,因为这是功能最强大的React
路由方案; webpack
作为构建工具,因为它有强大的分片能力和齐全的功能
react-router
下面是一个常见的路由例子:
var HomePage = require('./HomePage.jsx');
var AboutPage = require('./AboutPage.jsx');
var FAQPage = require('./FAQPage.jsx');
<Router history={history}>
<Route path="/" component={HomePage} />
<Route path="/about" component={AboutPage} />
<Route path="/faq" component={FAQPage} />
</Router>
为了方便解释,省略了很多代码
如果一个用户浏览 yourdomain.com/about
页面,他将看到AboutPage
组件;如果查看 yourdomain.com/faq
页面,将看到 FAQPage
组件等等。当你使用路由去配置你的应用,这些组件和相关代码将被打包成一个.js
文件。
值得庆幸的是,react-router
的
标签有一个叫做getComponent
的异步的方法去获取组件。他是一个function
接受两个参数,分别是location
和callback
。当react-router
执行回调函数 callback(null, ourComponent)
时,路由只渲染ourComponent
组件。
getComponent
让我们来重写上面的例子来支持异步组件:
<Router history={history}>
<Route
path="/"
getComponent={(location, callback) => {
// 在这里执行异步操作
callback(null, HomePage);
}}
/>
<Route
path="/about"
getComponent={(location, callback) => {
// 在这里执行异步操作
callback(null, AboutPage);
}}
/>
<Route
path="/faq"
getComponent={(location, callback) => {
// 在这里执行异步操作
callback(null, FAQPage);
}}
/>
</Router>
这些组件会在需要的时候异步加载。这些组件仍然会在同一个文件中,并且你的应用看起来不会有任何不同。但是没有它,我们的网页将无法运行。
webpack
webpack
有一个特性叫做 chunking(分片)
,意思就是通过输出多个文件(chunks)来替代一个总的大的文件。你代码里的分割点(split points
)决定了哪些模块被分到哪些文件里。
Split Points(分块点)
webpack
提供了很多方式去让我们设置分块点。但最有用的一个就是 require.ensure
方法。下面是一个例子:
function loadModule() {
require.ensure([], function(require) {
var module = require('module.js');
}, "MyModule");
}
module.js
模块将通过webpack
输出在第二个文件里,并且当浏览器执行require.ensure
的时候加载。(当loadModule
被调用的时候并不会加载)
require.ensure
方法的第三个参数是指定模块名称。它是一个可选项,如果不填,将会自动生成一个 ID作为文件名。
这离成功还差一步,我们还需要配置webpack.config.js
文件来支持分片thunks
配置
在webpack
配置文件中的 output
选项设置chunkFilename
:
output: {
chunkFilename: '[name].chunk.js'
}
你也可以设置[chunkhash]
和[ID]
变量作为分块的文件名。如果没有指定[name]
变量,将会使用 ID
作为分块文件名。
这可以正常运行,但有个问题。通用的依赖通常都在一个单独的文件中。如果在你应用的模块中使用了React
,React
将包含在每一个分片文件thunks
中。
我们可以通过 CommonsChunkPlugin
插件来解决这个问题。在你的配置文件中添加 plugins
选项:
plugins: [
new webpack.optimize.CommonsChunkPlugin('common.js')
]
我喜欢使用
common.js
作为文件名,因为这样很直观,但它也可以任意指定。
非常好,代码分割完成。让我们配合react-router
一起使用。
整合
还记得我们上面提到的异步加载组件的路由吗?上面通过 require
引用 HomePage
组件。让我们配合使用 getComponent
和 require.ensure
来实现按需加载。
<Route
path="/"
getComponent={(location, callback) => {
require.ensure([], function (require) {
var HomePage = require('./HomePage.jsx');
callback(null, HomePage);
}, 'HomePage');
}}
/>
把 require
放在 callback
里更简洁:
<Route
path="/"
getComponent={(location, callback) => {
require.ensure([], function (require) {
callback(null, require('./HomePage.jsx'));
}, 'HomePage');
}}
/>
完整的代码如下:
<Router history={history}>
<Route
path="/"
getComponent={(location, callback) => {
require.ensure([], function (require) {
callback(null, require('./HomePage.jsx'));
});
}}
/>
<Route
path="/about"
getComponent={(location, callback) => {
require.ensure([], function (require) {
callback(null, require('./AboutPage.jsx'));
});
}}
/>
<Route
path="/faq"
getComponent={(location, callback) => {
require.ensure([], function (require) {
callback(null, require('./FAQPage.jsx'));
});
}}
/>
</Router>
本文翻译自 http://blog.mxstbr.com/2016/01/react-apps-with-pages/ 参考 http://react-guide.github.io/react-router-cn/docs/guides/advanced/DynamicRouting.html