lxf2023-04-20 08:56:02

前言

我的webpack打包好像有点慢,而且还被领导发现了 在一个历史悠久的项目中,由于功能不断增加,代码变得越来越庞大和臃肿,项目依赖关系复杂,导致启动和打包项目的时间变得越来越长。
webpack构建这个项目可以有多慢呢? 大概就是我在这个构建项目的时间里,我可以“早餐、泡茶、WC”,等我做完这些事之后,它也就差不多构建完了
尽管最初考虑使用微前端将项目拆分,但由于各种考虑,项目负责人否决了这种方案(害怕背锅)。 无奈,我们只好在Webpack构建方面下功夫,以提高项目构建的效率。接下来,主要介绍一些Webpack构建提效的技巧,也是面试被经常问到的一些内容

小建议

在做这些优化类的工作的时候,其实建议可以先去了解一下webpack构建的整个过程,在哪些环节上我们可以对应做什么优化的措施?如果对这个执行过程没有兴趣的也可以直接跳过,直接看后面的优化手段。

webpack的执行过程

下面是我了解到的一些webpack的一个运行流程(面试也经常会被问到),如有错误,帮忙指出一下 Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

  1. 初始化参数: 从配置文件和 shell 语句中读取与合并参数,得出最终的参数;
  2. 开始编译: 用上一步得到的参数初始化 compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
  3. 确定入口: 根据配置中的 entry 找出所有的入口文件;
  4. 编译模块: 从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  5. 完成模块编译:在经过第4步使用 Loader翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 chunk,再把每个 chunk 转换成6个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果;

webpack优化手段

1. 使用缓存

可以在 webpack 配置文件中使用 cache-loader 或者 hard-source-webpack-plugin 来缓存编译依赖项。

使用 cache-loader:

module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: ['cache-loader', 'babel-loader'],
      },
    ],
  },
};

使用 hard-source-webpack-plugin:

const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

module.exports = {
  plugins: [
    new HardSourceWebpackPlugin(),
  ],
};

2. 减少模块热重载

可以在 webpack 配置文件中对模块热重载的行为进行调整。

module.exports = {
  devServer: {
    hot: true,
    hotOnly: true,
  },
};

在 devServer 启用 hot 和 hotOnly,hot 热更新失败时刷新页面,hotOnly 只找到模块热更新失败不会刷新页面。
大部分项目中,node_modules的变换频率都是极低的,所以我们在使用watch功能的时候可以通过配置 ignored来忽略node_modules从而减少性能压力

"watchOptions": {
    "ignored": /node_modules/
}

3. 使用 Tree Shaking 和 Dead Code Elimination

可以使用 mode: 'production' 和 UglifyJSPlugin 来优化代码。

const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
  mode: 'production',
  optimization: {
    minimizer: [
      new UglifyJSPlugin(),
    ],
  },
};

4. 使用 Code Splitting 和动态加载模块

可以使用 import() 异步加载和 SplitChunksPlugin 进行代码分割。

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'async',
    },
  },
};

使用 chunks: 'async' 实现动态加载,只加载需要的模块。

import(/* webpackChunkName: "my-chunk-name" */ './my-module')
  .then(module => {
    // 使用模块
  })
  .catch(error => {
    // 处理错误
  });

也可以使用 React.lazy + Suspense 懒加载 React 组件

import React, { lazy, Suspense } from 'react';

const MyComponent = lazy(() => import('./MyComponent'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <MyComponent />
      </Suspense>
    </div>
  );
}

5. 优化 Loader

可以通过配置 options 对 Loader 进行优化。

module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
            },
          },
        ],
      },
    ],
  },
};

可以使用 HappyPack 通过多进程并行处理 Loader。

const HappyPack = require('happypack');
const os = require('os');

module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: ['happypack/loader'],
      },
    ],
  },
  plugins: [
    new HappyPack({
      loaders: ['babel-loader'],
      threads: os.cpus().length,
    }),
  ],
};

6. 使用外部资源

使用 externals 避免打包不必要的外部依赖项。

module.exports = {
  externals: {
    react: 'React',
    'react-dom': 'ReactDOM',
  },
};

7. 指定构建环境

使用 webpack --mode development 或 webpack --mode production 来指定构建环境。

也可以在 webpack 配置文件的 mode 中进行指定。

module.exports = {
  mode: 'development',
};

8. 优化 bundle 文件大小

使用 mini-css-extract-plugin 分离 CSS 文件。

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css',
    }),
  ],
};

使用 terser-webpack-plugin 压缩文件。

