您的位置:首页 > 娱乐 > 明星 > webpack之HMR

webpack之HMR

2024/12/23 16:14:04 来源:https://blog.csdn.net/qq_40588441/article/details/139665268  浏览:    关键词:webpack之HMR

什么是HMR

  • Hot Module Replacement是指当我们对代码修改并保存后,webpack将会对代码进行重新打包,并将新的模块发送到浏览器端,浏览器用新的模块替换掉旧的模块,以实现在不刷新浏览器的前提下更新页面

使用HMR

安装

yarn add webpack webpack-cli webpack-dev-server html-webpack-plugin socket.io socket.io-client events mime fs-extra --dev

使用

webpack.config.js

webpack.config.js

let path = require("path");
let webpack = require("webpack");
let HtmlWebpackPlugin = require("html-webpack-plugin");
let HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
module.exports = {mode: "development",entry:"./src/index.js",output: {filename: "[name].js",path: path.resolve(__dirname, "dist")},devServer:{hot:true,port:8000,contentBase:path.join(__dirname,'static')},plugins: [new HtmlWebpackPlugin({template:'./src/index.html'}),new HotModuleReplacementPlugin()]
}

src\index.html

src\index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>hmr</title>
</head>
<body><input/><div id="root"></div>
</body>
</html>

src\index.js

src\index.js

let render = () => {let title = require("./title.js");root.innerText = title;
}
render();if (module.hot) {module.hot.accept(["./title.js"], render);
}

title.js

src\title.js

module.exports = "title";

package.json

"scripts": {"build": "webpack","dev": "webpack serve"
}

debugger

  "scripts": {"build": "webpack","dev": "webpack serve",
+    "debug": "webpack serve"},

基础知识

module和chunk

  • 在 webpack里有各种各样的模块
  • 一般一个入口会依赖多个模块
  • 一个入口一般会对应一个chunk,这个chunk里包含这个入口依赖的所有的模块

HotModuleReplacementPlugin

  • webpack\lib\HotModuleReplacementPlugin.js
  • 它会生成两个补丁文件
    • 上一次编译生成的hash.hot-update.json,说明从上次编译到现在哪些代码块发生成改变
    • chunk名字.上一次编译生成的hash.hot-update.js,存放着此代码块最新的模块定义,里面会调用webpackHotUpdate方法
  • 向代码块中注入HMR runtime代码,热更新的主要逻辑,比如拉取代码、执行代码、执行accept回调都是它注入的到chunk中的
  • hotCreateRequire会帮我们给模块 module的parentschildren赋值

webpack的监控模式

  • 如果使用监控模式编译webpack的话,如果文件系统中有文件发生了改变,webpack会监听到并重新打包
  • 每次编译会产生一个新的hash值

工作流程

服务器部分

  1. 启动webpack-dev-server服务器
  2. 创建webpack实例
  3. 创建Server服务器
  4. 添加webpack的done事件回调,在编译完成后会向浏览器发送消息
  5. 创建express应用app
  6. 使用监控模式开始启动webpack编译,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中
  7. 设置文件系统为内存文件系统
  8. 添加webpack-dev-middleware中间件
  9. 创建http服务器并启动服务
  10. 使用sockjs在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,浏览器端根据这些socket消息进行不同的操作。当然服务端传递的最主要信息还是新模块的hash值,后面的步骤根据这一hash值来进行模块热替换
步骤代码位置
1.启动webpack-dev-server服务器webpack-dev-server.js#L159
2.创建webpack实例webpack-dev-server.js#L89
3.创建Server服务器webpack-dev-server.js#L100
4.更改config的entry属性webpack-dev-server.js#L157
entry添加dev-server/client/index.jsaddEntries.js#L22
entry添加webpack/hot/dev-server.jsaddEntries.js#L30
5. setupHooksServer.js#L122
6. 添加webpack的done事件回调Server.js#L183
编译完成向websocket客户端推送消息,最主要信息还是新模块的hash值,后面的步骤根据这一hash值来进行模块热替换Server.js#L178
7.创建express应用appServer.js#L169
8. 添加webpack-dev-middleware中间件Server.js#L208
以watch模式启动webpack编译,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包index.js#L41
设置文件系统为内存文件系统index.js#L65
返回一个中间件,负责返回生成的文件middleware.js#L20
app中使用webpack-dev-middlerware返回的中间件Server.js#L128
9. 创建http服务器并启动服务Server.js#L135
10. 使用sockjs在浏览器端和服务端之间建立一个 websocket 长连接Server.js#L745
创建socket服务器并监听connection事件SockJSServer.js#L33

