webpack4-增量构建
webpack 4特性
Webpack 4 是一个在我所知截止日期(2022年1月)之前发布的版本,它引入了一些重要的特性和改进,以提高性能、构建速度和开发体验。以下是一些 Webpack 4 的主要特性:
模式(Mode): Webpack 4 引入了构建模式的概念,包括
development和production模式。不同的模式会自动配置一组默认选项,以便在开发和生产环境中获得最佳性能。开发模式下,Webpack 会启用有用的工具,如更详细的错误消息和源映射。生产模式下,Webpack 会进行代码压缩和其他优化。Tree Shaking: Webpack 4 引入了多种优化,包括模块树的Tree Shaking以减小 bundle 大小,以及对异步块的自动代码拆分以减少初始加载时间。
配置文件简化: Webpack 4 简化了配置文件的编写,不再需要
CommonsChunkPlugin和ModuleConcatenationPlugin等插件,许多功能都被整合到配置中,使得配置更简洁。Webpack 4对块图进行了巨大改进,并使用了新的块拆分技术。在新的改进过程中,诞生了新的插件---SplitChunksPlugin。这个插件能够自动识别哪些模块需要拆分为启发式的(heuristics),哪些需要拆分为块。
webpack-cli: Webpack 4 引入了
webpack-cli命令行工具,以提供更多的 CLI 功能,使得在终端中更容易运行 Webpack。模块热替换(Hot Module Replacement): Webpack 4 改进了模块热替换的支持,使得在开发模式下更容易进行热替换以提高开发效率。
为什么我只改了一行代码,却需要花 5 分钟才能构建完成?
了解 Webpack 构建原理,相信已经可以解答。尽管只改动了一行代码,但是在执行构建时,要完整执行所有模块的编译、优化和生成产物的处理过程,而不是只需要处理所改动的文件。
但是只编译打包所改动的文件真的不能实现吗?
怎么配置:增量构建
那就是在开启 devServer的时候,当我们执行 webpack-dev-server 命令后,Webpack 会进行一次初始化的构建,构建完成后启动服务并进入到等待更新的状态。
npm install webpack-dev-server --save-dev
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
devServer: {
contentBase: './dist', // 指定服务的根目录
port: 8080, // 指定端口号
},
};
当本地文件有变更时,Webpack 几乎瞬间将变更的文件进行编译,并将编译后的代码内容推送到浏览器端。你会发现,这个文件变更后的处理过程就符合上面所说的只编译打包改动的文件的操作,这就称为“增量构建”。
在开发服务模式下,初次构建编译了 47 个模块,完整的构建时间为 3306ms。当我们改动其中一个源码文件后,日志显示 Webpack 只再次构建了这一个模块,因此再次构建的时间非常短(24ms)。那么为什么在开发服务模式下可以实现增量构建的效果,而在生产环境下不行呢?
增量构建的影响因素
watch 配置:增量构建过程中-监控文件的变化
知变更的是哪个文件后,才能进行后续的针对性处理。在 Webpack 中启用 watch 配置即可,此外在使用 devServer 的情况下,该选项会默认开启。
在生产模式下开启 watch 配置后,相比初次构建,再次构建所编译的模块数量并未减少,即使只改动了一个文件,也仍然会对所有模块进行编译。因此可以得出结论,在生产环境下只开启 watch 配置后的再次构建并不能实现增量构建。
启用 Watch 模式。这意味着在初始构建之后,webpack 将继续监听任何已解析文件的更改。
https://v4.webpack.docschina.org/configuration/watch/#watch
module.exports = {
//...
watch: true
};
cache配置
https://v4.webpack.docschina.org/configuration/other-options/#cache
Webpack 5 中这一选项会有大的改变,会在下一节课中展开讨论。cache一般情况下默认为false,即不使用缓存,但在开发模式开启 watch 配置的情况下,cache 的默认值变更为true。此外,如果 cache 传值为对象类型,则表示使用该对象来作为缓存对象,这往往用于多个编译器 compiler 的调用情况。
缓存生成的 webpack 模块和 chunk,来改善构建速度。缓存默认在观察模式(watch mode)启用。禁用缓存只需简单传入:
module.exports = {
//...
cache: false
};
我们所期望的,再次构建时,在编译模块阶段只对有变化的文件进行了重新编译,实现了增量编译的效果。 但是美中不足的是,在优化阶段压缩代码时仍然耗费了较多的时间。这一点很容易理解:
体积最大的 react、react-dom 等模块和入口模块打入了同一个 Chunk 中,即使修改的模块是单独分离的 bar.js,但它的产物名称的变化仍然需要反映在入口 Chunk 的 runtime 模块中。因此入口 Chunk 也需要跟着重新压缩而无法复用压缩缓存数据。根据前面几节课的知识点,我们对配置再做一些优化,将vendor 分离后再来看看效果,如下面的图片所示:
可以看到,通过上面这一系列的配置后(watch + cache),在生产模式下,最终呈现出了我们期望的增量构建效果:
有文件发生变化时会自动编译变更的模块,并只对该模块影响到的少量 Chunk 进行优化并更新产物文件版本,而其他产物文件则保持之前的版本。如此,整个构建过程的速度大大提升。
生产环境下使用增量构建的阻碍
增量构建之所以快是因为将构建所需的数据(项目文件、node_modules 中的文件数据、历史构建后的缓存数据等)都保留在内存中。在 watch 模式下保留着构建使用的 Node 进程,使得下一次构建时可以直接读取内存中的数据。
而生产环境下的构建通常在集成部署系统中进行。对于管理多项目的构建系统而言,构建过程是任务式的:任务结束后即结束进程并回收系统资源。对于这样的系统而言,增量构建所需的保留进程与长时间占用内存,通常都是不可接受的。
因此,基于内存的缓存数据注定无法运用到生产环境中。要想在生产环境下提升构建速度,首要条件是将缓存写入到文件系统中。只有将文件系统中的缓存数据持久化,才能脱离对保持进程的依赖,你只需要在每次构建时将缓存数据读取到内存中进行处理即可。事实上,这也是上一课时中讲到的那些 Loader 与插件中的缓存数据的存储方式。
遗憾的是,Webpack 4 中的 cache 配置只支持基于内存的缓存,并不支持文件系统的缓存。因此,我们只能通过上节课讲到的一些支持缓存的第三方处理插件将局部的构建环节应用“增量处理”。
不过好消息是 Webpack 5 中正式支持基于文件系统的持久化缓存(Persistent Cache)。