如何优化 Webpack?

1,447 阅读4分钟

1)优化 Webpack 的构建速度

  • 使用高版本的 Webpack(使用 webpack4)
  • 多线程/多实例构建:happypack(不维护了)、thread-loader
  • 缩小打包作用域:
    • exclude/include (确定 loader 规则范围)
    • resolve.modules 指明第三方模块的绝对路径(减少不必要的查找)
    • resolve.extensions 尽可能减少后缀尝试的可能性
    • noParse 对完全不需要解析的库进行忽略(不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 import、require、define 等模块化语句)
    • IgnorePlugin(完全排除模块)
    • 合理使用 alias
  • 充分利用缓存提升二次构建速度:
    • babel-loader 开启缓存
    • terser-webpack-plugin 开启换成
    • 使用 cache-loader 或者 hard-source-webpack-plugin
    注:thread-loader 和 cache-loader 两个一起使用的话,请先放 cache-loader,接着是thread-loader,最后才是 heavy-loader。
  • DLL:
    • 使用 DllPlugin 进行分包,使用 DllReferencePlugin(索引链接)对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间。

2)优化 Webpack 的打包体积

  • 压缩代码
    • webpack-paralle-uglify-plugin
    • uglifyjs-webpack-plugin 开启 parallel 参数
    • terser-webpack-plugin 开启 parallel 参数
    • 多线程并行压缩
    • 通过 mini-css-extract-plugin 提取 Chunk 中的 CSS 代码到单独文件,通过对 optimize-css-asstes-webpack-plugin 插件,开启 cssnano 压缩 CSS
  • 提取页面公共资源
    • 使用 html-webpack-externals-plugin,将基础包通过 CDN 引入,不打入 bundle 中
    • 使用 SplitChunksPlugin 进行(公共脚本、基础包、也没公共文件)分离(Webpack4 内置),替代 CommonsChunkPlugin 插件
  • Tree-shaking
    • purgecss-webpack-plugin 和 mini-css-extract-plugin 配合使用
    • 打包过程中检测工程中没用引用过的模块并进行标记,在资源压缩时将他们从最终的 bundle 中去掉。该功能只能对 ES6 Modlue 生效,所以开发中尽可能使用 ES6 Modlue 的模块,提高 Tree-shaking 效率
    • 禁用 babel-loader 的模块依赖解析,否则 Webpack 接收到的就是转换过的 CommonJs 形式的模块,无法进行 Tree-shaking
    • 使用 PurifyCSS(不维护了)或者 uncss 去除无用 CSS 代码
  • Scope hoisting
    • 构建后的代码会存在大量闭包,造成体积增大,运行代码时创建的函数作用域变多,内存开销变大。Scope hoisting 将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突
    • 必须是 ES6 的语法,因为很多第三方库仍采用 CommonJS 语法,为了充分发挥 Scope hoisting 的作用,需要配置 mainFields 对第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法
  • 图片压缩
    • 使用基于 Node 库的 imagemin(很多定制选项、可以处理多种图片格式)
    • 配置 image-webpack-loader
  • 动态 Polyfill
    • 建议采用 polyfill-service 只给用户返回需要的 polyfill(部分国内奇葩浏览器UA可能无法识别,可以降级返回所需全部 polyfill)
    • @babel/preset-env 中通过 useBuiltlns:usage 参数来动态加载 polyfill

3)speed-measure-webpack-plugin

简称 SMP,分析出 Webpack 打包过程中 Loader 和 Plugin 的耗时,有助于找到构建过程中的性能瓶颈。

webpack4 的优化

  • V8 带来的优化(for of 替代 forEach、map,Set 替代 Object,includes 替代 indexOf)

  • 默认使用更快的 md4 hash 算法

  • webpacks AST 可以直接从 loader 传递给 AST,减少解析时间

  • 使用字符串方法替代正则表达式

    1. noParse

    不去解析某个库内部依赖关系,比如 jquery 这个库是独立的,则不去解析这个库内部以来的其他东西。在独立库的时候可以使用:

    module.exports = {
        module: {
            noParse: /jquery/,
            rules: []
        }
    }
    
    2.IgnorePlugin

    忽略掉某些内容,不去解析依赖库内部引用的某些内容。比如用 IgnorePlugin 来忽略 moment 的语言包 :

    module.exports = {
        plugins: [
            new Webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
        ]
    }
    
    3.dllPlugin

    Webpack.DllPlugin、Webpack.DllReferencePlugin

    • 不会多次打包,优化打包时间
    • 先把依赖的不变的库打包
    • 生成 mainfest.json 文件
    • 然后在 webpack.config 中引入
    4.happypack -> thread-loader
    • 在大项目的时候开启多线程打包
    • 影响前段发布速度的有两个方面,一个是构建,一个就是压缩,把这两个东西优化起来,可以减少很多发布的时间。
    5.thread-loader

    thread-loader 会将你的 loader 放置在一个 worker 池里面运行,以达到多线程构建。把这个 loader 放置在其他 loader 之前(如下图 example 的位置),放置在thread-loader 之后的 loader 就会在一个单独的 worker 池中运行。

    // webpack.config.js
    module.exports = {
        module: {
            rules: [
                {
                    test: /\.js$/,
                    include: path.resolve("src"),
                    use: [
                        "thread-loader",
                        // 你的高开销的 loader 放置在这里(e.g babel-loader)
                    ]
                }
            ]
        }
    }
    

    每个 worker 都是一个单独的有600ms限制的 node.js 进程,同时跨进程的数据交互也会被限制。请在高开销的 loader 中使用,否则效果不会很好。

    6.压缩加速--开启多线程压缩

    推荐使用 terser-webpack-plugin

    modeule.exports = {
        optimization: {
            minimizer: [
                new TerserPlugin({ parallel: true }) // 多线程
            ]
        }
    }