为什么要升级?

webpack4用的好好的,运行稳定,为什么要升级到webpack5, 每次升级,都要经历一场地震,处理许多loader和plugin API的破坏性改变。 请给我们一个充分的升级理由,不然真的没有动力去折腾。没问题,给你们一个充分的理由,webpack5对构建速度做了突破性的改进,开启文件缓存之后,再次构建,速度提升明显。在我参与的项目中,本地服务器开发环境,第一次构建速度是38.64s,第二次构建速度是1.69s,提升了一个数量级。My God, 是不是很惊喜,很意外。

生产打包构建速度,同样有显著提升,第一次打包耗时1.01m,第二次打包耗时10.95s.  看到这里,你是不是有了升级的热情,那请继续往下看。

为什么构建速度有了质的飞跃?

主要是因为:

1.webpack4是根据代码的结构生成chunkhash,添加了空白行或注释,会引起chunkhash的变化,webpack5是根据内容生成chunkhash,改了注释或者变量不会引起chunkhash的变化,浏览器可以继续使用缓存。

2.优化了对缓存的使用效率。在webpack4 中,chunkId与moduleId都是自增id。只要我们新增一个模块,那么代码中module的数量就会发生变化,从而导致moduleId发生变化,于是文件内容就发生了变化。chunkId也是如此,新增一个入口的时候,chunk数量的变化造成了chunkId的变化,导致了文件内容变化。所以对实际未改变的chunk文件不能有效利用。webpack5采用新的算法来计算确定性的chunkId和moduleId。可以有效利用缓存。在production模式下,optimization.chunkIds和optimization.moduleIds默认会设为’deterministic’。

3.新增了可以将缓存写入磁盘的配置项, 在命令行终止当前构建任务,再次启动构建时,可以复用上一次写入硬盘的缓存,加快构建过程。

这两项的默认配置为:

module.exports = (env) => {
return {
splitChunks: {
chunks: 'async', // 指明要分割的插件类型, async:异步插件(动态导入),inital:同步插件,all:全部类型
minSize: 20000, //文件最小大小,单位bite;即超过minSize有可能被分割;
minRemainingSize: 0, // webpack5新属性,防止0尺寸的chunk
minChunks: 1, // 被提取的模块必须被引用1次
maxAsyncRequests: 30, // 异步加载代码时同时进行的最大请求数不得超过30个
maxInitialRequests: 30, // 入口文件加载时最大同时请求数不得超过30个
enforceSizeThreshold: 50000,
cacheGroups: {
// 分组缓存
// 将来自node_modules的模块提取到一个公共文件中 (由v4的vendors改名而来)
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};

开启升级之旅

webpack每个大版本的升级,都是破坏性变革,很少向后兼容,webpack4到webpack5的升级,同样也不例外。升级犹如去西天取经一样,需要经过九九八十一难,才能取得真经,体会到成就感。只要没有坚持到最后,就会前功尽弃。所以一定要有耐心。好了,废话不多说。现在进入这个章节的主题,细数一下升级过程中踩过的各种坑。

我对webpack的升级之旅是这样开始的, 直接在webpack4的webpack.config.js添加与提升构建速度有关的配置

module.exports = () => {
return {
// ...
optimization: {
// 此设置保证有新增的入口文件时,原有缓存的chunk文件仍然可用
moduleIds: "deterministic",
// 值为"single"会创建一个在所有生成chunk之间共享的运行时文件
runtimeChunk: "single",
splitChunks: {
// 设置为all, chunk可以在异步和非异步chunk之间共享。
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all",
},
},
},
},
cache: {
// 将缓存类型设置为文件系统,默认是memory
type: "filesystem",
buildDependencies: {
// 更改配置文件时,重新缓存
config: [__filename],
},
},
};
};

报如下错误,webpack4 optimization.moduleIds不能设置为deterministic。

于是对webpack4进行升级, 从"webpack": "^4.39.1"升级到"webpack": "^5.36.1",,升级后,启动编译,报如下错误 configuration.devtool should match pattern "^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$"

将devtool的配置 由devtool: 'cheap-module-eval-source-map'改为devtool: 'eval-cheap-module-source-map', 继续前行,编译报如下错误:

升级 "html-webpack-plugin": "^3.2.0"到"html-webpack-plugin": "^5.3.1",继续前行,编译报如下错误:Cannot read property 'normal' of undefined

这次既有警告,又有报错,经查告警和报错是由于webpack5的API发生改变,而基于webpack4 API开发的一些node工具包还未同步变更, 版本与webpack5不兼容引起的,头痛医头,脚痛医脚,会事倍功半,不胜其烦。于是决定放大招,升级package.json中所有的开发时依赖到最新版本。

yarn upgrade-interactive --latest

对标红的开发依赖包进行升级后,继续前行,编译报如下错误 Cannot find module 'webpack-cli/bin/config-yargs'

经查,是因为webpack-cli4移除了yargs模块,除了要注释掉项目中对yargs模块的引用,还要修改package.json里面webpack-dev-server的写法, 将'webpack-dev-server'改为'webpack serve'。

    "start:local": "cross-env NODE_ENV=development webpack-dev-server --config webpack/dev.js --progress --mode development --current-env local",
"start:dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack/dev.js --progress --mode development --current-env dev",
"start:test": "cross-env NODE_ENV=development webpack-dev-server --config webpack/dev.js --progress --mode development --current-env test",
"start:prod": "cross-env NODE_ENV=development webpack-dev-server --config webpack/dev.js --progress --mode development --current-env prod",
    "start:local": "cross-env NODE_ENV=development webpack serve --config webpack/dev.js --progress --mode development --current-env local",
"start:dev": "cross-env NODE_ENV=development webpack serve --config webpack/dev.js --progress --mode development --current-env dev",
"start:test": "cross-env NODE_ENV=development webpack serve --config webpack/dev.js --progress --mode development --current-env test",
"start:prod": "cross-env NODE_ENV=development webpack serve --config webpack/dev.js --progress --mode development --current-env prod",

改完之后,继续前行,编译报如下错误 Unknown options

经查是因为webpack-cli的参数写法不对,于是按照官方文档修改为

    "start:local": "cross-env NODE_ENV=development webpack serve --config webpack/dev.js --progress --mode development  --env currentEnv=local",
"start:dev": "cross-env NODE_ENV=development webpack serve --config webpack/dev.js --progress --mode development --env currentEnv=dev",
"start:test": "cross-env NODE_ENV=development webpack serve --config webpack/dev.js --progress --mode development --env currentEnv=test",
"start:prod": "cross-env NODE_ENV=development webpack serve --config webpack/dev.js --progress --mode development --env currentEnv=prod",

获取命令行自定义参数的写法改为

module.exports = (env) => {
const currentEnv = env.currentEnv;
//...
}

改完之后,继续前行,编译报如下错误:TypeError: merge is not a function

经查是最新版本的webpack-merge的merge导出方式有问题,修改merge的导出方式为

const { merge } = require('webpack-merge');

改完之后,继续前行,编译报如下错误: this.getOptions is not a function

经查是less-loader的配置写法导致的, 按照最新版本的配置写法,修改less和module.less加载器的配置

const lessLoader = [
"css-loader",
"postcss-loader",
{
loader: "less-loader",
options: { lessOptions: { javascriptEnabled: true } },
},
]; module.exports = () => {
return {
// ...
module: {
rules: [
{
test: lessReg,
exclude: lessModuleReg,
use: isDev
? ["style-loader", ...lessLoader]
: [MiniCssExtractPlugin.loader, "happypack/loader?id=less"],
},
{
test: lessModuleReg,
exclude: path.resolve(__dirname, "./node_modules"),
// include: [path.resolve(__dirname, '../src')],
use: isDev
? ["style-loader", ...lessLoader]
: [
MiniCssExtractPlugin.loader,
"happypack/loader?id=lessWithModule",
],
},
],
},
};
};

继续前行,编译有如下警告: consider using [chunkhash] or [contenthash]

将项目配置中用到hash的地方,修改成contenthash

module.exports = () => {
return {
// ...
output: {
path: path.resolve(rootPath, "./dist"),
filename: isDev
? "js/[name].[contenthash:8].js"
: "js/[name].[chunkhash:8].js",
publicPath,
},
module: {
rules: [
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.ico$/],
loader: "url-loader",
options: {
limit: 10000,
name: isDev
? "image/[name][contenthash:8].[ext]"
: "image/[name].[contenthash:8].[ext]",
},
},
{
// 添加otf字体支持
test: /\.(woff|svg|eot|ttf|otf)\??.*$/,
loader: "url-loader",
options: {
limit: 10000,
name: isDev
? "font/[name][contenthash:8].[ext]"
: "font/[name].[contenthash:8].[ext]",
},
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: isDev
? "css/[name][contenthash:8].css"
: "css/[name].[chunkhash:8].css",
chunkFilename: isDev
? "css/[id][contenthash:8].css"
: "css/[id].[chunkhash:8].css",
ignoreOrder: false,
}),
],
};
};