const TerserPlugin = require('terser-webpack-plugin');

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

9. DLL 模式

Webpack 中的 DLL(Dynamic Link Library)优化是一种基于缓存和分离应用程序的代码库的技术,它可以显著提高构建性能和开发体验。具体来说,DLL 技术可以将某些模块打包成一个 DLL 文件,该文件可以在多次构建中重复使用,从而减少打包时间和文件大小。

具体实现方法如下:

  1. 首先,在项目中新建一个 webpack.dll.js 配置文件用于打包 DLL 包。

  2. webpack.dll.js 中设置 entry,将需要提取出来的第三方库等代码,作为 entry 中的值。如下所示:

    entry: {
       vendors: ['react', 'react-dom', 'jquery']
    },
    
  3. 接着,在 plugins 中加入 webpack.DllPlugin,配置生成的 DLL 文件的名称、路径和映射关系,代码如下所示:

    const webpack = require('webpack');
    const path = require('path');
    
    module.exports = {
        entry: {
           vendors: ['react', 'react-dom', 'jquery']
        },
        output: {
            filename: '[name].dll.js',
            path: path.resolve(__dirname, './dll'),
            library: '[name]'
        },
        plugins: [
            new webpack.DllPlugin({
                name: '[name]',
                path: path.resolve(__dirname, './dll/[name].manifest.json')
            })
        ]
    }
    
  4. 运行 webpack.dll.js 配置文件,生成 DLL 文件和 manifest 文件。

  5. 在正常的项目构建中,通过 webpack.DllReferencePlugin 引入 DLL 文件,加速构建时间。如下所示:

    const webpack = require('webpack');
    const path = require('path');
    
    const dllPath = path.resolve(__dirname, './dll');
    
    module.exports = {
        entry: './src/index.js',
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, './build')
        },
        plugins: [
            new webpack.DllReferencePlugin({
                context: process.cwd(),
                manifest: require(`${dllPath}/vendors.manifest.json`)
            })
        ]
    }
    
  6. 最后,运行 npm run build 打包项目即可

使用 DLL 技术可以显著减少构建时间和文件大小,尤其是在重复构建时,可以避免重复打包第三方库等代码。因此,在复杂的项目中,DLL 技术是一个非常实用的优化手段

10. gzip

开启 Gzip 压缩可以大幅度减小文件大小,提高网络传输速度,加快网站的加载速度,而在 Webpack 中开启 Gzip 压缩的方式如下:

  1. 安装 compression-webpack-plugin 插件:

    npm install compression-webpack-plugin --save-dev
    
  2. 在 Webpack 配置文件中引入该插件,代码如下所示:

    const CompressionWebpackPlugin = require('compression-webpack-plugin');
    
    module.exports = {
      // ...
      plugins: [
        new CompressionWebpackPlugin({
          filename: '[path][base].gz',
          algorithm: 'gzip',
          test: /.(js|css|html)$/,
          threshold: 10240,
          minRatio: 0.8
        })
      ]
    };
    

    上述插件的配置项含义如下:

    • filename:Gzip 压缩后的文件名称。
    • algorithm:Gzip 压缩算法。
    • test:需要压缩的文件类型。
    • threshold:当文件超过指定大小时才会被压缩。在本例中,只有当文件大于等于 10KB 时才被压缩。
    • minRatio:只有当压缩之后的文件大小比原始文件减少了一定比例时,才会被保留。在本例中,只有当压缩之后的文件大小比原始文件减少了 80% 以上时,才会被保留。
  3. 在 Webpack 配置文件中开启 Gzip 压缩,代码如下所示:

    module.exports = {
      // ...
      devServer: {
        compress: true,
      },
    };
    

    设置 devServer 配置项的 compress 属性为 true,即可启用 Gzip 压缩

总结

Webpack的优化配置在不同的版本中可能会有所不同。上文中提到的优化方法的目的并不是让大家直接拿来使用,而是让大家了解有哪些可行的优化手段,在实际使用时能够根据所使用的Webpack版本进行相应的配置优化。

当进行优化时,最重要的是明确优化方向。一旦确定了可以优化的方向,具体的实现方法就会呼之欲出,即使你现在不知道如何实现,也可以通过官网、谷歌、百度等途径找到答案。

探索新的工具和方法:通过探索新的工具和技术,你可以提高自己的效率,并且在团队中分享这些知识,帮助别人提高工作效率。

提供有价值的反馈:当你发现存在问题或者有改进的地方,提供有价值的反馈并提出提高效率的建议可以让你在团队中更有影响力

本文正在参加「」