Webpack 构建性能优化之旅

2,531 阅读5分钟

目前负责的项目,是未通过脚手架和其他框架搭建的 react 项目。开发过程中,启动项目和热更新速度都不是很理想;打包构建时,速度也比较缓慢且由于单个文件过大,用户在访问时,加载动画的时间也比较长。所以打算对其进行部分优化工作。 本着提升 webpack 性能,最直接的方式就是升级 webpack 版本的思路。对项目中的进行了一系列的升级和优化。升级和优化的结果如下: QQ截图20210528180306.png 编译速度方面:

  • 开发启动项目的速度提升了(非修改配置/初次编译)1180%;
  • 开发过程中代码热更新速度变化不大,但开发体验更好,浏览器更新进行局部更新而不是 reload;
  • 生产环境打包构建的速度提升了(非修改配置/初次编译)201%。

体积方面:

  • 项目中的两个大文件体积分别减少了 77%和 86% 左右,大大加快了页面的加载速度。

查看性能相关的插件:

  • speed-measure-webpack-plugin测量 plugin、loader 等时间;
  • webpack-bundle-analyzer打包分析工具。

根据官方迁移文档升级

  1. 升级Node.js ,至少 10.13.0 版本;
  2. 升级 webpack 4 及其相关的plugin/loader;
    • 升级 webpack 4 至最新版本,升级 webpack-cli 至最新版本;
    • 升级使用的 plugin 和 loader 为最新版本。
// 检查过时的依赖包
npm outdated
// 安装最新版本依赖包
npm install package@latest
  1. 获取堆栈信息中的弃用警告;
// 如果不存在默认的 webpack.config.js 配置,则需要 --config 指定配置文件
node --trace-deprecation node_modules/webpack/bin/webpack.js
  1. 测试 webpack5 兼容性 添加以下配置选项,检查构建是否正常运行(完成后删除这些配置选项)。
module.exports = {
  node: {
    Buffer: false,
    process: false
  },
}
  1. 升级至 webpack5
npm install webpack@latest -D
  1. babel 升级到7,升级指南:链接。以及相应的插件。 具体注意事项查看官方迁移指南:To v5 from v4

升级后调整和优化

调整

  1. 使用asset代替file-loader url-loader raw-loader
  2. JavaScript 代码压缩使用 webpack5 内置的terser-webpack-plugin

优化编译和开发速度

  1. cache: filesystem持久缓存。能够大大提升二次构建速度(修改配置文件除外)。
{
	cache: {
        type: "filesystem",
        buildDependencies: {
            // config 添加为 buildDependency,以便在改变 config 时获得缓存无效
            config: [__filename],
        },
        name: process.env.NODE_ENV,
        version: "1.0.0",
    },
}
  1. 使用thread-loader进行多进程构建。
// worker 启动有一定的消耗,
// 可以预热 worker 池
const jsWorkerPool = {
    workerParallelJobs: 80,
    poolTimeout: 2000,
};
const cssWorkerPool = {
    workerParallelJobs: 10,
    poolTimeout: 2000,
};

threadLoader.warmup(jsWorkerPool, ["babel-loader"]);
threadLoader.warmup(cssWorkerPool, ["css-loader", "less-loader"]);
  1. 开发时,自动保存代码导致构建频繁且会报错,又不想手动保存,则可以开启延迟构建。
devServer: {
  ...
  watchOptions: {
    // 延迟构建
    aggregateTimeout: 1500,
    ignored: /node_modules/,
  },
},
  1. css 模块热替换:css 借助style-loader,可以实现模块热替换;
  2. JavaScript 模块热替换:可以使用社区的库或插件实现,否则会通过 reload 的方式刷新。
    1. 如果符合以下条件,可使用[react-refresh-webpack-plugin](https://github.com/pmmmwh/react-refresh-webpack-plugin)插件;

QQ截图20210528173521.png

{
	plugins: [
  	new ReactRefreshWebpackPlugin()
  ]
}
  1. 若不符合上面的条件,可使用react-hot-loader
// .babelrc
{
    "plugins": [
      	...,
        "react-hot-loader/babel"
    ]
}
// index.js
import { hot } from 'react-hot-loader';
...
const App = hot(module)(() => ...);
render(<App />, document.getElementById('root'));

优化编译体积

打包后体积优化,主要是使用 Gzip 压缩,将打包后体积较大的文件使用 Gzip 压缩。项目中的两个大文件体积分别减少了 77%和 86% 左右,大大加快了页面的加载速度。 QQ截图20210528171118.png

Gzip 压缩

Gzip(GNU- ZIP) 是一种压缩技术。经过压缩的页面能大大加快浏览器端的加载速度。 启用 Gzip 需要服务器端和浏览器端都支持:服务器端压缩(提供压缩的文件),浏览器端解压。 注意:

  • 图片/mp3 等不必压缩,压缩率较小;
  • 使用 proxy_pass 反向代理页面,gzip——http_version 需要修改为 1.0;
  • 静态压缩:gzip_static on;
  • 动态压缩:gzip on。

其他

其他体积优化需要修改业务相关代码较多,就暂未进行优化。

遇到坑以及相关解决

  1. DeprecationWarning:[DEP_WEBPACK_COMPILATION_NORMAL_MODULE_LOADER_HOOK] DeprecationWarning: Compilation.hooks.normalModuleLoader was moved to NormalModule.getCompilationHooks(compilation).loader

speed-measure-webpack-plugin插件兼容性问题,目前还没有完全和 webpack5 兼容,可以在优化完成后移除相关配置。

  1. Error: Cannot find module 'webpack-cli/bin/config-yargs

    4.X版本的 webpack-cli 移除了yargs包,需要使用 webpack serve启动webpack-dev-server

// package.json
{
	"script": {
		"dev": "webpack serve --config webpack.dev.js"
	}
}
  1. Support for the experimental syntax 'classProperties' isn't currently enabled

@babel/preset-env不包含小于 Stage 3 的语法提案,需要手动安装相应插件。需要安装插件@babel/plugin-proposal-class-properties

//.babelrc
{
  "plugin":[
  	"@babel/plugin-proposal-class-properties"
  ]
}
  1. Support for the experimental syntax 'decorators-legacy' isn't currently enabled

原因同上,需要安装插件@babel/plugin-proposal-decorators

//.babelrc
{
  "plugin":[
  	["@babel/plugin-proposal-decorators", {"legacy": true}]
  ]
}
  1. expose-loader升级后,报错:ValidationError: Invalid options object. Expose Loader has been initialized using an options object that does not match the API schema.
// webpack 配置文件
module: {
	rules:[
  	...,
    {
    	use: {
    		loader: "expose-loader",
    		options: {
    				exposes: "videojs"
    		}
    	}
    }
  ]
}
// **.js 使用
require('videojs');
  1. webpack.HotModuleReplacementPluginhot: true共同使用时,无法热更新。

QQ截图20210528145936.png hot: true会自动添加webpack.HotModuleReplacementPlugin插件,无需再次添加配置。 7. speed-measure-webpack-plugin插件在 build 时,无法使用。 8. webpack5 不再为 Node.js 提供自动引入polyfills。

根据错误提示修改即可。

  1. 生产环境中构建完成,但是未退出命令

    thread-loader插件在生产环境中,预热 worker 池会导致此问题。

  2. 升级后,webpack 对代码要求更加严格,需要按需修改。

参考