修改完之后,本地开发环境终于不报错了。可是发现修改代码之后页面不自动刷新。经查是webpack5的bug, 如果在 package.json 里面写了 browserslist,会导致热更新失效,解决方案是在 webpack 配置中设置 target 字段,在开发阶段使得 browserslist 失效

module.exports = (env) => {
return {
// ...
target: process.env.NODE_ENV === "development" ? "web" : "browserslist",
};
};

再看看生产编译打包是否正常。

执行yarn build:prod之后,报如下错误 MainTemplate.hooks.hashForChunk is deprecated,这个报错前面遇到过,一看就是生产模式用到的不同于开发模式的插件,与webpack5不兼容导致的。

经查解决方案是用 terser-webpack-plugin替换原来的js压缩插件uglifyjs-webpack-plugin

const TerserPlugin = require('terser-webpack-plugin'); // 对js进行压缩

module.exports = () => {
return {
// ...
optimization: {
minimize: true,
minimizer: [
// terserPlugin是webpack推荐及内置的压缩插件,cache与parallel默认为开启状态
// 缓存路径在node_modules/.cache/terser-webpack-plugin
new TerserPlugin({
terserOptions: {
// https://github.com/terser/terser#minify-options
compress: {
warnings: false, // 删除无用代码时是否给出警告
drop_debugger: true, // 删除所有的debugger
// drop_console: true, // 删除所有的console.*
pure_funcs: [''],
// pure_funcs: ['console.log'], // 删除所有的console.log
},
},
}),
new CssMinimizerPlugin(),
],
},
};
};

改完之后,编译报许多如下错误: You forgot to add 'mini-css-extract-plugin'

经查是因为webpack5中,happypack不再支持less-loader,修改配置文件,less-loader不开启多进程编译

module.exports = () => {
return {
// ...
module: {
rules: [
{
test: lessReg,
exclude: lessModuleReg,
// use: isDev ? ['style-loader', ...lessLoader] : ['happypack/loader?id=less'],
use: isDev
? ["style-loader", ...lessLoader]
: [MiniCssExtractPlugin.loader, ...lessLoader],
},
{
test: lessModuleReg,
exclude: path.resolve(__dirname, "./node_modules"),
// include: [path.resolve(__dirname, '../src')],
// use: isDev
// ? ['style-loader', ...lessLoader]
// : ['happypack/loader?id=lessWithModule'],
use: isDev
? ["style-loader", ...lessLoader]
: [MiniCssExtractPlugin.loader, ...lessLoader],
},
],
},
plugins: [
// new Happypack({
// id: 'less',
// threadPool: happyThreadPool,
// use: [MiniCssExtractPlugin.loader, ...lessLoader],
// }),
// new Happypack({
// id: 'lessWithModule',
// threadPool: happyThreadPool,
// use: [MiniCssExtractPlugin.loader, ...lessLoader],
// }),
],
};
};

修改之后,继续编译,报如下错误:Module not found: Error: Can't resolve 'crypto'

经查webpack4 引入crypto-js模块会自动引入polyfill: crypto-browserify, webpack5默认会自动将path、crypto、http、stream、zlib、vm的node polyfill剔除,为了不影响之前的业务,我们手动添加这个工具包

yarn add -D crypto-browserify
module.exports = () => {
return {
// ...
resolve: {
fallback:{
"stream": false,
"buffer": false,
"crypto": require.resolve("crypto-browserify")
}
},
};
};

改完之后,编译报如下警告: Conflicting values for 'process.env'

经查是webpack5 定义全局变量的写法改变了,按照最新的语法修改如下:

module.exports = () => {
return {
// ...
plugins: [
// webpack5 定义环境变量的写法变了
new webpack.DefinePlugin({
"process.env.WX_JS_SDK_ENABLED": WX_JS_SDK_ENABLED,
"process.env.CURRENT_ENV": JSON.stringify(currentEnv),
"process.env.RELEASE_VERSION": JSON.stringify(RELEASE_VERSION),
}), // webpack4的写法
// new webpack.DefinePlugin({
// "process.env": {
// WX_JS_SDK_ENABLED: WX_JS_SDK_ENABLED, // 是否真机调试SDK模式
// CURRENT_ENV: JSON.stringify(currentEnv),
// RELEASE_VERSION: JSON.stringify(RELEASE_VERSION),
// },
// }),
],
};
};

