源码
bundle-loader
实现按需加载Bundle
一下是 react-router4.0 官方文档中给出的例子
import React from 'react';
export default class Bundle extends React.Component {
state = {
mod: null
}
componentWillMount() {
this.load(this.props);
}
componentWillReceiveProps(nextProps) {
if (nextProps.load !== this.props.load) {
this.load(nextProps);
}
}
// load 方法,用于更新 mod 状态
load(props) {
// 初始化
this.setState({
mod: null
});
/*
调用传入的 load 方法,并传入一个回调函数
这个回调函数接收 在 load 方法内部异步获取到的组件,并将其更新为 mod
*/
props.load(mod => {
this.setState({
mod: mod.default ? mod.default : mod
});
});
}
render() {
/*
将存在状态中的 mod 组件作为参数传递给当前包装组件的'子'
*/
return this.state.mod ? this.props.children(this.state.mod) : null;
}
}
load
的 props
load -> function (cb) {...cb(/* 异步加载的组件 */)}
import React from 'react';
import Bundle from './Bundle';
// 默认加载组件,可以直接返回 null
const Loading = () => <div>Loading...div>;
/*
包装方法,第一次调用后会返回一个组件(函数式组件)
由于要将其作为路由下的组件,所以需要将 props 传入
*/
const lazyLoad = loadComponent => props => (
<Bundle load={loadComponent}>
{Comp => (Comp ? <Comp {...props} /> : <Loading />)}
Bundle>
);
export default lazyLoad;
结合第一步中
this.props.children(this.state.mod)
加载后的组件会被传入{Comp => (Comp ?
中渲染: )}
import React from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import { hot } from 'react-hot-loader';
// 引入包装函数
import lazyLoad from './lazyLoad';
import App from '../containers/App';
/*
使用 bundle-loader 插件引用组件,返回的实际上是包装后的组件异步加载的函数
*/
import Home from 'bundle-loader?lazy&name=home!../containers/Home';
import Test from 'bundle-loader?lazy&name=test!../containers/Test';
const Root = () => (
<div>
<Switch>
<Route path="/" render={props => (
<App>
<Switch>
<Route path="/" exact component={lazyLoad(Home)} />
<Route path="/home" component={lazyLoad(Home)} />
<Route path="/test" component={lazyLoad(Test)} />
<Route render={() => <Redirect to="/" />} />
Switch>
App>
)}
/>
<Route render={() => <Redirect to="/" />} />
Switch>
div>
);
export default hot(module)(Root);
bundle-loader
的另一种使用方式 - 在 webpack 中配置Bundle
创建的组件与 方法一 中完全一致
函数与 方法一 中完全一致
由于不是所有
js
文件都是需要包装的组件,因此这里要做一个区分,就是 需要包装的组件要写为[name].bundle.js
module.exports = {
module: {
rules: [
{
test: /\.bundle\.js$/,
loader: 'bundle-loader',
include: path.join(__dirname, 'src'), // 源码目录
options: {
lazy: true,
name: '[name]'
}
}
]
}
}
import lazyLoad from './lazyLoad';
import App from '../containers/App';
/*
其他配置使用与 方法一 一致
只是引用组件的时候不在需要在路径中配置 bundle-loader
需要按需加载的组件也要文件名变为 [name].bundle.js 的格式
*/
// import Home from 'bundle-loader?lazy&name=home!../containers/Home';
// import Test from 'bundle-loader?lazy&name=test!../containers/Test';
import Home from '../containers/Home.bundle';
import Test from '../containers/Test.bundle';
...
import()
方法代替 bundle-loader 实现
import('../xxx.js')
返回的是一个 promise,因此需要改写 Bundle 组件,此外不在需要 bundle-loader ,其在 webpack 中的配置应该删除
Bundle
import React from 'react';
export default class Bundle extends React.Component {
state = {
mod: null
}
componentWillMount() {
this.load(this.props);
}
componentWillReceiveProps(nextProps) {
if (nextProps.load !== this.props.load) {
this.load(nextProps);
}
}
// 更改 load 方法为异步函数
async load(props) {
this.setState({
mod: null
});
/*
使用 props.load() 返回的是一个 promise
*/
const mod = await props.load();
this.setState({
mod: mod.default ? mod.default : mod
});
}
render() {
return this.state.mod ? this.props.children(this.state.mod) : null;
}
}
函数与 方法一 中完全一致
import React from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import { hot } from 'react-hot-loader';
import lazyLoad from './lazyLoad';
import App from '../containers/App';
// 动态引入
const Home = lazyLoad(() => import('../containers/Home'));
const Test = lazyLoad(() => import('../containers/Test'));
const Root = () => (
<div>
<Switch>
<Route path="/" render={props => (
<App>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/home" component={Home} />
<Route path="/test" component={Test} />
<Route render={() => <Redirect to="/" />} />
Switch>
App>
)}
/>
<Route render={() => <Redirect to="/" />} />
Switch>
div>
);
export default hot(module)(Root);
output: {chunkFilename: 'chunk/[name].[chunkhash].js'}
但是使用 ‘方法三’ 拆分后的包名还是以数字开始的,如下Time: 9556ms
Asset Size Chunks Chunk Names
chunk/0.7429253c8b.js 5.86 kB 0 [emitted]
chunk/1.e5cc1c11d3.js 1.32 kB 1 [emitted]
static/js/app.f61c516b85.bundle.js 40 kB 2 [emitted] app
static/js/vendor.9f5f812985.bundle.js 209 kB 3 [emitted] vendor
static/js/runtime.1bbb4b5baf.bundle.js 1.44 kB 4 [emitted] runtime
static/css/app.ec6e1795a1.css 2.54 kB 2 [emitted] app
index.html 2.16 kB [emitted]
可以使用 webpack 提供的一个特殊的注释语法
/* webpackChunkName: "Home" */
来提供 chunk name (需要 Webpack > 2.4),这样生成的模块就能与 bundle-loader 一致的命名
const Home = lazyLoad(() => import(/* webpackChunkName: "Home" */'../containers/Home'));
const Test = lazyLoad(() => import(/* webpackChunkName: "Test" */'../containers/Test'));
Time: 9190ms
Asset Size Chunks Chunk Names
chunk/Home.4a8c77815d.js 21.6 kB 0 [emitted] Home
chunk/Test.283681f60c.js 1.32 kB 1 [emitted] Test
static/js/app.3edc8970d4.js 24.2 kB 2 [emitted] app
static/js/vendor.9f5f812985.js 209 kB 3 [emitted] vendor
static/js/runtime.0f777aeb81.js 1.46 kB 4 [emitted] runtime
static/css/app.ec6e1795a1.css 2.54 kB 2 [emitted] app
index.html 2.14 kB [emitted]