[聚焦Webpack]10-3 webpack性能优化 - 图1

优化打包构建速度

缩小打包范围
resolve.modules

  1. resolve: {
  2. extensions: ['.js', '.vue', '.json'],
  3. alias: config.alias,
  4. modules: ['node_modules']
  5. },

生产环境

缩小打包作用域(范围)

1. 优化babel-loader(module/rules)。 开启缓存, 设置文件范围(include、exclude)

  1. test: /\.js$/,
  2. loader: ['babel-loader?cacheDirectory'], // 开启混存
  3. include: path.resolve(__dirname, 'src'), // 明确范围
  4. // exclude: path.resolve(__dirname, 'node_modules') //两者选一即可

dev启动提升30%: 11286ms -> 7965ms
prod时间提升35%: 14.41s -> 9.34s

2. IgnorePlugin 避免引入无用模块

以moment 为例,正常打包index.js大小约为5K, 设置忽略local文件后, 打包后的index.js 约为1K。 项目需要的
地方手动引入。webpack.prod.js/plugins

  1. new webpack.IgnorePlugin(/\.\/locale/, /moment/),// 忽略 moment 下的 /locale 目录

⚠️ 中文需要单独引入

  1. import moment from 'moment'
  2. import 'moment/locale/zh-cn'
  3. moment.locale('zh-cn')

dev:
prod: moment(async-vendors.js)体积大大减小 571k -> 151k
image.png image.png

3. noParse避免重复打包。

忽略对xx.js 文件的递归解析处理。例如jquery内部独立, 没有第三方依赖。

  1. module: {
  2. noParse: /jquery|lodash/,
  3. noParse: [/react\.min\.js/],
  4. noParse: (content) => /jquery|lodash/.test(content)
  5. }

IgnorePlugin 与 noParse 的区别:IgnorePlugin 直接不引入, 代码中没有; noParse 引入, 但不打包

