10分钟带你升级webpack5

8,175 阅读8分钟

前言

最近很多小伙伴们都在问vite都出来了,webpack还有必要升级到5么?vite是不是很快就要取代webpack了?

针对这个问题,我个人观点是:适合你项目的构建工具就是最好的。当前前端的构建工具有很多种,从早期的gulp、再到ES时代的rollupwebpack等;每种构建工具都有自己的不同特性,主要是看公司以及你的团队所需,不一定有了新工具出来我们就一定要立马去学习,立马抛弃生产环境正在跑的工具,经历过大浪的洗礼,最终活下来的才是真正值得我们学习的

在上一篇文章你不得不知道的webpack性能优化中,我们主要针对webpack4如何去做打包构建的优化,距离webpack5的发布已经有大半年了,相信有很多小伙伴们已经在自己的项目中升级了webpack5,也体验到了webpack5打包构建时带来的飞一般的感觉。

stock-photo-214709789.jpg

webpack4跟webpack5构建效率对比

webpack4构建的项目地址:github.com/Paulinho89/…

webpack5构建的项目地址:github.com/Paulinho89/…

我们来两张打包的速度截图比对一下:

webpack4
image.png webpack5 image.png

两个不同的项目,但是同样的打包文件,图1是使用webpack4构建、图2是使用webpack5构建。在第三次构建之后的时间对比可以发现,使用webpack4需要2278ms,而使用webpack5仅仅需要550ms,相差了近4倍;没错,这就是webpack5的最大魅力所在,尤其是在大型复杂项目中,webpack5在构建效率的提升上会更加明显;

踩坑指南

笔者也在近期将项目升级到了webpack5,因为项目的脚手架手动搭建,没有基于官方的cli,所以在升级的过程中难免踩了不少坑,这篇文章主要跟大家分享一下踩坑历程。

package.json中的插件升级

既然是从webpack4升级到webpack5,相信很多小伙伴第一时间可能先会想到直接把webpackwebpack-cli升级到最新的版本不就好了。

"webpack": "^5.38.1",
"webpack-cli": "^4.7.0"

如果你只是单纯的这样手动的去升级包的话,很快你就会遇到各种各样的loaderplugin语法报错;

所以为了减少升级的成本,所以不建议通过手动去升级包的版本,那有没有一键升级包的工具呢?答案肯定是有的。

npm-check-updates

  1. 安装
cnpm install -g npm-check-updates
  1. package.json的跟目录下执行npm-check-updates
npm-check-updates

image.png

  1. 执行ncu -u检查package.json文件
ncu -u

image.png 你会发现package.json里面的依赖版本号已经变成最新版本

  1. 执行npm install下载最新的依赖
npm install

loader跟plugin配置升级

经过上述package.json中的插件一键升级以后,我们再次尝试执行npm run build:prod命令打包试试,发现有报错。

image.png

webpack4对比后发现,webpack5中最新的merge需要使用解构的方式来配置。

webpack4

const Merge = require('webpack-merge');

module.exports = WebpackMerge(WebpackConfig, {
    mode: "production",
    devtool: "hidden-source-map",
    entry: {
        app: resolve(__dirname, "../src/main")
    },
    plugins: [
        new Webpack.DllReferencePlugin({
            manifest: require("../library/library.json")
        })
    ]
});

webpack5

const { merge: WebpackMerge } = require("webpack-merge");

module.exports = WebpackMerge(WebpackConfig, {
    mode: "production",
    devtool: "hidden-source-map",
    entry: {
        app: resolve(__dirname, "../src/main")
    },
    plugins: [
        new Webpack.DllReferencePlugin({
            manifest: require("../library/library.json")
        })
    ]
});

我们再次尝试执行npm run build:prod命令打包试试,又发现有新的报错

image.png

经排查发现原来在webpack4url-loader使用loaders的配置已经被废弃掉,而在webpack5url-loader在使用options配置的时候必须要使用use数组,相应的loader也要使用对象的形式来书写。

webpack4

{
    test: /\.(jpg|jpeg|png|gif)$/,
    loaders: 'url-loader',
    exclude: /node_modules/,
    options: {
        limit: 8192,
        outputPath: 'img/',
        name: '[name]-[hash:6].[ext]'
    }
}

webpack5

{
    test: /\.(jpg|jpeg|png|gif)$/,
    use: [
        {
            loader: "url-loader",
            options: {
                limit: 8192,
                outputPath: "img/",
                name: "[name]-[hash:6].[ext]"
            }
        }
    ]
}

我们再次尝试执行npm run build:prod命令打包试试,又发现有新的报错。

image.png

经排查发现原来在webpack4中经常使用的hard-source-webpack-plugin来缓存我们的打包文件,在webpack5中被废弃掉了;喜欢思考的小伙伴们也许就会发问,上边都描述了webpack5webpack4构建效率上有大大的提升,那webpack5它构建的效率提升到底体现在哪地方呢?个人觉得就是webpack5中最大的优势就是它的持久化缓存能力,那webpack5中我们是如何开启缓存的呢?

webpack4

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

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

webpack5

