天天看點

React-router-v4 - Webpack 實作按需加載(code-splitting)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 元件會接受一個名為

    load

    props

  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 ? <Comp {...props} /> : <Loading />)}

中渲染

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: ms
                                 Asset     Size  Chunks             Chunk Names
                 chunk/c8b.js   kB         [emitted]
                 chunk/.e5cc1c11d3.js   kB         [emitted]
    static/js/app.f61c516b85.bundle.js     kB         [emitted]  app
 static/js/vendor.f5f812985.bundle.js    kB         [emitted]  vendor
static/js/runtime.bbb4b5baf.bundle.js   kB         [emitted]  runtime
         static/css/app.ec6e1795a1.css   kB         [emitted]  app
                            index.html   kB          [emitted]
           
可以使用 webpack 提供的一個特殊的注釋文法 來提供 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: ms
                          Asset     Size  Chunks             Chunk Names
       chunk/Home.a8c77815d.js   kB         [emitted]  Home
       chunk/Test.f60c.js   kB         [emitted]  Test
    static/js/app.edc8970d4.js   kB         [emitted]  app
 static/js/vendor.f5f812985.js    kB         [emitted]  vendor
static/js/runtime.f777aeb81.js   kB         [emitted]  runtime
  static/css/app.ec6e1795a1.css   kB         [emitted]  app
                     index.html   kB          [emitted]
           

參考

  • react-router
  • Create React App

繼續閱讀