3

我做 react 开发时通常是直接用的 create-react-app。最近想分析一下一个用 create-react-app 开发的项目的打包结果,看看有没有什么可以优化的地方。

项目情况

执行 npm run eject 导出配置(单向操作,不可逆)。
项目中使用的一些库:

"dependencies": {
    "antd": "^3.9.2",
    "axios": "^0.18.0",
    "echarts": "^3.8.5",
    "less": "^3.0.1",
    "moment": "^2.21.0",
    "react": "^16.4.2",
    "react-dom": "^16.2.0",
    "react-router-dom": "^4.2.2",
},
"devDependencies": {
    "babel-loader": "7.1.2",
    "babel-plugin-import": "^1.6.5",
    "webpack": "3.8.1",
    "webpack-bundle-analyzer": "3.0.2",
}

antd 按需加载

按照官网配置,使用 babel-plugin-import,在 package.json 中配置:

  "babel": {
    "plugins": [
      [
        "import",
        {
          "libraryName": "antd",
          "style": true
        }
      ]
    ]
  }

项目中直接使用:

import { Button } from 'antd';

Moment.js locale 打包优化

create-react-app 的 webpack 已经做好配置了:

plugins: [
    ...
    // Moment.js is an extremely popular library that bundles large locale files
    // by default due to how Webpack interprets its code. This is a practical
    // solution that requires the user to opt into importing specific locales.
    // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
    // You can remove this if you don't use Moment.js:
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
    ...
]

router component 懒加载

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

    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;
    }
}
const lazyLoad = loadComponent => props => (
    <Bundle load={loadComponent}>
        {Comp => (Comp ? <Comp {...props} /> : <Loading/>)}
    </Bundle>
);

使用:

// dynamic import
const Demo = lazyLoad(() => import('../components/demo'));

// react-router
<Route path="/demo" component={Demo} />

分析项目打包情况

使用工具webpack-bundle-analyzer,将打包内容转换成可缩放的树状图。

// 安装
yarn add -D webpack-bundle-analyzer

config/webpack.config.dev.jsconfig/webpack.config.prod.js 中添加:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
...
plugins: [
    ...
    new BundleAnalyzerPlugin(),
    ...
]
...

默认在 127.0.0.1:8888 页面显示。
添加在 config/webpack.config.dev.js后,每次 npm start 时都会弹出分析页面。
添加在 config/webpack.config.prod.js后,每次 npm build 时都会弹出分析页面。
项目分析结果:
项目分析结果

发现问题:

  1. 多个 chunk 中重复打包了相同的 antd 组件代码(table, pagination, .etc)。
  2. echarts 全部打包了进来,体积太大。

优化

antd 组件代码重复打包问题

由于我对 webpack 不算太熟,知道 webpack 3 中可以用 plugin CommonsChunkPlugin 抽取公共代码,但具体用法不甚明了,折腾了很久都没有作用,终于在 antd github 的 issues 中找到答案

plugins: [
    ...
    new webpack.optimize.CommonsChunkPlugin({
      minChunks: 2,
      minSize: 0,
      children: true,
      deepChildren: true,
      async: true
    }),
    ...
]

似乎去掉 name 就可以了?这一点我还没搞清楚是为什么。

echarts 按需加载

参考在 webpack 中使用 ECharts

import echarts from 'echarts/lib/echarts';
import 'echarts/lib/chart/map';
import 'echarts/lib/chart/pie';
import 'echarts/lib/chart/scatter';
import 'echarts/lib/chart/effectScatter';
import 'echarts/lib/component/geo';
import 'echarts/lib/component/tooltip';
import chinaJson from 'echarts/map/json/china.json';

...
echarts.registerMap('china', chinaJson);
...

需要注意,可以按需引入的模块列表见 https://github.com/ecomfe/ech...

优化结果

优化结果


Jane_Shen
629 声望4 粉丝