webpack优化

157 阅读5分钟
  1. 分析打包速度:speed-measure-webpack-plugin
  2. 分析包内容:webpack-bundle-analyzer
  3. 分析影响打包速度环节:
    • 开始打包,我们需要获取所有的依赖模块
      • 优化搜索时间:在遇到导入语句时webpack会做两件事
          1. 根据导入语句去寻找对应要导入的文件
          1. 根据找到的要导入的文件的后缀,使用配置中的loader去处理文件。
      • 优化loader配置:通过以下配置来命中loader要应用规则的文件
        • test
        • include
        • exclude
      • 优化resolve.module配置:resolve.module用于配置webpack去哪些目录下寻找第三方模块,默认值就是["node_modules"].
      • 优化resolve.alias配置:该配置通过别名来把原导入路径映射成一个新的导入路径,减少耗时的递归解析操作
      • 优化resolve.extensions配置:在导入语句没带文件后缀时,webpack会根据webpack.extension自动带上后缀去尝试询问文件是否存在,所以在配置resolve.extensions应尽量注意以下几点:
        • 高频的后缀放前面
        • 不存在的文件后缀情况不要写在里面
        • 在源码中写导入语句时,要尽可能的带上后缀,从而避免寻找过程。
      • 优化resolve.mainFields配置:有些第三方模块会针对不同环境提供几份代码,例如分别提供采用es5,es6的两份代码,这两份代码的位置写在package.json文件里,webpack会根据mainFields的配置去决定优先采用哪份代码。mainFields默认值如下:["browser","main"]。webpack会按照数组里的顺序去package.json里找,只会使用找到的第一个
      {
          "jsnext:main": "es/index.js",// 采用 ES6 语法的代码入口文件
          "main": "lib/index.js" // 采用 ES5 语法的代码入口文件
      }
      
      
      如果想优先采用es6的那份代码,可以配置为:mainFields:["jsnext:main","browser","main"]
      • 优化module.noParse:可以让webpack忽略对部分没采用模块化的文件递归解析处理,能提高构建性能。例如jquery,chartjs,庞大且没采用模块化标准,webpack解析这些文件耗时且没意义。
    • 通过loader解析代码,js单线程特性使js/css/图片/字体等文件转换不能并发处理
      • 优化解析时间
        • 开启多进程打包
          • thread-loader:原理是放置在这个loader之后的loader会在一个单独的worker池运行。一个worker就是一个nodejs进程,每个单独进程处理时间上限为600ms,各个进程的数据交换也会限制在这个时间内。但是启动worker是有高延迟的,所以对worker池的优化是预热。仅在耗时的loader上使用。
          • 当项目较小时,多进程打包反而会使打包速度变慢
    • 将所有依赖模块打包到一个文件,js压缩会先将代码解析成AST语法树,然后再根据复杂的规则去分析和处理AST,最后将AST还原成js,这个过程涉及大量计算,所以会耗时且卡。
      • 优化压缩时间
      • webpack3启动打包时加上--optimize-minimize,webpack会自动为你注入一个带有默认配置的UglifyJSPlugin(webpack4已废弃)或:
      module.exports = {      
        optimization: {
            minimize: true,
        },
      }
      
      UglifyJSPlugin是单线程,ParallelUglifyJSPlugin插件实现了多进程压缩。它会开启多个子进程,把多个文件的压缩工作分配给多个子进程去完成,每个子进程其实还是通过UglifyJS去压缩代码,但变成了并行执行。
      • webpack4:默认内置使用terser-webpack-plugin插件压缩优化代码,而该插件使用terser来缩小js。
        为什么webpack选择terser?因为uglify-es不再维护,且uglify-js不支持es6。terser是uglify-es的一个分支,保留了uglify-es和uglify-js@3的API和cli兼容性。
        使用多进程并行运行来提高构建速度。并发运行的默认数量为os.cpu().lenght-1。即设置parallel为true。多进程可显著加快构建速度,推荐开启。
      module.exports = {
         optimization: {
           minimizer: [
             new TerserPlugin({
               parallel: true,
             }),
           ],
         },
       };
      
      
    • 二次打包,一点小修改就需要重新打包,而且所有文件都需要重新打包,需要浪费初次打包相同的时间,但很多文件没变更
      • 优化二次打包时间
        • 合理利用缓存(缩短连续构建时间,增加初始构建时间)
          • 使用缓存的几种方法有:cache-loader,HardSourceWepackPlugin或babel-loader的cacheDirectory标志。所有这些缓存方法都有启动的开销。重新运行期间在本地节省的时间很大,但是初始(冷)运行实际上会更慢。
          • cache-loader:在一些性能开销比较大的loader之前添加此loader,会将结果缓存到磁盘里,显著提升二次构建速度。保存和读取这些缓存文件有一些时间的开销,所以只针对性能开销较大的loader使用此loader。
          • HardSourceWebpackPlugin:第一次构建将花费正常的时间。第二次构建将显著加快(大概提升90%)

// 编译代码的基础配置
module.exports = {
  // ...
  module: {
    // 项目中使用的 jquery 并没有采用模块化标准,webpack 忽略它
    noParse: /jquery/,
    rules: [
      {
        // 这里编译 js、jsx
        // 注意:如果项目源码中没有 jsx 文件就不要写 /\.jsx?$/,提升正则表达式性能
        test: /\.(js|jsx)$/,
        // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
        use: ['babel-loader?cacheDirectory'],
        // 排除 node_modules 目录下的文件
        // node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换
        exclude: /node_modules/,
      },
    ]
  },
  resolve: {
    // 设置模块导入规则,import/require时会直接在这些目录找文件
    // 可以指明存放第三方模块的绝对路径,以减少寻找
    modules: [
      path.resolve(`${project}/client/components`), 
      path.resolve('h5_commonr/components'), 
      'node_modules'
    ],
    // import导入时省略后缀
    // 注意:尽可能的减少后缀尝试的可能性
    extensions: ['.js', '.jsx', '.react.js', '.css', '.json'],
    // import导入时别名,减少耗时的递归解析操作
    alias: {
      '@compontents': path.resolve(`${project}/compontents`),
    }
  },
};

参考: 玩转 webpack,使你的打包速度提升 90%