webpack就是一个模块打包工具,将我们分不同模块编写的代码经过一系列处理生成可以在浏览器上可以运行的代码。 这一系列处理包括从一个或多个入口开始处理相关依赖,比如使用loader转换ts,使用plugin压缩等,最终形成在html文件引用的bundle,甚至直接在html中引用。
而这个打包过程又可分为开发环境和生产环境,本文讨论的是开发环境修改后自动编译和模块热替换相关的内容。
修改后的自动编译
使用watch模式,可以在文件有变动后自动编译,但是需要刷新浏览器才有效果
webpack --watch
编译后的自动刷新浏览器
webpack-dev-server提供了重新编译自动刷新的功能,且因为插件webpack-dev-middleware的作用,每次编译的结果会输出到内存中,而不是硬盘里,注意如果后者和express等单独使用时重新编译后并不会自动刷新浏览器,还需要webpack-hot-middleware。
模块热替换
前面我们做到了代码修改后自动编译然后刷新整个页面,这样做的结果是每次修改保存后的反馈慢且丢失了当前页面的状态,比如当前表单已经输入的内容,因此我们要引入模块热替换,保证每次只更新修改相关的部分且保留状态。
webpack-dev-server内置了模块热替换功能,具体使用看这里。下面我们讨论一下具体替换过程。
- 首次build时会生成一个manifest文件和bundles,其中后者中注入了webpack runtime,其中manifest包括hash和对应的资源,runtime根据manifest加载对应的文件,完成整个过程。这次会生成一个hash,通过websocket发送给runtime,作为下一次请求的依据。
- 当我们修改代码进行保存时,由于webpack的watch模式,会重新编译,生成新的hash和manifest文件
[hash].hot-update.json和对应的待应用的更新*.hot-update.js。文件名中的hash是上次打包生成的hash。
- 如果我们没有修改内容进行保存,hash不会变,也没有新的manifest和待更新文件
-
当前文件保存后,通过websocket通知浏览器中的webpack runtime,如果有更新会动态创建script请求manifest和manifest中指定的待更新的js。
-
当请求到js文件后,会从待更新的模块开始查找update handler,如果没有则向上冒泡,直到到达起点或者带有 update handler的模块结束。前者会热更新失败,进行浏览器刷新,后者执行对应回调,完成本次热更新。 所谓的update handler就是执行HMR的对应api用来对更新进行处理
if (module.hot) {
module.hot.accept('./library.js', function() {
// 对更新过的 library 模块做些事情...
});
}
// or
if (import.meta.webpackHot) {
import.meta.webpackHot.accept('./library.js', function () {
// Do something with the updated library modue…
});
}
实际工作中,相关第三方工具已经完成了这一步,比如style-loader处理了css的handler,react也有React Hot Loader等等。
参考
- 看完这篇,面试再也不怕被问 Webpack 热更新
- Webpack’s Hot Module Replacement Feature Explained
- Understanding webpack HMR beyond the docs
- 120 行代码帮你了解 Webpack 下的 HMR 机制
Fast Refresh
前面提到的HMR是webpack提出的通用解决方案,具体的react上,虽然也有对应的loader,但是使用体验并不是很好,react native推出了更细化的解决方案Fast Refresh,并且为webpack配置了插件,用来替换原来的React Hot Loader,具体使用看这里。
这个方案目前还在发展中,使用起来还有一些限制。
工作方式
- 如果在编辑一个只导出react组件的文件,只会更新当前文件
- 如果导出的还有别的,会更新当前文件和引用他的模块
- 如果编辑的文件被react tree以外的文件引入,这时候会回退到full reload,比如一个组件同时导出一个常量,解决方案是将常量单独放咋一个文件维护,即不要将组件和其他在同一个文件导出,否则会造成失败。
限制
- 只能在函数组件保存state
- 在refresh时,带有依赖的hook会忽略依赖执行,比如useEffect
- 其他不生效的原因,比如未命名的函数组件,因为不知道是不是和react相关。