修改完之后,编译报如下错误:optimizeChunkAssets is deprecated

经查是optimize-css-assets-webpack-plugin插件与webpack5不兼容引起的警告,webpack5中同等功能的插件是css-minimizer-webpack-plugin,安装并修改配置

yarn add -D css-minimizer-webpack-plugin
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); // 对CSS进行压缩
module.exports = () => {
return {
// ...
optimization: {
minimize: true,
minimizer: [
// ...
// new OptimizeCSSAssetsPlugin(),
new CssMinimizerPlugin(),
],
},
};
};

改好之后,编译报如下错误 complier.plugin is not a function

经查是webpack-cos-plugin插件报的错, Webpack5 发布后,各大主流 plugin 都已经相继适配webpack5新的plugin api, 而webpack-cos-plugin最新的版本是两年前的,近期没有做过维护,看完官网文档后,手动修复一下

compiler.hooks.emit.tap('WebpackQcloudCOSPlugin', (compilation) => {
var files = _this.pickupAssetsFiles(compilation);
log('' + green('\nCOS 上传开始......'));
_this
.uploadFiles(files, compilation)
.then(function () {
log('' + green('COS 上传完成\n'));
})
.catch(function (err) {
log(red('COS 上传出错') + '::: ' + red(err.code) + '-' + red(err.name) + ': ' + red(err.message));
_this.config.ignoreError || compilation.errors.push(err);
});
});

然后在Linux机器上部署打包编译时,用修改之后的文件替换node_modules下的同名文件

\cp  -rf webpack/cos/index.js node_modules/webpack-cos-plugin/lib

运行打包命令,这次终于可以正常打包上传了,可是发现,打包之后的文件,页面中有些图片展示不出来,经查,未加载出来的图片,src的值是[object Module]


通过样式名查找,发现代码中凡是通过require给图片的src属性赋值的图片都加载不出来

<img src="require('assets/xxx.png')"/>

原因是url-loader最新版本默认情况下会把require引入的内容当做esModules去处理,而不是解析内容本身,所以要关闭默认解析方式。

module.exports = (env) => {
return {
// ...
module: {
rules: [
{
test: /\.(png|jpe?g|gif|ico|bmp)$/i,
use: [
{
loader: 'url-loader',
options: {
esModule: false, // 增加这一句
limit: 10000,
name: isDev ? 'image/[name][hash:8].[ext]' : 'image/[name].[contenthash:8].[ext]',
},
},
],
},
],
},
}
}

至此,大功告成。本地开发和生产打包所有的升级报错问题都已解决。可以愉快地享受webpack5带来全新打包体验。

参考文章

  • https://stackoverflow.com/questions/59070216/webpack-file-loader-outputs-object-module
  • https://stackoverflow.com/questions/64557638/how-to-polyfill-node-core-modules-in-webpack-5
  • https://webpack.js.org/api/cli/#env
  • https://webpack.docschina.org/blog/2020-10-10-webpack-5-release/
  • https://www.npmjs.com/package/webpack-cos-plugin
  • https://blog.csdn.net/qq_36741436/article/details/78732201
  • https://webpack.js.org/api/plugins/#plugin-types