多进程打包。 JS 单线程, 开启多进程打包。提高构建速度(特别是多核CPU)

  • thread-loader
  • webpack-parallel-uglify-plugin
  • happypack(已废弃)

    thread-loader(周下载130万)

    webpack.prod.js (建议, 仅在生产环境中使用)

    1. module: {
    2. rules: [
    3. {
    4. test: /\.js$/,
    5. loader: ['babel-loader?cacheDirectory', 'thread-loader'],
    6. }
    7. ]
    8. }

    实践

    1. loader: 'babel-loader',
    2. loaders: ['thread-loader', 'babel-loader'],

    before: Time: 36650ms
    after: Time: 15276ms
    提升: 58%

    webpack-parallel-uglify-plugin

  • webpack 内置Uglify 工具压缩JS。JS 单线程, 开启多进程压缩更快。和happyPack 同理。

    1. const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
    2. new ParallelUglifyPlugin({
    3. uglifyJS: { //使用 UglifyJS 压缩
    4. output: {
    5. beautify: false, // 最紧凑的输出
    6. comments: false, // 删除所有的注释
    7. },
    8. compress: {
    9. drop_console: true, // 删除所有的 `console` 语句,可以兼容ie浏览器
    10. collapse_vars: true, // 内嵌定义了但是只用到一次的变量
    11. reduce_vars: true, // 提取出出现多次但是没有定义成变量去引用的静态值
    12. }
    13. }

    开启多进程需要按照需求使用:项目较大, 打包较慢, 开启多进程能提高速度。项目较小, 打包很快, 开启多进程会降低速度(进程开销)

    happypack(已废弃)

    happyPack(周下载11万)

    1. module: {
    2. rules: [{
    3. test: /\.js$/
    4. use: ['happypack/loader?id=babel'], // 把对 .js文件的处理转交给 id 为 babel 的 HappyPack 实例
    5. include: srcPath,
    6. }]
    7. },
    8. plugins: [
    9. new HappyPack({ // happyPack 开启多进程打包
    10. id: 'babel', // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
    11. loaders: ['babel-loader?cacheDirectory'] // 如何处理 .js 文件,用法和 Loader 配置中一样
    12. }),
    13. ]

    vue-cli4自带

    vue-cli (内置接口)

    1. module.exports = {
    2. parallel: require('os').cpus().length > 1,
    3. }

    缓存

    babel-loader缓存

    terser-webpack-plugin实现缓存

    cache-loader/hard-source-webpack-plugin

    优化开发体验(开发环境)

    6. 自动刷新(开发环境)

    1. watch: true, // 开启监听,webpack-dev-server 会自动开启刷新浏览器
    2. watchOptions: {
    3. ignored: /node_modules/,
    4. aggregateTimeout: 300, // 防止文件更新太快导致重新编译频率太高,
    5. poll: 1000 // 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
    6. }

    7. 热更新

    | 自动刷新(整个页面全部刷新) | 热更新 | | —- | —- | |
    - 速度较慢
    - 状态会丢失
    | 新代码生效, 网页不刷新, 状态不丢失 |

配置+ 开启监听范围

  1. plugins: [ new HotModuleReplacementPlugin() ],
  2. devServer: { hot: true, }
  3. if (module.hot) { // 配置, 哪些模块进行热更新

8. 动态链接库插件 : DllPugin + DllReferencePlugin

拆分 bundles
背景:前端框架(Vue、React),体积大,构建慢。 较稳定, 不常升级版本。 同一个版本只构建一次即可, 不用每次重新构建
webpack已内置DllPlugin支持;DllPlugin - 打包出dll文件; DllReferencePlugin - 使用dll 文件。 "dll": "webpack --config build/webpack.dll.js"

  1. const DllPlugin = require('webpack/lib/DllPlugin')
  2. module.exports = {
  3. mode: 'development',
  4. entry: {
  5. react: ['react', 'react-dom'] // 把 React 相关模块的放到一个单独的动态链接库
  6. },
  7. output: {
  8. filename: '[name].dll.js', // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
  9. path: distPath, // 输出的文件都放到 dist 目录下
  10. library: '_dll_[name]', // 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react
  11. },
  12. plugins: [
  13. new DllPlugin({
  14. name: '_dll_[name]', // 动态链接库的全局变量名称,需要和 output.library 中保持一致
  15. context: __dirname, // 必填,_dll_polyfill is not defined
  16. path: path.join(distPath, '[name].manifest.json'), // 描述动态链接库的 manifest.json 文件输出时的文件名称
  17. }),
  18. ],
  19. }

webpack.dev.js

  1. const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
  2. new DllReferencePlugin({
  3. manifest: require(path.join(distPath, 'react.manifest.json')), // 描述 react 动态链接库的文件内容
  4. }),

与 splitChunks 的区别?
splitChunks 作用是将第三方的组件拆分出来,打包成一个或几个包,用于长期缓存。这个行为可以在webpack中设置并自动完成。
DllPlugin 也能将第三方组件拆分出来,打包成一个或几个包,用于长期缓存且能加速打包过程。
那么它们的差异在于:

  1. DllPlugin 需要设置打包的配置文件,并先于项目打包将第三方组件打包;
  2. DllPlugin 需要手动插入到对应的页面(可以使用 add-asset-html-webpack-plugin 在打包项目的时候自动插入 );
  3. Dllplugin 内含有的组件在 webpack 打包项目的时候,不经过打包过程。所以能加快打包速度。(这个其实可以使用 webpack 中的 external 来排除打包某些组件,然后通过链接将对应的组件链入页面,达到相同效果);
  4. 如果库可以按需加载,Dllplugin 将不能按需加载,它方式是全量的引入的,而 splitChunks 可以按需加载地打包。

其实看起来,splitChunks 就是 DllPlugin 的自动版本。

优化产出

使用production。 优势:

  • 自动开启代码压缩(webpack v4+)
  • Vue、React等会自动删掉调试代码(ex, 开发环境的warning)
  • 启动 Tree-Shaking(mode为’production’即可)

什么是Tree-Shaking?(注意: 必须使用ES6 Module 才能使 Tree-shaking 生效)比如引入但未使用的不打包。
ES6 Module, 静态引用, 编译时引用; Commonjs, 动态引入, 执行引入

什么是Scope Hosting?
new webpack.optimize.ModuleConcatenationPlugin();前提是ES6模块化语法的文件
优势:代码体积更小; 创建函数作用域更少; 代码易读性更好

懒加载

  1. const Foo = resolve => require(['./Foo.vue'], resolve)
  2. //或者
  3. const Foo = () => import('./Foo');

动态polyfill

根据不同浏览器不同版本载入不同补丁。

  1. {
  2. "presets": [
  3. [
  4. "@babel/preset-env",
  5. {
  6. "useBuiltIns": "entry"
  7. }
  8. ]
  9. ]
  10. }

vue-cli

  1. // webpack.dll.conf.js
  2. module.exports = {
  3. entry: { // 把 vue 相关模块的放到一个单独的动态链接库
  4. vue: ['babel-polyfill', 'vue', 'vue-router', 'vuex', 'axios', 'element-ui']
  5. },
  6. }

vue-cli4

  1. // babel.config.js
  2. module.exports = {
  3. presets: [
  4. ['@vue/app', {
  5. polyfills: [
  6. 'es.promise',
  7. 'es.symbol'
  8. ]
  9. }]
  10. ]
  11. }

参考:
https://tsejx.github.io/webpack-guidebook/best-practice/optimization/dynamic-polyfill

测试工具

webpack-bundle-analyzer

  1. if (config.build.bundleAnalyzerReport) {
  2. const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  3. webpackConfig.plugins.push(new BundleAnalyzerPlugin())
  4. }

image.png

webpack-jarvis

  1. if (process.env.watch) {
  2. const Jarvis = require('webpack-jarvis')
  3. console.log(config.build.watch);
  4. webpackConfig.plugins.push(new Jarvis({
  5. watchOnly: false,
  6. port: 1337 // optional: set a port
  7. }))
  8. }

image.png

speed-measure-webpack-plugin

参考:
https://github.com/lgwebdream/FE-Interview/issues/25
https://www.yuque.com/allenzhoujiawei/kb/zsp9gp?language=zh-cn