客户端部分

  1. webpack-dev-server/client-src/default/index.js端会监听到此hash消息,会保存此hash值
  2. 客户端收到ok的消息后会执行reloadApp方法进行更新
  3. 在reloadApp中会进行判断,是否支持热更新,如果支持的话发射webpackHotUpdate事件,如果不支持则直接刷新浏览器
  4. webpack/hot/dev-server.js会监听webpackHotUpdate事件,然后执行check()方法进行检查
  5. 在check方法里会调用module.hot.check方法
  6. 它通过调用 JsonpMainTemplate.runtimehotDownloadManifest方法,向 server 端发送 Ajax 请求,服务端返回一个 Manifest文件,该 Manifest 包含了所有要更新的模块的 hash 值和chunk名
  7. 调用JsonpMainTemplate.runtimehotDownloadUpdateChunk方法通过JSONP请求获取到最新的模块代码
  8. 补丁JS取回来后会调用JsonpMainTemplate.runtime.jswebpackHotUpdate方法,里面会调用hotAddUpdateChunk方法,用新的模块替换掉旧的模块
  9. 然后会调用HotModuleReplacement.runtime.jshotAddUpdateChunk方法动态更新模块代 码
  10. 然后调用hotApply方法进行热更新
步骤代码
1.连接websocket服务器socket.js#L25
2.websocket客户端监听事件socket.js#L53
监听hash事件,保存此hash值index.js#L55
3.监听ok事件,执行reloadApp方法进行更新index.js#L93
4. 在reloadApp中会进行判断,是否支持热更新,如果支持的话发射webpackHotUpdate事件,如果不支持则直接刷新浏览器reloadApp.js#L7
5. 在webpack/hot/dev-server.js会监听webpackHotUpdate事件dev-server.js#L55
6. 在check方法里会调用module.hot.check方法dev-server.js#L13
7. 调用hotDownloadManifest,向 server 端发送 Ajax 请求,服务端返回一个 Manifest文件(lastHash.hot-update.json),该 Manifest 包含了本次编译hash值 和 更新模块的chunk名HotModuleReplacement.runtime.js#L180
8. 调用JsonpMainTemplate.runtimehotDownloadUpdateChunk方法通过JSONP请求获取到最新的模块代码JsonpMainTemplate.runtime.js#L14
9. 补丁JS取回来后会调用JsonpMainTemplate.runtime.jswebpackHotUpdate方法JsonpMainTemplate.runtime.js#L8
10. 然后会调用HotModuleReplacement.runtime.jshotAddUpdateChunk方法动态更新模块代码HotModuleReplacement.runtime.js#L222
11.然后调用hotApply方法进行热更新HotModuleReplacement.runtime.js#L257 HotModuleReplacement.runtime.js#L278
12.从缓存中删除旧模块HotModuleReplacement.runtime.js#L510
13.执行accept的回调HotModuleReplacement.runtime.js#L569

相关代码

  • webpack-dev-server.js
  • Server.js
  • webpack-dev-middleware/index.js
  • SockJSServer.js

启动开发服务器

startDevServer.js

startDevServer.js

const webpack = require("webpack")
const Server = require('./webpack-dev-server/lib/Server');
const config = require("./webpack.config")
function startDevServer(compiler,options) {const devServerOptions = options.devServer||{};const server = new Server(compiler, devServerOptions);const {host='localhost',port=8080}=devServerOptions;server.listen(port, host, (err) => {console.log(`Project is running at http://${host}:${port}`);});
}
const compiler = webpack(config);
startDevServer(compiler,config);

Server.js

webpack-dev-server\lib\Server.js

const express = require("express");
const http = require("http");
class Server {constructor(compiler,devServerOptions) {this.compiler = compiler;this.devServerOptions = devServerOptions;this.setupApp();this.createServer();}setupApp() {this.app = new express();}createServer() {this.server = http.createServer(this.app);}listen(port, host = "localhost", callback = ()=>{}) {this.server.listen(port, host, callback);}
}
module.exports = Server;

package.json

package.json

  "scripts": {"build": "webpack","dev": "webpack-dev-server",
+   "start":"node ./startDevServer.js"},

给entry添加客户端

Server.js

webpack-dev-server\lib\server\Server.js