Webpack5构建速度提升令人惊叹,早升级早受益的更多相关文章

  1. Next.js 7发布,构建速度提升40%

    Next.js团队发布了其开源React框架的7版本.该版本的Next.js主要是改善整体的开发体验,包括启动速度提升57%.开发时的构建速度提升40%.改进错误报告和WebAssembly支持. \ ...

  2. vue-cli3使用 DllPlugin 实现预编译,提升构建速度

    在项目打包上有两个目标:减少打包代码体积和加快打包速度 1. 减少打包体积: (1)对于用的比较少的库,可以去掉(我去掉了jquery以及lodash),用到的地方,参考源码自己写 (2)非用不可的又 ...

  3. Docker实用技巧之更改软件包源提升构建速度

    一.开篇 地球,中国,成都市,某小区的阳台上,一青年负手而立,闭目沉思,阵阵的凉风吹得他衣衫呼呼的飘.忽然,他抬起头,刹那间,睁开了双眼,好似一到精光射向星空,只见这夜空......一颗星星都没有.他 ...

  4. webpack 提升90%的构建速度 HardSourceWebpackPlugin

    HardSourceWebpackPlugin 插件 不能提升第一次构建的速度,但对于第二次构建能提升99%的构建速度 第一次构建: 第二次: 提升了..,算不出来,反正就是很多啦~~~ npm in ...

  5. [转]20个令人惊叹的桌面Docker容器

    大家好,今天我们会列出一些运行在Docker容器中的很棒的桌面软件,我们可以在自己的桌面系统中运行它们.Docker 是一个开源项目,提供了一个可以打包.装载和运行任何应用的轻量级容器的开放平台.它没 ...

  6. Android 优化APP 构建速度的17条建议

    转载:http://www.jianshu.com/p/a1cc8f2e0877 较长的构建时间将会减缓项目的开发进度,特别是对于大型的项目,app的构建时间长则十几分钟,短则几分钟,长的构建时间已经 ...

  7. 记——加快gradle 构建速度的经验

    Gradle作为一个新的构建系统,无疑在灵活,扩展,跨平台等各方面都表现得非常优秀,然而,它也有一点备受吐槽,就是速度慢.以下为本人使用gradle过程中,几次加快gradle构建速度的经验之谈. 本 ...

  8. 20个令人惊叹的深度学习应用(Demo+Paper+Code)

    20个令人惊叹的深度学习应用(Demo+Paper+Code) 从计算机视觉到自然语言处理,在过去的几年里,深度学习技术被应用到了数以百计的实际问题中.诸多案例也已经证明,深度学习能让工作比之前做得更 ...

  9. 如何将 iOS 工程打包速度提升十倍以上

    如何将 iOS 工程打包速度提升十倍以上   过慢的编译速度有非常明显的副作用.一方面,程序员在等待打包的过程中可能会分心,比如刷刷朋友圈,看条新闻等等.这种认知上下文的切换会带来很多隐形的时间浪费. ...

随机推荐

  1. POJ_2253 Frogger 【最短路变形】

    一.题目 Frogger 二.分析 题意关键点就是那个青蛙距离.就是所有1到2的点的路径中,每条路径都可以确定一个最大值,这个最大值就是青蛙要跳的青蛙距离,然后要求这个青蛙距离最小值. 其实就是最短路 ...

  2. SQL排名问题,100% leetcode答案大公开!

    (首先原谅我最近新番看多了,起了一个中二的名字) 最近在找实习,所以打算系统总结(复习)一下sql中经常遇到问题.不管是刷leetcode还是牛客的sql题,有一个问题总是绕不开的,那就是排名问题.其 ...

  3. dll远线程注入

    原理 核心函数 CreateRemoteThread:让在其他进程中创建一个线程变成可能 核心思想 HANDLE WINAPI CreateRemoteThread( __in HANDLE hPro ...

  4. PAT (Advanced Level) Practice 1046 Shortest Distance (20 分) 凌宸1642

    PAT (Advanced Level) Practice 1046 Shortest Distance (20 分) 凌宸1642 题目描述: The task is really simple: ...

  5. JS实现鼠标点击爱心&绘制多边形&每日一言功能

    本篇文章主要介绍我的个人博客 程序猿刘川枫 中页面使用的美化功能(基于JS实现): 1.鼠标点击出现不同颜色爱心特效 2.页面浮动多边形跟随鼠标移动 3.每日一言功能 1.鼠标点击出现爱心特效 经常在 ...

  6. linux日志文件说明

    /var/log/message 系统启动后的信息和错误日志,是Red Hat Linux中最常用的日志之一 /var/log/secure 与安全相关的日志信息 /var/log/maillog 与 ...

  7. C语言const是如何保证变量不被修改的?

    这小段文章要理清楚的是,在C语言中,$const$是如何保证变量不被修改的? 我们可以想到两种方式: 第一种,由编译器来阻止修改$const$变量的语句,让这种程序不能通过编译: 第二种,由操作系统来 ...

  8. SVN讲解

    1.SVN是什么? 代码版本管理工具 它能记住你每次的修改 查看所有的修改记录 恢复到任何历史版本 恢复到已经删除的文件 2.SVN和Git相比,有什么优势? 使用简单,上手快 git没有目录级权限控 ...

  9. 以Aliyun体验机为例,从零搭建LNMPR环境(下)

    使用云服务器搭建 Web 运行环境,尤其是搭建常见的 LNMPR(Linux+Nginx+MySQL+PHP+Redis) 环境,对于开发人员是必备的职场基本技能之一.在这里,借着搭建我的" ...

  10. 强大的工具(一):Capslock+ 2.x版本

    2020.07.09 更新 作者更新了3.x版本,因此更新了3.x版本的博客,可以戳这里. 本篇文章介绍的是2.x版本. 1 Capslock+简介 Capslock+利用了键盘少用的Capslock ...