webpack的热更新原理

Webpack热更新( Hot Module Replacement,简称 HMR),无需完全刷新整个页面的同时,更新所有类型的模块,是 Webpack 提供的最有用的功能之一。

  • 保留在完全重新加载页面期间丢失的应用程序状态。
  • 只更新变更内容,以节省宝贵的开发时间。
  • 在源代码中对 CSS / JS 进行修改,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
    HMR作为一个Webpack内置的功能,可以通过HotModuleReplacementPlugin--hot开启。

刷新我们一般分为两种:

  • 一种是页面刷新,不保留页面状态,就是简单粗暴,直接window.location.reload()
  • 另一种是基于WDS (Webpack-dev-server)的模块热替换,只需要局部刷新页面上发生变化的模块,同时可以保留当前的页面状态,比如复选框的选中状态、输入框的输入等。

webpack 构建

项目启动之后,会进行首次构建打包,控制台中会输出整个的构建过程,可以观察到一个 Hash 值 3606e1ab1ddcf6626797。

在每次代码的修改后,保存时都会在控制台上出现 compiling...字样,可以在控制台中观察到:

  • Hash 值更新:4f8c0eff7ac051c13277;
  • 新生成文件:3606e1ab1ddcf6626797.hot-update.json
  • main1.3606e1ab1ddcf6626797.hot-update.js。

    如果没有任何改动,直接保存,控制台输出编译打包信息,Hash 值没有发生变化,仍旧是 4f8c0eff7ac051c13277。

    再次修改代码保存,控制台输出编译打包信息。根据文件名可以发现,上次输出的 Hash 值被作为本次编译新生成的 Hmr 文件标识。同样,本次输出的 Hash 值会被作为下次热更新的标识。

webpack watch

为什么代码的改动保存会自动编译,重新打包?这一系列的重新检测编译依赖于 Webpack 的文件监听:在项目启动之后,Webpack 会通过 Compiler 类的 Run 方法开启编译构建过程,编译完成后,调用 Watch 方法监听文件变更,当文件发生变化,重新编译,编译完成之后继续监听。

页面的访问需要依赖 Web 服务器,那要如何将 Webpack 编译打包之后的文件传递给 Web 服务器呢?这就要看 Webpack-dev-middleware了。 Webpack-dev-middleware 是一个封装器( wrapper ),它可以把 Webpack 处理过的文件发送到一个Server(其中 Webpack-Dev-Server 就是内置了 Webpack-dev-middlewareExpress 服务器)。上面有说到编译之后的文件会被写入到内存,而 Webpack-dev-middleware 插件通过 memory-fs 实现静态资源请求直接访问内存文件。

js 复制代码
	const webpack = require('webpack');
	const webpackConfig = require('./webpack.dev.conf');
	const compiler = webpack(webpackConfig);
	debug('webpack编译完成');
	debug('将编译流通过webpack-dev-middleware');
	const devMiddleware = require('webpack-dev-middleware')(compiler, {
	// self-define options
	});

上面代码可以看到,Webpack 编译打包之后得到一个 Compilation ,并将 Compilation 传递到 Webpack-dev-middleware 插件中,Webpack-dev-middleware 可以通过 Compilation 调用 Webpack中 的 Watch 方法实时监控文件变化,并重新编译打包写入内存。

留意一下浏览器端,在 Network 中可以看到几个请求:

/__Webpack_hmr 请求返回的消息包含了首次 Hash 值,每次代码变动重新编译后,浏览器会发出 hash.hot-update.json、fileChunk.hash.hot-update.js 资源请求。

点开查看 hash.hot-update.json 请求,返回的结果中,h 是一个 hash 值,用于下次文件热更新请求的前缀,c 表示当前要热更新的文件是 main1 。

继续查看 fileChunk.hash.hot-update.js,返回的内容是使用 webpackHotUpdate 标识的 fileChunk 内容。

热更新源码

我们根据webpack-dev-serverpackage.json中的bin命令,可以找到命令的入口文件bin/webpack-dev-server.js

js 复制代码
	// node_modules/webpack-dev-server/bin/webpack-dev-server.js
	
	// 生成webpack编译主引擎 compiler
	let compiler = webpack(config);
	
	// 启动本地服务
	let server = new Server(compiler, options, log);
	server.listen(options.port, options.host, (err) => {
	    if (err) {throw err};
	});

本地服务代码:

js 复制代码
	// node_modules/webpack-dev-server/lib/Server.js
	class Server {
	    constructor() {
	        this.setupApp();
	        this.createServer();
	    }
	    
	    setupApp() {
	        // 依赖了express
	    	this.app = new express();
	    }
	    
	    createServer() {
	        this.listeningApp = http.createServer(this.app);
	    }
	    listen(port, hostname, fn) {
	        return this.listeningApp.listen(port, hostname, (err) => {
	            // 启动express服务后,启动websocket服务
	            this.createSocketServer();
	        }
	    }                                   
	}
总结:

这一小节代码主要做了三件事:

  • 启动webpack,生成compiler实例。compiler上有很多方法,比如可以启动 webpack 所有编译工作,以及监听本地文件的变化。
  • 使用express框架启动本地server`,让浏览器可以请求本地的静态资源。
  • 本地server启动之后,再去启动websocket服务,通过websocket,可以建立本地服务和浏览器的双向通信。这样就可以实现当本地文件发生变化,立马告知浏览器可以热更新代码啦!

如果不了解websocket,建议简单了解一下websocket速成.

参考文章

相关推荐
J总裁的小芒果6 分钟前
Vue3 el-table 默认选中 传入的数组
前端·javascript·elementui·typescript
Lei_zhen969 分钟前
记录一次electron-builder报错ENOENT: no such file or directory, rename xxxx的问题
前端·javascript·electron
咖喱鱼蛋11 分钟前
Electron一些概念理解
前端·javascript·electron
yqcoder12 分钟前
Vue3 + Vite + Electron + TS 项目构建
前端·javascript·vue.js
鑫宝Code29 分钟前
【React】React Router:深入理解前端路由的工作原理
前端·react.js·前端框架
Mr_Xuhhh2 小时前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
永乐春秋3 小时前
WEB攻防-通用漏洞&文件上传&js验证&mime&user.ini&语言特性
前端
鸽鸽程序猿3 小时前
【前端】CSS
前端·css
ggdpzhk3 小时前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
学不会•5 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html