Hot Module Replacement
是指当我们对代码修改并保存后,webpack将会对代码进行重新打包,并将新的模块发送到浏览器端,浏览器用新的模块替换掉旧的模块,以实现在不刷新浏览器的前提下更新页面yarn add webpack webpack-cli webpack-dev-server html-webpack-plugin socket.io socket.io-client events mime fs-extra --dev
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
<!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
let render = () => {
let title = require("./title.js");
root.innerText = title;
}
render();
if (module.hot) {
module.hot.accept(["./title.js"], render);
}
src\title.js
module.exports = "title";
"scripts": {
"build": "webpack",
"dev": "webpack serve"
}
"scripts": {
"build": "webpack",
"dev": "webpack serve",
+ "debug": "webpack serve"
},
webpackHotUpdate
方法hotCreateRequire
会帮我们给模块 module的parents
、children
赋值done
事件回调,在编译完成后会向浏览器发送消息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.js | addEntries.js#L22 |
entry添加webpack/hot/dev-server.js | addEntries.js#L30 |
5. setupHooks | Server.js#L122 |
6. 添加webpack的done 事件回调 |
Server.js#L183 |
编译完成向websocket客户端推送消息,最主要信息还是新模块的hash值,后面的步骤根据这一hash值来进行模块热替换 | Server.js#L178 |
7.创建express应用app | Server.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 |
webpack-dev-server/client-src/default/index.js
端会监听到此hash
消息,会保存此hash值ok
的消息后会执行reloadApp
方法进行更新webpackHotUpdate
事件,如果不支持则直接刷新浏览器webpack/hot/dev-server.js
会监听webpackHotUpdate
事件,然后执行check()
方法进行检查module.hot.check
方法JsonpMainTemplate.runtime
的hotDownloadManifest
方法,向 server 端发送 Ajax 请求,服务端返回一个 Manifest
文件,该 Manifest
包含了所有要更新的模块的 hash
值和chunk名JsonpMainTemplate.runtime
的hotDownloadUpdateChunk
方法通过JSONP请求获取到最新的模块代码JsonpMainTemplate.runtime.js
的webpackHotUpdate
方法,里面会调用hotAddUpdateChunk
方法,用新的模块替换掉旧的模块HotModuleReplacement.runtime.js
的hotAddUpdateChunk
方法动态更新模块代
码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.runtime 的hotDownloadUpdateChunk 方法通过JSONP请求获取到最新的模块代码 |
JsonpMainTemplate.runtime.js#L14 |
9. 补丁JS取回来后会调用JsonpMainTemplate.runtime.js 的webpackHotUpdate 方法 |
JsonpMainTemplate.runtime.js#L8 |
10. 然后会调用HotModuleReplacement.runtime.js 的hotAddUpdateChunk 方法动态更新模块代码 |
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 |
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);
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
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server",
+ "start":"node ./startDevServer.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;
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;
webpack-dev-server\lib\client\index.js
console.log('webpack-dev-server\client\index.js');
webpack-dev-server\lib\client\hot\dev-server.js
console.log('webpack-dev-server\lib\client\hot\dev-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编译和文件相关操作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
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;
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);
}
};
};
webpack-dev-server\lib\server\Server.js
const express = require("express");
const updateCompiler = require('./utils/updateCompiler');
const webpackDevMiddleware = require('../../webpack-dev-middleware');
const http = require("http");
+const WebsocketServer = require("socket.io");
class Server {
constructor(compiler) {
this.compiler = compiler;
updateCompiler(compiler);
this.sockets = [];
this.setupHooks();
this.setupApp();
this.setupDevMiddleware();
this.createServer();
+ this.createSocketServer();
}
setupDevMiddleware() {
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);
}
+ createSocketServer() {
+ const io = WebsocketServer(this.server);
+ io.on("connection", (socket) => {
+ console.log("client connected");
+ this.sockets.push(socket);
+ socket.on("disconnect", () => {
+ let index = this.sockets.indexOf(socket);
+ this.sockets = this.sockets.splice(index, 1);
+ });
+ if(this._stats){
+ socket.emit('hash', this._stats.hash);
+ socket.emit('ok');
+ }
+ });
+ }
listen(port, host = "localhost", callback = ()=>{}) {
this.server.listen(port, host, callback);
}
}
module.exports = Server;
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>
+ <script src="/socket.io/socket.io.js"></script>
</body>
</html>
webpack\hot\emitter.js
class EventEmitter {
constructor() {
this.events = {};
}
on(eventName, fn) {
this.events[eventName] = fn;
}
emit(eventName, ...args) {
this.events[eventName](...args);
}
}
module.exports = new EventEmitter();
webpack-dev-server\client\index.js
var hotEmitter = require("../../webpack/hot/emitter");
var socket = io();
var currentHash = "";
var initial = true;
socket.on("hash", (hash) => {
currentHash = hash;
});
socket.on("ok", () => {
console.log("ok");
if(initial){
return initial=false;
}
reloadApp();
});
function reloadApp() {
hotEmitter.emit("webpackHotUpdate", currentHash);
}
webpack\hot\dev-server.js
var hotEmitter = require('../../webpack/hot/emitter');
hotEmitter.on("webpackHotUpdate", (currentHash) => {
console.log('dev-server',currentHash);
})
static\hmr.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input/>
<div id="root"></div>
<script src="/socket.io/socket.io.js"></script>
<script src="hmr.js"></script></body>
</html>
static\hmr.js
(() => {
var modules = {
"./src/title.js": (module) => {
module.exports = "title3";
},
"./webpack/hot/emitter.js": (module) => {
class EventEmitter {
constructor() {
this.events = {};
}
on(eventName, fn) {
this.events[eventName] = fn;
}
emit(eventName, ...args) {
this.events[eventName](...args);
}
}
module.exports = new EventEmitter();
},
};
var cache = {};
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports;
}
var module = (cache[moduleId] = {
exports: {},
});
modules[moduleId](module, module.exports, require);
return module.exports;
}
(() => {
var hotEmitter = require("./webpack/hot/emitter.js");
var socket = io();
var currentHash = "";
var initial = true;
socket.on("hash", (hash) => {
currentHash = hash;
});
socket.on("ok", () => {
console.log("ok");
if (initial) {
return (initial = false);
}
reloadApp();
});
function reloadApp() {
hotEmitter.emit("webpackHotUpdate", currentHash);
}
})();
(() => {
var hotEmitter = require("./webpack/hot/emitter.js");
hotEmitter.on("webpackHotUpdate", (currentHash) => {
console.log("dev-server", currentHash);
});
})();
(() => {
let render = () => {
let title = require("./src/title.js");
root.innerText = title;
};
render();
})();
})();
(() => {
var modules = {
+ "./src/index.js": (module,exports,require) => {
+ let render = () => {
+ let title = require("./src/title.js");
+ root.innerText = title;
+ };
+ render();
+ },
"./src/title.js": (module) => {
module.exports = "title3";
},
"./webpack/hot/emitter.js": (module) => {
class EventEmitter {
constructor() {
this.events = {};
}
on(eventName, fn) {
this.events[eventName] = fn;
}
emit(eventName, ...args) {
this.events[eventName](...args);
}
}
module.exports = new EventEmitter();
},
};
var cache = {};
+ function hotCreateModule() {
+ var hot = {
+ _acceptedDependencies: {},
+ accept: function (deps, callback) {
+ for (var i = 0; i < deps.length; i++)
+ hot._acceptedDependencies[deps[i]] = callback;
+ },
+ check: hotCheck,
+ };
+ return hot;
+ }
+ function hotCreateRequire(parentModuleId) {
+ var parentModule = cache[parentModuleId];
+ if (!parentModule) return require;
+ var fn = function (childModuleId) {
+ parentModule.children.push(childModuleId);
+ require(childModuleId);
+ let childModule = cache[childModuleId];
+ childModule.parents.push(parentModule);
+ return childModule.exports;
+ };
+ return fn;
+ }
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports;
}
var module = (cache[moduleId] = {
exports: {},
+ hot: hotCreateModule(moduleId),
+ parents: [],
+ children: [],
});
+ modules[moduleId](module, module.exports, hotCreateRequire(moduleId));
return module.exports;
}
(() => {
var hotEmitter = require("./webpack/hot/emitter.js");
var socket = io();
var currentHash = "";
var initial = true;
socket.on("hash", (hash) => {
currentHash = hash;
});
socket.on("ok", () => {
console.log("ok");
reloadApp();
});
function reloadApp() {
hotEmitter.emit("webpackHotUpdate", currentHash);
}
})();
(() => {
var hotEmitter = require("./webpack/hot/emitter.js");
hotEmitter.on("webpackHotUpdate", (currentHash) => {
console.log("dev-server", currentHash);
});
})();
+ return hotCreateRequire("./src/index.js")("./src/index.js");
})();
webpack.config.js
module.exports = {
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
+ hotUpdateGlobal:'webpackHotUpdate'
}
}
static\hmr.js
(() => {
var cache = {};
+ var currentHash;
+ var lastHash;
+ let hotCheck = () => {
+ hotDownloadManifest()
+ .then((update) => {
+ update.c.forEach((chunkID) => {
+ hotDownloadUpdateChunk(chunkID);
+ });
+ lastHash = currentHash;
+ })
+ .catch((err) => {
+ window.location.reload();
+ });
+ };
+ let hotDownloadManifest = () => {
+ return new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ let hotUpdatePath = `main.${lastHash}.hot-update.json`;
+ xhr.open("get", hotUpdatePath);
+ xhr.onload = () => {
+ let hotUpdate = JSON.parse(xhr.responseText);
+ resolve(hotUpdate);
+ };
+ xhr.onerror = (error) => {
+ reject(error);
+ };
+ xhr.send();
+ });
+ };
+ let hotDownloadUpdateChunk = (chunkID) => {
+ let script = document.createElement("script");
+ script.src = `${chunkID}.${lastHash}.hot-update.js`;
+ document.head.appendChild(script);
+ };
+ self['webpackHotUpdate'] = (chunkId, moreModules) => {
+ hotAddUpdateChunk(chunkId, moreModules);
+ };
+ let hotUpdate = {};
+ function hotAddUpdateChunk(chunkId, moreModules) {
+ for (var moduleId in moreModules) {
+ hotUpdate[moduleId] = modules[moduleId] = moreModules[moduleId];
+ }
+ hotApply();
+ }
+ function hotApply() {
+ for (let moduleId in hotUpdate) {
+ let oldModule = cache[moduleId];
+ delete cache[moduleId];
+ oldModule.parents &&
+ oldModule.parents.forEach((parentModule) => {
+ parentModule.hot._acceptedDependencies[moduleId] &&
+ parentModule.hot._acceptedDependencies[moduleId]();
+ });
+ }
+ }
var modules = {
"./src/index.js": (module, exports, require) => {
let render = () => {
let title = require("./src/title.js");
root.innerText = title;
};
render();
if (module.hot) {
module.hot.accept(["./src/title.js"], render);
}
},
"./src/title.js": (module) => {
module.exports = "title3";
},
"./webpack/hot/emitter.js": (module) => {
class EventEmitter {
constructor() {
this.events = {};
}
on(eventName, fn) {
this.events[eventName] = fn;
}
emit(eventName, ...args) {
this.events[eventName](...args);
}
}
module.exports = new EventEmitter();
},
};
function hotCreateModule() {
var hot = {
_acceptedDependencies: {},
accept: function (deps, callback) {
for (var i = 0; i < deps.length; i++)
hot._acceptedDependencies[deps[i]] = callback;
},
check: hotCheck,
};
return hot;
}
function hotCreateRequire(parentModuleId) {
var parentModule = cache[parentModuleId];
if (!parentModule) return require;
var fn = function (childModuleId) {
parentModule.children.push(childModuleId);
require(childModuleId);
let childModule = cache[childModuleId];
childModule.parents.push(parentModule);
return childModule.exports;
};
return fn;
}
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports;
}
var module = (cache[moduleId] = {
exports: {},
hot: hotCreateModule(moduleId),
parents: [],
children: [],
});
modules[moduleId](module, module.exports, hotCreateRequire(moduleId));
return module.exports;
}
(() => {
var hotEmitter = require("./webpack/hot/emitter.js");
var socket = io();
var initial = true;
socket.on("hash", (hash) => {
currentHash = hash;
});
socket.on("ok", () => {
console.log("ok");
reloadApp();
});
function reloadApp() {
hotEmitter.emit("webpackHotUpdate", currentHash);
}
})();
(() => {
var hotEmitter = require("./webpack/hot/emitter.js");
hotEmitter.on("webpackHotUpdate", (currentHash) => {
+ if (!lastHash) {
+ lastHash = currentHash;
+ console.log("lastHash=", lastHash, "currentHash=", currentHash);
+ return;
+ }
+ console.log("lastHash=", lastHash, "currentHash=", currentHash);
+ console.log("webpackHotUpdate hotCheck");
+ hotCheck();
});
})();
return hotCreateRequire("./src/index.js")("./src/index.js");
})();
(() => {
var cache = {};
var currentHash;
var lastHash;
let hotUpdate = {};
let hotCheck = () => {
//6.它通过调用 JsonpMainTemplate.runtime的hotDownloadManifest方法,向 server 端发送 Ajax 请求,服务端返回一个 Manifest文件,该 Manifest 包含了所有要更新的模块的 hash 值和chunk名
hotDownloadManifest()
.then((update) => {
update.c.forEach((chunkID) => {
//7.调用JsonpMainTemplate.runtime的hotDownloadUpdateChunk方法通过JSONP请求获取到最新的模块代码
hotDownloadUpdateChunk(chunkID);
});
lastHash = currentHash;
})
.catch((err) => {
window.location.reload();
});
};
let hotDownloadManifest = () => {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
let hotUpdatePath = `main.${lastHash}.hot-update.json`;
xhr.open("get", hotUpdatePath);
xhr.onload = () => {
let hotUpdate = JSON.parse(xhr.responseText);
resolve(hotUpdate);
};
xhr.onerror = (error) => {
reject(error);
};
xhr.send();
});
};
let hotDownloadUpdateChunk = (chunkID) => {
let script = document.createElement("script");
script.src = `${chunkID}.${lastHash}.hot-update.js`;
document.head.appendChild(script);
};
//8.补丁JS取回来后会调用JsonpMainTemplate.runtime.js的webpackHotUpdate方法,里面会调用hotAddUpdateChunk方法,用新的模块替换掉旧的模块
self['webpackHotUpdate'] = (chunkId, moreModules) => {
//9.然后会调用HotModuleReplacement.runtime.js的hotAddUpdateChunk方法动态更新模块代 码
hotAddUpdateChunk(chunkId, moreModules);
};
function hotAddUpdateChunk(chunkId, moreModules) {
for (var moduleId in moreModules) {
hotUpdate[moduleId] = modules[moduleId] = moreModules[moduleId];
}
//10.然后调用hotApply方法进行热更新
hotApply();
}
function hotApply() {
for (let moduleId in hotUpdate) {
let oldModule = cache[moduleId];
delete cache[moduleId];
oldModule.parents &&
oldModule.parents.forEach((parentModule) => {
parentModule.hot._acceptedDependencies[moduleId] &&
parentModule.hot._acceptedDependencies[moduleId]();
});
}
}
var modules = {
"./src/index.js": (module, exports, require) => {
let render = () => {
let title = require("./src/title.js");
root.innerText = title;
};
render();
if (module.hot) {
module.hot.accept(["./src/title.js"], render);
}
},
"./src/title.js": (module) => {
module.exports = "title3";
},
"./webpack/hot/emitter.js": (module) => {
class EventEmitter {
constructor() {
this.events = {};
}
on(eventName, fn) {
this.events[eventName] = fn;
}
emit(eventName, ...args) {
this.events[eventName](...args);
}
}
module.exports = new EventEmitter();
},
};
function hotCreateModule() {
var hot = {
_acceptedDependencies: {},
accept: function (deps, callback) {
for (var i = 0; i < deps.length; i++)
hot._acceptedDependencies[deps[i]] = callback;
},
check: hotCheck,
};
return hot;
}
function hotCreateRequire(parentModuleId) {
var parentModule = cache[parentModuleId];
if (!parentModule) return require;
var fn = function (childModuleId) {
parentModule.children.push(childModuleId);
require(childModuleId);
let childModule = cache[childModuleId];
childModule.parents.push(parentModule);
return childModule.exports;
};
return fn;
}
function require(moduleId) {
if (cache[moduleId]) {
return cache[moduleId].exports;
}
var module = (cache[moduleId] = {
exports: {},
hot: hotCreateModule(moduleId),
parents: [],
children: [],
});
modules[moduleId](module, module.exports, hotCreateRequire(moduleId));
return module.exports;
}
(() => {
var hotEmitter = require("./webpack/hot/emitter.js");
var socket = io();
//1.客户端会监听到此`hash`消息,会保存此hash值
socket.on("hash", (hash) => {
currentHash = hash;
});
socket.on("ok", () => {
console.log("ok");
//2. 客户端收到ok的消息后会执行reloadApp方法进行更新
reloadApp();
});
function reloadApp() {
//3.在reloadApp中会进行判断,是否支持热更新,如果支持的话发射webpackHotUpdate事件,如果不支持则直接刷新浏览器
hotEmitter.emit("webpackHotUpdate", currentHash);
}
})();
(() => {
var hotEmitter = require("./webpack/hot/emitter.js");
//4.监听webpackHotUpdate事件,然后执行hotCheck()方法进行检查
hotEmitter.on("webpackHotUpdate", (currentHash) => {
if (!lastHash) {
lastHash = currentHash;
console.log("lastHash=", lastHash, "currentHash=", currentHash);
return;
}
console.log("lastHash=", lastHash, "currentHash=", currentHash);
console.log("webpackHotUpdate hotCheck");
//在check方法里会调用module.hot.check方法
//module.hot.check(); hotCheck();
});
})();
return hotCreateRequire("./src/index.js")("./src/index.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||{};
//3.创建Server服务器
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}`);
});
}
//1.启动webpack-dev-server服务器
//2.创建webpack实例
const compiler = webpack(config);
startDevServer(compiler,config);
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");
const WebsocketServer = require("socket.io");
class Server {
constructor(compiler,devServerOptions) {
this.compiler = compiler;
this.devServerOptions = devServerOptions;
updateCompiler(compiler);
this.sockets = [];
//4.添加webpack的done事件回调,在编译完成后会向浏览器发送消息
this.setupHooks();
//5.创建express应用app
this.setupApp();
//6.使用监控模式开始启动webpack编译,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中
//8.添加webpack-dev-middleware中间件
this.setupDevMiddleware();
//9.创建http服务器并启动服务
this.createServer();
//10.使用sockjs在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,浏览器端根据这些socket消息进行不同的操作。当然服务端传递的最主要信息还是新模块的hash值,后面的步骤根据这一hash值来进行模块热替换
this.createSocketServer();
}
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);
}
createSocketServer() {
const io = WebsocketServer(this.server);
io.on("connection", (socket) => {
console.log("client connected");
this.sockets.push(socket);
socket.on("disconnect", () => {
let index = this.sockets.indexOf(socket);
this.sockets = this.sockets.splice(index, 1);
});
if(this._stats){
socket.emit('hash', this._stats.hash);
socket.emit("ok");
}
});
}
listen(port, host = "localhost", callback = ()=>{}) {
this.server.listen(port, host, callback);
}
}
module.exports = Server;
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!");
});
//7.设置文件系统为内存文件系统
let fs = compiler.outputFileSystem = memoryFileSystem;
return middleware({
fs,
outputPath:compiler.options.output.path
});
}
module.exports = webpackDevMiddleware;