const express = require("express");
+const updateCompiler = require('./utils/updateCompiler');
const http = require("http");
class Server {constructor(compiler,devServerOptions) {this.compiler = compiler;this.devServerOptions = devServerOptions;
+       updateCompiler(compiler);this.setupApp();this.createServer();}setupApp() {this.app = new express();}createServer() {this.server = http.createServer(this.app);}listen(port, host = "localhost", callback = ()=>{}) {this.server.listen(port, host, callback);}
}
module.exports = Server;

updateCompiler.js

webpack-dev-server\lib\utils\updateCompiler.js

const path = require("path");
let updateCompiler = (compiler) => {const config = compiler.options;//来自webpack-dev-server/client/index.js 在浏览器启动WS客户端config.entry.main.import.unshift(require.resolve("../../client/index.js"),);//webpack/hot/dev-server.js 在浏览器监听WS发射出来的webpackHotUpdate事件config.entry.main.import.unshift(require.resolve("../../../webpack/hot/dev-server.js"));console.log(config.entry);compiler.hooks.entryOption.call(config.context, config.entry);
}
module.exports = updateCompiler;

client\index.js

webpack-dev-server\lib\client\index.js

console.log('webpack-dev-server\client\index.js');

dev-server.js

webpack-dev-server\lib\client\hot\dev-server.js

console.log('webpack-dev-server\lib\client\hot\dev-server.js');

添加webpack的done事件回调

Server.js

webpack-dev-server\lib\server\Server.js

const express = require("express");
const updateCompiler = require('./utils/updateCompiler');
const http = require("http");
class Server {constructor(compiler,devServerOptions) {this.compiler = compiler;this.devServerOptions=devServerOptions;updateCompiler(compiler);
+       this.sockets = [];
+       this.setupHooks();this.setupApp();this.createServer();}
+    setupHooks() {
+        this.compiler.hooks.done.tap('webpack-dev-server', (stats) => {
+            console.log("stats.hash", stats.hash);
+            this.sockets.forEach((socket) => {
+                socket.emit("hash", stats.hash);
+                socket.emit("ok");
+            });
+            this._stats = stats;
+        });
+    }setupApp() {this.app = new express();}createServer() {this.server = http.createServer(this.app);}listen(port, host = "localhost", callback = ()=>{}) {this.server.listen(port, host, callback);}
}
module.exports = Server;

webpack-dev-middleware中间件

  • webpack-dev-middleware 实现webpack编译和文件相关操作

Server.js

webpack-dev-server\lib\Server.js

const express = require("express");
const updateCompiler = require('./utils/updateCompiler');
+const webpackDevMiddleware = require('../../webpack-dev-middleware');
const http = require("http");
class Server {constructor(compiler,devServerOptions) {this.compiler = compiler;this.devServerOptions = devServerOptions;updateCompiler(compiler);this.sockets = [];this.setupHooks();this.setupApp();
+       this.setupDevMiddleware();this.createServer();}
+   setupDevMiddleware() {
+        if(this.devServerOptions.contentBase)
+            this.app.use(express.static(this.devServerOptions.contentBase));
+        this.middleware = webpackDevMiddleware(this.compiler);
+        this.app.use(this.middleware);
+   }setupHooks() {this.compiler.hooks.done.tap('webpack-dev-server', (stats) => {console.log("stats.hash", stats.hash);this.sockets.forEach((socket) => {socket.emit("hash", stats.hash);socket.emit("ok");});this._stats = stats;});}setupApp() {this.app = new express();}createServer() {this.server = http.createServer(this.app);}listen(port, host = "localhost", callback = ()=>{}) {this.server.listen(port, host, callback);}
}
module.exports = Server;

webpack-dev-middleware\index.js

webpack-dev-middleware\index.js

const middleware = require("./middleware");
const MemoryFileSystem = require("memory-fs");
let memoryFileSystem = new MemoryFileSystem();
function webpackDevMiddleware(compiler) {compiler.watch({}, () => {console.log("start watching!");});let fs = compiler.outputFileSystem = memoryFileSystem;return middleware({fs,outputPath:compiler.options.output.path});
}module.exports = webpackDevMiddleware;

middleware.js

webpack-dev-middleware\middleware.js

const mime = require('mime');
const path = require("path");
module.exports = function wrapper(context) {return function middleware(req, res, next) {let url = req.url;if (url === "/") { url = "/index.html"; }let filename = path.join(context.outputPath, url);try {let stat = context.fs.statSync(filename);if (stat.isFile()) {let content = context.fs.readFileSync(filename);res.setHeader("Content-Type", mime.getType(filename));res.send(content);} else {res.sendStatus(404);}} catch (error) {res.sendStatus(404);}};
};

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com