webpack性能优化

1,118 阅读4分钟

前言

webpack构建的产品大小会直接影响我们的线上页面性能,为此我总结了一下优化点。

针对webpack打包构建速度的优化见juejin.cn/post/698032…

Tree-shaking

Tree-shaking是一种基于ES module去删除死代码(没有被利用代码)的方式,其会在运行时静态的分析出模块的导入和导出关系,确定哪些字段没有被用到后将其删除。webpack会在production环境自动开启Tree-shaking。

Tree-shaking启动的条件

  • 使用ESM的规范去编写代码
  • 配置 optimization.usedExports 为 true,启动标记功能
    • 配置 mode = production

    • 配置 optimization.minimize = true

    • 提供 optimization.minimizer 数组

理论基础

例如commonjs这种规范是运行时加载,若是分析文件是否导入导出有较大的不确定性,而ES module是编译时进行引入的,其文件之间的依赖关系是可以确定的,所以编译工具只需要对代码进行静态分析就可以判断是否被其他模块使用。

原理

1.收集模块阶段。首先Tree-shaking会去收集模块导出的信息。 2.模块标记阶段。之后回去收集到的模块中去标记没有导出的模块。 3.代码生成及死代码删除阶段。webpack会根据导出值的使用情况,生成代码,后续Terser、UglifyJS插件会将这些导出却未被使用的代码删除。

防止tree-shaking失效的建议

  • 建议使用支持ES module的包 例如lodash的库,可以使用支持ESM的lodash-es包。

  • 使用 #pure 标注纯函数调用 在函数前标注/*#__PURE__*/,告诉webpack本次函数调用不会有影响上下文环境的副作用。

  • 优化文件导出的粒度

function a() {
   console.log(234)
}
function b() {
   console.log(123)
}
export default {
    a, b
}

如果在其他的文件引用只引用了这两个函数其中的一个,那我们需要按需引入即可,但是export default会将另一个未引入的给导出。

export {
    a, b
}
  • 禁止 Babel 转译模块导入导出语句 Babel 提供的部分功能特性会致使 Tree Shaking 功能失效,例如 Babel 可以将 import/export 风格的 ESM 语句等价转译为 CommonJS 风格的模块化语句,但该功能却导致 Webpack 无法对转译后的模块导入导出内容做静态分析,从而导致webpack无法进行tree shaking。

代码分包(code splitting)

为什么需要进行代码分包?

  • 资源过大,加载时间长: 在首屏渲染时,很多的JS文件可能运用不到,但是在初始化也进行了加载,会导致资源过大,页面首屏渲染时间过长。
  • 难以命中缓存资源: 当多文件打包在一起时,即使只有一个文件修改了东西,也回导致整个资源包无法命中缓存,从而重新加载。

优化途径

  • 多路由页面根据路由进行分包,降低首屏的渲染时间
  • 将第三方库进行分包: 当多个页面同时用到了一些第三方库,若将其打包到代码中会引起第三方库重复加载,所以我们可以将其从业务代码中剥离。
  • 限制分包的大小: 我们可以控制分包的maxSize的大小,最大限度的命中缓存,并配合HTTP2多路复用的特性,从而实现性能优化。

静态资源压缩

JS代码压缩

TerserWebpackPlugin:该插件使用 terser 来压缩 JavaScript。

// webpack.config.js
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
  },
};

css提取及压缩

mini-css-extract-plugin:这个插件将 CSS 提取到单独的文件中。它为每个包含 CSS 的 JS 文件创建一个 CSS 文件。它支持按需加载 CSS 和 SourceMap。

css-minimizer-webpack-plugin:这个插件可以对CSS文件进行压缩。

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  plugins: [new MiniCssExtractPlugin()],
  module: {
    rules: [
      {
        test: /.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
};

图片压缩

image-webpack-loader:这个插件可以对图片进行压缩和降质处理。

rules: [{
  test: /.(gif|png|jpe?g|svg)$/i,
  use: [
    'file-loader',
    {
      loader: 'image-webpack-loader',
      options: {
        mozjpeg: {
          progressive: true,
        },
        // optipng.enabled: false will disable optipng
        optipng: {
          enabled: false,
        },
        pngquant: {
          quality: [0.65, 0.90],
          speed: 4
        },
        gifsicle: {
          interlaced: false,
        },
        // the webp option will enable WEBP
        webp: {
          quality: 75
        }
      },
    },
  ],
}]

html压缩

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    plugins: [
        new HtmlWebpackPlugin({
        // ...
           minify: {
               removeComments: true, // 移除HTML中的注释 
               collapseWhitespace: true, // 删除空白符与换行符 
               minifyCSS: true // 压缩内联css 
           }
        })
    ]
}

减少第三方包不必要文件的打包

我们这里以moment.js为例,moment.js每次打包会带上自动引入所有的语言包,我们可以使用IgnorePlugin对不需要的那部分语言包文件夹进行忽略。

new webpack.IgnorePlugin(/^./locale$/, /moment$/)