React-router-v4 - Webpack 实现按需加载(code-splitting)

React-router-v4 - Webpack 实现按需加载(code-splitting)

源码

方法一、结合 bundle-loader 实现按需加载

1. 首先创建一个包装组件 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;
       }
    }
  1. Bundle 组件会接受一个名为 loadprops
  2. load 值一是一个组件异步加载的方法 load -> function (cb) {...cb(/* 异步加载的组件 */)}
  3. 这个方法需要传入一个回调函数作为参数
  4. 回调函数会在在方法内异步接收加载完的组件

2. 创建包装组件的方法

    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 ? : )} 中渲染

3. 在 Router 配置中使用

    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 中配置

1. 创建一个包装组件 Bundle

创建的组件与 方法一 中完全一致

2. 创建包装组件的函数

函数与 方法一 中完全一致

3. webpack 中配置 loader

由于不是所有 js 文件都是需要包装的组件,因此这里要做一个区分,就是 需要包装的组件要写为 [name].bundle.js

    module.exports = {
        module: {
            rules: [
                {
                    test: /\.bundle\.js$/,
                    loader: 'bundle-loader',
                    include: path.join(__dirname, 'src'), // 源码目录
                    options: {
                        lazy: true,
                        name: '[name]'
                    }
                }
            ]
        }
    }

4. Router 配置中使用

    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 中的配置应该删除

1. 创建一个包装组件 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;
       }
    }

2. 创建包装组件的函数

函数与 方法一 中完全一致

3. Router 配置中使用

    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);

问题

1. 虽然在配置 webpack 时设置了 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'));

2. 而使用 bundle-loader 的方法,由于配置了参数 name ,所以打包后会有模块命名如下

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]

参考

  • react-router
  • Create React App

你可能感兴趣的:(React全家桶)