module.exports = {    
    cache: {      
        // 将缓存类型设置为文件系统      
        type: "filesystem",       
        buildDependencies: {        
            /* 将你的 config 添加为 buildDependency,           
               以便在改变 config 时获得缓存无效*/        
            config: [__filename],        
            /* 如果有其他的东西被构建依赖,           
               你可以在这里添加它们*/        
            /* 注意,webpack.config,           
               加载器和所有从你的配置中引用的模块都会被自动添加*/      
        },      
        // 指定缓存的版本      
        version: '1.0'     
    }
}

我们再次尝试执行npm run build:prod命令打包后,找到node_modules/.cache目录会发现

image.png

每次我们执行完打包命令后,为了防止缓存过于固定,导致更改构建配置无感知,webpack5依然使用旧的缓存,默认情况下,每次修改构建配置文件都会导致重新开始缓存。当然也可以自己主动设置 version 来控制缓存的更新。

原理

webpack5增加了确定的 moduleIdchunkId 的支持

optimization.moduleIds = 'deterministic'
optimization.chunkIds = 'deterministic'
  • 这个配置在mode:production的时候是默认开启的,它的作用是以确定的方式为 modulechunk 分配 3-5数字id,相比于 webpack4 版本的选项 hashed,它会导致更小的文件 bundles

  • 由于 moduleIdchunkId 确定了,构建的文件的 hash 值也会确定,有利于浏览器长效缓存。同时此配置有利于减少文件打包大小。这个也是webpack5持久化缓存的秘诀之一。

打包没有报错后,接下来我们开始执行npm run dev看看开发环境启动有没有问题。

image.png

因为我们的脚手架在开发环境使用的是webpack-dev-middleware配合express来启动本地前端服务的,经排查在webpack5中废弃掉了compiler.plugin()的方式调用。

webpack4

const compiler = Webpack(WebpackConfig);
// webpack5废弃改写法
compiler.plugin("compilation", compilation => {
    compilation.plugin("html-webpack-plugin-after-emit", () => {
        hotMiddleware.publish({ action: "reload" });
    });
});

compiler.apply(new DashboardPlugin());

所以,我们按照官方文档的说法采用webpack serve来启动我们的本地服务

webpack4

"scripts": {
    "start": "node build/dev-server.js"
}

webpack5

"scripts": {
    "start": "webpack serve --config build/webpack.config.dev.js"
}

再次执行npm run dev后,发现每次启动都会有一个关于hash配置的警告

image.png

这个警告提示我们要把项目中之前在webpack4中使用hash配置的改成chunkhash或者contenthash

webpack4

module.exports = {
    output: {
        filename: "js/[name]-[hsah:6].js",
        chunkFilename: "js/[name]-[chunkhash:6].js",
        path: resolve(__dirname, "../dist")
    }
}

webpack5

module.exports = {
    output: {
        filename: "js/[name]-[chunkhash:6].js",
        chunkFilename: "js/[name]-[chunkhash:6].js",
        path: resolve(__dirname, "../dist")
    }
}

再次执行npm run dev后,启动终于算是正常了。但是当我们访问系统中带有图片的页面的时候,发现使用require引入图片都无法正常加载展示。审查元素发现,图片路径无法正常的编译。

image.png

经过排查发现,我们需要在url-loader配置中添加esModule: false才可以把require引入的图片正常的编译。

webpack5

module.exports = {
    {
    test: /\.(jpg|jpeg|png|gif)$/,
    use: [
        {
            loader: "url-loader",
            options: {
                // 新增一行这个配置
                esModule: false,
                limit: 8192,
                outputPath: "img/",
                name: "[name]-[chunkhash:6].[ext]"
            }
        }
    ]
}
}

总结

当然,上述仅仅是我摘取几个比较有代表性的打包报错,没有把所有的报错一一给大家罗列出来。具体的细节还需要小伙伴们自己动手去折腾,这样才能完整的体验到整个升级带来的快(痛)乐(苦);尤其是你在历经各种各样奇奇怪怪的报错,然后又一个个的将其修复的时候,内心还是会有一些成就感的,最重要的是webpack5在构建的效率上确实相比webpack4有了非常大幅的提升。

所以,webpack5中除了在构建上利用其强大的持久化缓存,将构建的效率有非常大的提升之外,还有一些非常令人兴奋的新特性

  • 更优的 tree-shaking
  • Module Federation
  • Node Polyfill 脚本被移除
  • ...

尤其是Module Federation,有点类似于云组件的概念,跟微前端的理念非常类似。它使 JavaScript 应用得以从另一个 JavaScript 应用中动态地加载代码 —— 同时共享依赖。相当于 webpack 提供了线上 runtime 的环境,多个应用利用 CDN 共享组件或应用,不需要本地安装 npm 包再构建了。

后续有机会我也会在单页的项目中尝试使用Module Federation

好了,关于单页脚手架的搭建以及webpack在我们项目中的优化,已经连续输出了3篇相关的文章了,后续会针对其他类型的文章做专题输出,比如浏览器网络设计模式前端性能优化等,敬请期待!

如果您觉得这篇文章对您有一点点帮助,欢迎您看完后给我点赞,您的点赞是我前进的动力,谢谢~~

images.jpeg