webpack打包优化 ---- 1

700 阅读6分钟

之前面试被问到webpack打包优化,回答不怎么好,一直说总结了补充在【千人虐】面试总结---1中,后来总结下来发现内容太多,所以还是单独开篇记录下吧

我个人认为优化主要分为两个方面

  • 提高webpack构建速度
  • 缩小webpack打包后文件大小

1 提高webpack构建速度

提高webpack构建速度更多是对于开发环境的需求,减少启动等待时间,之前遇到过大项目,启动时间要六分多钟,着实难受。

1.1 webpack打包流程

整个打包流程我们可以将其理解为一个函数,配置文件则是其参数,传入合理的参数后,运行函数就能得到我们想要的结果。 大致流程如下:

  • 初始化参数:解析webpack配置参数,合并shell传入和webpack.config.js文件配置的参数,形成最后的配置结果。
  • 开始编译:上一步得到的参数初始化compiler对象,注册所有配置的插件,插件监听webpack构建生命周期的事件节点, 做出相应的反应,执行对象的 run 方法开始执行编译。
  • 确定入口:从配置的entry入口,开始解析文件构建AST语法树,找出依赖,递归下去。
  • 编译模块:递归中根据文件类型和loader配置,调用所有配置的loader对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
  • 完成模块编译并输出:递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据entry配置生成代码块chunk。
  • 输出完成:输出所有的chunk到文件系统。

1.2 如何提升构件速度

  • 区分开发环境和生产环境区分开发环境和生产环境,上面提到我们需要区分开发环境和生产环境,在WebPack4.x中非常贴心的为我们引入了mode配置项,通过指定production 或 development来区分是开发环境还是生产环境。当我们把mode配置为development,那么WebPack会默认开启debug工具,当我们编译有错误的时候,提供良好的报错提示。打包的时候也会直接采用增量编译,而不是覆盖更新,这样我们可以节省大量编译时间。当我们把mode配置为production,则会默认开启运行时的性能优化以及构建结果优化。

  • 减少不必要的编译,我们在使用loader处理文件的时候,应该尽量把文件范围缩小,对于一些不需要处理的文件直接忽略。这里我们以babel-loader为例,看一下如何处理:

      module: {
        rules: [
          {
            //处理后缀名为js的文件
            test: /\.js$/, 
            //exclude去掉不需要转译的第三方包 && 或者这里使用include去声明哪些文件需要被处理
            exclude: /(node_modules|bower_components)/,
            //babel的常用配置项
            use: {
              loader: 'babel-loader',
              options: {
                presets: ['@babel/preset-env'],
                //缓存设置为开启
                cacheDirectory: true
              }
            }
          }
        ]
      }
      
      // 这里我们对于不需要处理的第三方包直接使用 exclude 属性排除在外,    
         // 或者需要处理的文件使用 include 属性去包含,此外上面我们在 options 配置当中增加了 cacheDirectory: true,
          //这样对于转译结果就可以直接缓存到文件系统当中,在我们下次需要的时候直接到缓存当中读取即可。
    
  • 使用模块热替换(HMR),传统的如果我们没有配置模块热替换,则需要每次刷新整个页面,效率很低。而使用模块热替换之后,我们只需要重新编译发生变化的模块,不需要编译所有模块,速度上面大大提高。具体配置方法如下:

        module.exports = {
          ......
          plugins: [
            new webpack.HotModuleReplacementPlugin(), // 引入模块热替换插件
          ],
          devServer: {
            hot: true // 开启模块热替换模式
          }
        }
    
  • 由于现在的项目都会引用大量的第三方包,这些包基本都是不会变的,我们完全把他们打包到单独的文件当中,这就涉及到了公共代码的提取,之前Webpack3.x中使用的是CommonsChunkPlugin插件,后来Webpack升级到4.x之后使用的则是optimization.splitChunks。下面我们来分别介绍一下这2个插件的使用:

    • CommonsChunkPlugin配置
        new webpack.optimize.CommonsChunkPlugin({
            // 指定该代码块的名字
            name: "framework",
            // 指定输出代码的文件名
            filename: "framework.js",
            // 指定最小共享模块数
            minChunks: 4,
            // 指定作用于哪些入口
            chunks: ["pageA", "pageB""pageC"]
        })
    
    • optimization.splitChunks配置
        optimization: {
          splitChunks: {
             //设置那些代码用于分割
              chunks: "all",
             // 指定最小共享模块数(与CommonsChunkPlugin的minChunks类似)
              minChunks: 1,
             // 形成一个新代码块最小的体积
              minSize: 0,
              cacheGroups: {
                  framework: {
                      test: /react|lodash/,
                      name: "vendor",
                      enforce: true
                  }
              }
          }
      }
      //cacheGroups对象,定义了需要被抽离的模块,其中test属性是比较关键的一个值,他可以是一个字符串,也可以是正
      //则表达式,还可以是函数。如果定义的是字符串,会匹配入口模块名称,会从其他模块中把包含这个模块的抽离出来。
      //name属性是要缓存的分离出来的chunk名称。
    
  • 使用插件DLLPlugin
    该插件它用来分离我们在程序调用中反复会用到的代码。其实我上面说到的CommonsChunkPlugin和optimization.spli tChunks和DLLPlugin是一类插件,但是DLLPlugin更加智能,配置过程也更复杂。

    • 配置动态链接库:首先需要为动态链接库单独创建一个 Webpack 配置文件,这里我们叫做 webpack.vendor.config.js。该配置对象需要引入 DLLPlugin,其中的 entry 指定了把哪些模块打包为 vendor。
           const path    = require('path');
           const webpack = require('webpack');
           module.exports = {
             entry: {
                 //提取的公共文件
                 vendor: ['react','lodash',] 
             },
             output: {
               path: path.resolve('./dist'),
               filename: 'vendor.js',
               library: '[name]_library'
             },
             plugins: [
               new webpack.DllPlugin({
                 path: path.resolve('./dist', '[name]-manifest.json'),
                 name: '[name]_library'
               })
             ]
           };
      
    • 打包动态链接库并生成 vendor 清单:使用该配置文件进行打包。会生成一个 vendor.js 以及一个资源的清单文件manifest.json,在内部每一个模块都会分配一个 ID。
    • 将 vendor 连接到项目中:最后在工程的 webpack.config.js 中我们需要配置 DllReferencePlugin 来获取刚刚打包出来的模块清单。这相当于工程代码和 vendor 连接的过程。
          module.exports = {
            plugins: [
              new webpack.DllReferencePlugin({
                // 指定需要用到的 manifest 文件
                manifest: path.resolve(__dirname, 'dist/manifest.json'), 
              }),
            ],
          }
      
    • 最后不要忘了需要在HTML文件当中正确引入我们打包好的vendor.js文件。
  • 最后不要忘了需要在HTML文件当中正确引入我们打包好的vendor.js文件。

另一部分文件质量优化在另一篇文章中输出。。。。