umi项目本地构建优化

# webpack层面优化[要点]

  • 详见【vue-config详解及实践】

# 配置

# externals

可处理包大小及构建速度;

const getArgvOptions = require('../argvOptions');
const { join } = require('path');

const argvOptions = getArgvOptions();

const base = argvOptions.base || '/';
const ROUTER_BASE = base.lastIndexOf('/') === base.length - 1 ? base : `${base}/`;

module.exports = (api, options = {}) => {
  api.chainWebpackConfig(config => {
    config.externals({
      react: 'window.React',
      'react-dom': 'window.ReactDOM',
      'react-router': 'window.ReactRouter',
      'react-router-dom': 'window.ReactRouterDOM',

      dva: 'window.dva',
      'umi-plugin-locale': 'window.UmiPluginLocale',
      'umi-plugin-locale/lib/locale': 'window.UmiPluginLocale', // 兼容高版本umi国际化
      'umi/locale': 'window.UmiLocale',
      'umi-plugin-react/locale': 'window.UmiLocale',

      moment: 'window.moment',
      jquery: 'window.jQuery',
      '@antv/data-set': 'window.DataSet',
      antd: 'window.antd',
      // '@ant-design/icons': 'window.cloud.ant-design-icons',
      bizcharts: 'window.BizCharts',
      // 'ace-builds': 'window.cloud.ace-builds',
      'lottie-web': 'window.lottie',
      // jsplumb: 'window.cloud.jsPlumb',
      lodash: 'window._',
    });
  });

  api.addHTMLHeadScript(() => {
    const scripts = (options.scripts || []).map(sub => {
      return { src: sub };
    });
    return [
      { src: `${ROUTER_BASE}js/react.production.min.js` },
      { src: `${ROUTER_BASE}js/react-dom.production.min.js` },
      { src: `${ROUTER_BASE}js/react-router.min.js` },
      { src: `${ROUTER_BASE}js/react-router-dom.min.js` },

      { src: `${ROUTER_BASE}js/dva.min.js` },
      { src: `${ROUTER_BASE}js/umi-plugin-locale.js` },

      { src: `${ROUTER_BASE}js/moment.min.js` },
      { src: `${ROUTER_BASE}js/moment/zh-cn.js` },
      { src: `${ROUTER_BASE}js/moment/zh-tw.js` },

      { src: `${ROUTER_BASE}js/antd.min.js` },
      { src: `${ROUTER_BASE}js/antd-with-locales.min.js` },
      { src: `${ROUTER_BASE}js/jquery.min.js` },
      { src: `${ROUTER_BASE}js/lodash.min.js` },
      { src: `${ROUTER_BASE}js/biz-charts.min.js` },
      { src: `${ROUTER_BASE}js/lottie.min.js` },
      ...scripts,
    ];
  });

  api.addHTMLLink(() => {
    return (options.stylesheets || []).map(sub => {
      return { href: sub, rel: 'stylesheet' };
    });
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

# moment

ignoreMomentLocale: true, // 忽略 moment 的 locale 文件,用于减少尺寸 ===> 使用 dayjs 代替了
1
// 过滤掉moment的那些不使用的国际化文件,过滤后打包出来的vendors.xxx.async.js可以减少290KB
config.plugin("replace").use(require("webpack").ContextReplacementPlugin).tap(() => {
  return [/moment[/\\]locale$/, /zh-cn|en-us/];
});
1
2
3
4

# splitChunks

chainWebpack(config) {
  config.optimization.splitChunks({
    chunks: "all", //async异步代码分割 initial同步代码分割 all同步异步分割都开启
    automaticNameDelimiter: "~",
    name: true,
    minSize: 30000, //字节 引入的文件大于30kb才进行分割
    //maxSize: 50000,         //50kb,尝试将大于50kb的文件拆分成n个50kb的文件
    minChunks: 1, //模块至少使用次数
    // maxAsyncRequests: 5,    //同时加载的模块数量最多是5个,只分割出同时引入的前5个文件
    // maxInitialRequests: 3,  //首页加载的时候引入的文件最多3个
    // name: true,                  //缓存组里面的filename生效,覆盖默认命名
    cacheGroups: {
      react: {
        name: "react",
        test: /[\\/]node_modules[\\/](react)[\\/]/,
        priority: -9,
        enforce: true,
      },
      reactDom: {
        name: "react-dom",
        test: /[\\/]node_modules[\\/](react-dom)[\\/]/,
        priority: -9,
        enforce: true,
      },
      echarts: {
        name: "echarts",
        test: /[\\/]node_modules[\\/](echarts)[\\/]/,
        priority: -9,
        enforce: true,
      },
      antd: {
        name: "antd",
        test: /[\\/]node_modules[\\/](@ant-design|antd|antd-mobile)[\\/]/,
        priority: -10,
        enforce: true,
      },
      vendors: {
        name: "vendors",
        test: /[\\/]node_modules[\\/]/,
        priority: -11,
        enforce: true,
      },
    },
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

其他方式:

config.optimization.splitChunks({
        chunks: 'all', 
        automaticNameDelimiter: '.', 
        name: true,
        minSize: 30000,
        maxSize: 0,
        minChunks: 1,
        maxAsyncRequests: 10,
        maxInitialRequests: 5,
        cacheGroups: {
            vendors: {
                name: 'vendors',
                chunks: 'all',
                test: /[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom|lodash|lodash-decorators|redux-saga|re-select|dva|moment)[\\/]/,
                priority: -10,
            },
            antdesigns: {
                name: 'antdesigns',
                chunks: 'all',
                test: /[\\/]node_modules[\\/](@ant-design|antd)[\\/]/,
                priority: -11,
            },
          default: {
            minChunks: 1,
            priority: -20,
            reuseExistingChunk: true
          }
        }
      })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 引入插件

# happypack

Happypack 的作用就是将文件解析任务分解成多个子进程并发执行。子进程处理完任务后再将结果发送给主进程。利用 js 的多进程来实现打包加速。

const HappyPack = require('happypack');
config.plugin('HappyPack').use(HappyPack, [{
  id: 'js',
  loaders: ['babel-loader'],
  threadPool: HappyPack.ThreadPool({ size: require('os').cpus().length }),
}])
1
2
3
4
5
6

注意:Ahmad Amireh 推荐使用 thread-loader,并宣布将不再继续维护 happypack,所以不推荐使用它

# uglifyjs

不支持es6压缩,推荐用terser;

chainWebpack(config) {
  // 删除 打包时 进度条插件
  // config.plugins.delete('progress');
  config.merge({
    plugin: {
      install: {
        plugin: require('uglifyjs-webpack-plugin'),
        args: [{
          sourceMap: false,
          uglifyOptions: {
            compress: {
              drop_console: true, // 删除所有的 `console` 语句
            },
            output: {
              beautify: false,// 最紧凑的输出
              comments: false,// 删除所有的注释
            },
          }
        }]
      }
    },
     optimization: {
                minimize: true,
                splitChunks: {
                    chunks: 'async',
                    minSize: 20000, //生成块的最小大小(以字节为单位)1024字节=1KB。
                    minChunks: 1,   //拆分前必须共享模块的最小块数。
                    maxInitialRequests: 30, //入口点的最大并行请求数。
                    automaticNameDelimiter: '.',
                    cacheGroups: {
                        vendor: {
                            name: 'vendors',
                            test: /^.*node_modules[\\/](?!ag-grid-|lodash|wangeditor|react-virtualized|rc-select|rc-drawer|rc-time-picker|rc-tree|rc-table|rc-calendar|echarts|echarts-gl|xlsx|@ant-design|antd|ali-oss).*$/,
                            priority: -10,
                            enforce: true,
                        },
                    }
                }
     }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# terser-webpack-plugin

如果你使用的是 webpack v5 或以上版本,你不需要安装这个插件。webpack v5 自带最新的 terser-webpack-plugin。如果使用 webpack v4,则必须安装 terser-webpack-plugin v4 的版本。

npm install --save-dev terser-webpack-plugin@4
1
 const TerserPlugin = require('terser-webpack-plugin');
 
 config.plugin('TerserPlugin').use(TerserPlugin, [{
    parallel: require('os').cpus().length - 1,
    terserOptions: {
      compress: {
        inline: false
      },
      mangle: {
        safari10: true
      }
    }
}])
1
2
3
4
5
6
7
8
9
10
11
12
13

# hard-source-webpack-plugin

在 umi2 默认使用了 hard-source-webpack-plugin 插件提升构建速度,不知道为啥在 umi3 干掉了这一功能

第一步肯定是安装插件了

yarn add --dev hard-source-webpack-plugin
1

第二步在 config.ts 中使用 chainWebpack 配置插件

import { defineConfig } from 'umi';
export default defineConfig({
  // webpack 配置
  chainWebpack: function (config, { webpack }) {
    // 打包加速
    config.plugin('hardSource').use(HardSourceWebpackPlugin);
    // 对下面配置的 module 不进行缓存
    config.plugin('hardSourceExcludeModule').use(HardSourceWebpackPlugin.ExcludeModulePlugin, [
      [
        {
          test: /mini-css-extract-plugin[\\/]dist[\\/]loader/,
        },
        {
          test: /eslint-loader/,
        },
        {
          test: /.*\.DS_Store/,
        },
      ],
    ]);
  },
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# compression-webpack-plugin

项目安装plugin依赖

yarn add compression-webpack-plugin -D
1

在config/config.js目录下进行配置

const CompressionWebpackPlugin = require('compression-webpack-plugin');
const prodGzipList = ['js', 'css'];
	
chainWebpack: config => {
  if (process.env.NODE_ENV === 'production') {  // 生产模式开启
    config.plugin('compression-webpack-plugin').use(
      new CompressionWebpackPlugin({
        // filename: 文件名称,这里我们不设置,让它保持和未压缩的文件同一个名称
        algorithm: 'gzip', // 指定生成gzip格式
        test: new RegExp('\\.(' + prodGzipList.join('|') + ')$'), // 匹配哪些格式文件需要压缩
        threshold: 10240, //对超过10k的数据进行压缩
        minRatio: 0.6 // 压缩比例,值为0 ~ 1
      })
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 最后设置

plugin.config.js

const HappyPack = require('happypack');
const TerserPlugin = require('terser-webpack-plugin');

export default config => {
  config.plugin('HappyPack').use(HappyPack, [{
    id: 'js',
    loaders: ['babel-loader'],
    threadPool: HappyPack.ThreadPool({ size: require('os').cpus().length }),
  }])

  config.plugin('TerserPlugin').use(TerserPlugin, [{
    parallel: require('os').cpus().length - 1,
    terserOptions: {
      compress: {
        inline: false
      },
      mangle: {
        safari10: true
      }
    }
  }])

  // 设置 alias
  config.resolve.alias.set('@', path.join(__dirname, '../src/'));
  config.resolve.alias.set('@node_modules', path.join(__dirname, '../node_modules/'));
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
上次更新: 2022/04/15, 05:41:29
×