共计 24006 个字符,预计需要花费 61 分钟才能阅读完成。
前置问题
- 本地文件扭转,webpack 是如何晓得并且触发编译的?
- 浏览器是如何晓得本地代码从新编译,并且迅速申请了新生成的文件的?
- webpack 本地服务器是如何告知浏览器?
- 浏览器取得这些文件又是如何热更新的?热更新的流程是什么?
前置知识点
一. 代码扭转时主动编译的几种办法
摘录自 webpack 官网文档
1.webpack’s Watch Mode
2.webpack-dev-server
3.webpack-dev-middleware
Watch Mode
{ | |
"scripts": { | |
"watch": "webpack --watch", | |
"build": "webpack" | |
} | |
} |
命令行减少 watch
指令,当咱们扭转文件时,webpack
会主动编译扭转的模块,然而咱们得手动刷新浏览器能力看到变动。
webpack-dev-server
webpack.config.js
const path = require('path'); | |
const HtmlWebpackPlugin = require('html-webpack-plugin'); | |
module.exports = { | |
devServer: {static: './dist',}, | |
optimization: {runtimeChunk: 'single',// 多入口时配置} | |
}; |
package.json
{ | |
"scripts": { | |
"watch": "webpack --watch", | |
"start": "webpack serve --open", | |
"build": "webpack" | |
} | |
} |
webpack.config.js
配置 devServer
属性,命令行减少 webpack serve --open
指令,当咱们扭转文件时,webpack
会主动编译扭转的模块,并且主动刷新浏览器
webpack-dev-middleware
webpack-dev-middleware
内置于webpack-dev-server
,次要是用于监测代码文件变动,解决文件编译等流程,咱们也能够将它独自拿进去进行其它场景的开发,比方联合 express 实现文件编译监听性能。
server.js
const express = require('express'); | |
const webpack = require('webpack'); | |
const webpackDevMiddleware = require('webpack-dev-middleware'); | |
const app = express(); | |
const config = require('./webpack.config.js'); | |
const compiler = webpack(config); | |
// Tell express to use the webpack-dev-middleware and use the webpack.config.js | |
// configuration file as a base. | |
app.use( | |
webpackDevMiddleware(compiler, {publicPath: config.output.publicPath,}) | |
); | |
// Serve the files on port 3000. | |
app.listen(3000, function () {console.log('Example app listening on port 3000!\n'); | |
}); |
package.json
{ | |
"scripts": { | |
"watch": "webpack --watch", | |
"start": "webpack serve --open", | |
"server": "node server.js", | |
"build": "webpack" | |
} | |
} |
二. 入口调试
package.json
1. 在 Server.js
中设置 debugger
断点
2. 在package.json
中设置 node --inpect-brk
的主动断点
3. 运行命令后会主动跳转到Server.js
中
"scripts": { | |
"dev-Server": "node --inspect-brk ./node_modules/webpack-dev-server/bin/webpack-dev-server.js", | |
"dev-client": "webpack-dev-server", | |
}, |
本地客户端和服务端 debugger 相干文件阐明
客户端入口文件 entry
1. 编译造成 index.html
文件入口
2. 在index.html
入口文件中编译造成 index.js
,将webpack-dev-server/client/index.js
和webpack/hot/dev-server.js
注入到 index.js
中,进行 webSocket
的建设和监听,实时进行热更新操作
3. 调试时应用客户端的链接http://localhost:8080/
入口 entry 注入:// 1. node_modules/webpack-dev-server/client/index.js | |
// 2. node_modules/webpack/hot/dev-server.js |
服务端启动
1. 监听文件变动,进行编译
2. 将编译后果发送给客户端进行数据的更新操作
3. 调试时应用 node --inspect-brk ./node_modules/webpack-dev-server/bin/webpack-dev-server.js
编译过程中启动的 JS 文件:// 3. node_modules/webpack-dev-server/lib/Server.js | |
// 4. node_modules/webpack-dev-server/lib/servers/WebsocketServer.js |
源码剖析流程
一. 热更新总体流程图解
1. 整体流程图
2. 流程图文字剖析
初始化:本地服务器和客户端初始化
- Server:
new Server()
后会间接调用server.start()
,进行服务的启动 - Client:初始化过程中注入
webpack-dev-server/client/index.js
和webpack/hot/dev-server.js
到入口文件中 - Server:在
Server.js
中,进行webpack-dev-middleware
插件的注册,触发编译以及文件变动的监听 - Server:应用
express
开启本地 node 服务器 -
Server 和 Client:创立
WebSocket
/** * 省去细节代码,只保留(外围代码 || 要重点剖析的代码) */ class Server {async start() {await this.normalizeOptions(); await this.initialize(); if (this.options.webSocketServer) {this.createWebSocketServer(); } } async normalizeOptions() {const { options} = this; options.client.webSocketURL = { protocol: parsedURL.protocol, hostname: parsedURL.hostname, port: parsedURL.port.length > 0 ? Number(parsedURL.port) : "", pathname: parsedURL.pathname, username: parsedURL.username, password: parsedURL.password, }; const defaultWebSocketServerOptions = {path: "/ws"}; if (typeof options.webSocketServer === "undefined") { options.webSocketServer = { type: defaultWebSocketServerType, options: defaultWebSocketServerOptions, }; } } initialize() {compilers.forEach((compiler) => {this.addAdditionalEntries(compiler); if (this.options.hot) { // Apply the HMR plugin const plugin = new webpack.HotModuleReplacementPlugin(); plugin.apply(compiler); } }); this.setupApp(); this.setupDevMiddleware(); this.setupMiddlewares(); this.createServer();} addAdditionalEntries(compiler) {let additionalEntries = []; if (this.options.webSocketServer) { additionalEntries.push(`${require.resolve("../client/index.js")}?${webSocketURLStr}` ); } if (this.options.hot === "only") {additionalEntries.push(require.resolve("webpack/hot/only-dev-server")); } else if (this.options.hot) {additionalEntries.push(require.resolve("webpack/hot/dev-server")); } if (typeof webpack.EntryPlugin !== "undefined") { // node_modules/webpack-dev-server/client/index.js?protocol=ws%3A&hostname=0.0.0.0&port=9000&pathname=%2Fws&logging=info&overlay=true&reconnect=10&hot=true&live-reload=true // node_modules/webpack/hot/dev-server.js for (const additionalEntry of additionalEntries) { new webpack.EntryPlugin(compiler.context, additionalEntry, {name: undefined,}).apply(compiler); } } } setupDevMiddleware() {const webpackDevMiddleware = require("webpack-dev-middleware"); // middleware for serving webpack bundle this.middleware = webpackDevMiddleware( this.compiler, this.options.devMiddleware ); } setupApp() {this.app = new express(); } setupMiddlewares() {let middlewares = []; middlewares.push({ name: "webpack-dev-middleware", middleware: this.middleware }); // middlewares: Array(6) // 0:{name: 'compression', middleware: ƒ} // 1:{name: 'webpack-dev-middleware', middleware: ƒ} // 2:{name: 'express-static', path: '/', middleware: ƒ} // 3:{name: 'serve-index', path: '/', middleware: ƒ} // 4:{name: 'serve-magic-html', middleware: ƒ} // 5:{name: 'options-middleware', path: '*', middleware: ƒ} middlewares.forEach((middleware) => {if (typeof middleware === "function") {(this.app).use(middleware); } else if (typeof middleware.path !== "undefined") {(this.app).use(middleware.path, middleware.middleware); } else {(this.app).use(middleware.middleware); } }); } createServer() {this.server = require("http").createServer( options, this.app ); this.server.on("connection", (socket) => { // Add socket to list this.sockets.push(socket); }); } createWebSocketServer() {this.webSocketServer = new (this.getServerTransport())(this); // this.webSocketServer = new WebsocketServer(this); if (this.options.hot === true || this.options.hot === "only") {this.sendMessage([client], "hot"); } if (this.options.liveReload) {this.sendMessage([client], "liveReload"); } this.sendStats([client], this.getStats(this.stats), true); } getServerTransport() { let implementation; if (this.options.webSocketServer.type === "ws") {implementation = require("./servers/WebsocketServer"); } return implementation; } } 初始化文件变动 Watching.js 治理类,并且触发编译
Server.js
在注册webpack-dev-middleware
的时候,进行Watching.js
的初始化,并且触发第一次编译// node_modules/webpack-dev-server/lib/Server.js setupDevMiddleware() {const webpackDevMiddleware = require("webpack-dev-middleware"); // middleware for serving webpack bundle this.middleware = webpackDevMiddleware( this.compiler, this.options.devMiddleware ); } // node_modules/webpack-dev-middleware/dist/index.js function wdm() {const context = { compiler}; setupOutputFileSystem(context); // Start watching context.compiler.watch(watchOptions, errorHandler); } // node_modules/webpack/lib/Compiler.js watch(watchOptions, handler) {this.watching = new Watching(this, watchOptions, handler); return this.watching; } // node_modules/webpack/lib/Watching.js // Watching.js 的 constructor()->_invalidate()->_go() _go(fileTimeInfoEntries, contextTimeInfoEntries, changedFiles, removedFiles) {const run = () => {this.compiler.compile(onCompiled); }; run();} // node_modules/Complier.js compile(callback) {this.hooks.make.callAsync(compilation, err => {}); // 触发编译 } 本地服务端 - 文件变动运行逻辑
Server.js
在Watching.js
注册了文件内存零碎的监听,文件发生变化时,会触发从新编译// node_modules/webpack/lib/webpack.js new NodeEnvironmentPlugin({infrastructureLogging: options.infrastructureLogging}).apply(compiler); // node_modules/webpack/lib/node/NodeEnvironmentPlugin.js compiler.watchFileSystem = new NodeWatchFileSystem(compiler.inputFileSystem); // node_modules/webpack/lib/Watching.js // 第一次会被动触发 this._go()进行编译,每次编译完结时注册监听 _done(err, compilation) { //... this.watch( compilation.fileDependencies, compilation.contextDependencies, compilation.missingDependencies ); //... } watch(files, dirs, missing) {this.watcher = this.compiler.watchFileSystem.watch(...args, () => { this._invalidate( fileTimeInfoEntries, contextTimeInfoEntries, changedFiles, removedFiles ); this._onChange();}); } _invalidate() {this._go(...args); } _go(fileTimeInfoEntries, contextTimeInfoEntries, changedFiles, removedFiles) {const run = () => {this.compiler.compile(onCompiled); }; run();} // node_modules/Complier.js compile(callback) {this.hooks.make.callAsync(compilation, err => {}); // 触发编译 } 本地服务端 - 告诉客户端
1. 监听 Webpack 编译实现后,被动触发
sendStats
办法
2. 本地服务端的webSocket
被动发送hash
命令和ok
命令到本地浏览器 client 端class Server {setupHooks() { // 初始化时注册 done 的监听事件,编译实现后,调用 sendStats 办法进行 webSocket 的命令发送 this.compiler.hooks.done.tap( "webpack-dev-server", (stats) => {if (this.webSocketServer) {this.sendStats(this.webSocketServer.clients, this.getStats(stats)); } this.stats = stats; } ); } sendStats(clients, stats, force) { // 更新以后的 hash this.currentHash = stats.hash; // 发送给客户端以后的 hash 值 this.sendMessage(clients, "hash", stats.hash); // 发送给客户端 ok 的指令 this.sendMessage(clients, "ok"); } } 客户端 - 接管到服务端发来的 WebSocket 音讯
1. 收到
type=hash
和type=ok
两条音讯2.
type=hash
更新了以后的currentHash
值
3.type=ok
触发了reloadApp()
办法的执行var onSocketMessage = {hash: function hash(_hash) { status.previousHash = status.currentHash; status.currentHash = _hash; }, ok: function ok() {sendMessage("Ok"); reloadApp(options, status); }, }; var socketURL = createSocketURL(parsedResourceQuery); socket(socketURL, onSocketMessage, options.reconnect); function reloadApp(_ref, status) {function applyReload(rootWindow, intervalId) {rootWindow.location.reload(); } var search = self.location.search.toLowerCase(); var allowToHot = search.indexOf("webpack-dev-server-hot=false") === -1; var allowToLiveReload = search.indexOf("webpack-dev-server-live-reload=false") === -1; if (hot && allowToHot) {log.info("App hot update..."); hotEmitter.emit("webpackHotUpdate", status.currentHash); } else if (liveReload && allowToLiveReload) {// 依据条件判断执行 applyReload()办法 } } 客户端 -hotEmitter.emit(“webpackHotUpdate”, status.currentHash)
1.
webpack/hot/dev-server.js
接管到hotEmitter
的音讯后,进行check()
办法的调用
2.module.hot.check(true)
触发,而后判断是否须要重启var check = function check() { module.hot .check(true) .then(function (updatedModules) {if (!updatedModules) {log("warning", "[HMR] Cannot find update. Need to do a full reload!"); window.location.reload(); return; } }); }; var hotEmitter = require("./emitter"); hotEmitter.on("webpackHotUpdate", function (currentHash) { lastHash = currentHash; check();}); 客户端 -module.hot.check
/node_modules/webpack/lib/hmr/HotModuleReplacement.runtime.js
在编译造成最终代码时,会注入HotModuleReplacement.runtime.js
代码,拦挡require
,进行createRequire
和createModuleHotObject
createRequire
构建以后
request
的parent
和children
,实质是在require
的根底上保留各个模块之间的依赖关系,为前面的热更新做筹备,因为一个文件的更新必然波及到另外依赖模块的相干更新createModuleHotObject
构建以后
module
的hot
API,前面的热更新都须要通过hotCheck
和hotApply
进行操作function __webpack_require__(moduleId) { // ...... 省略代码 ...... var execOptions = {id: moduleId, module: module, factory: __webpack_modules__[moduleId], require: __webpack_require__ }; __webpack_require__.i.forEach(function (handler) {handler(execOptions); }); // ...... 省略代码 ...... return module.exports; } __webpack_require__.i.push(function (options) { var module = options.module; var require = createRequire(options.require, options.id); module.hot = createModuleHotObject(options.id, module); module.parents = currentParents; module.children = []; currentParents = []; options.require = require; }); function createRequire(require, moduleId) {var me = installedModules[moduleId]; // ...... 省略代码 ...... var fn = function (request) {if (me.hot.active) {if (installedModules[request]) {var parents = installedModules[request].parents; if (parents.indexOf(moduleId) === -1) {parents.push(moduleId); } } else {currentParents = [moduleId]; currentChildModule = request; } if (me.children.indexOf(request) === -1) {me.children.push(request); } } else {currentParents = []; } return require(request); }; // ...... 省略代码 ...... return fn; } function createModuleHotObject(moduleId, me) { var hot = { // ...... 省略代码 ...... active: true, accept: function (dep, callback, errorHandler) {// ...... 省略代码 ......}, // ...... 省略代码 ...... check: hotCheck, apply: hotApply, // ...... 省略代码 ...... data: currentModuleData[moduleId] }; currentChildModule = undefined; return hot; } hotCheck
1.
module.hot.check
最终会触发hotCheck()
办法
2.__webpack_require__.hmrM
:先应用旧的hash
值进行hot-update.json
文件的申请,失去update = {c:["main"], m:[], r:[]}
的更新内容function hotCheck(applyOnUpdate) {return setStatus("check") .then(__webpack_require__.hmrM) // 为 fetch("http://localhost:8080/main.fc1c69066ce336693703.hot-update.json") .then(function (update) {// update = {c:["main"], m:[], r:[]} 更新内容 return setStatus("prepare").then(function () {var updatedModules = []; currentUpdateApplyHandlers = []; return Promise.all(Object.keys(__webpack_require__.hmrC).reduce(function ( promises, key ) { // key=jsonp // __webpack_require__.hmrC[key]( // update.c, // update.r, // update.m, // promises, // currentUpdateApplyHandlers, // updatedModules // ); ===> 转化为 jsonp,便于了解 __webpack_require__.hmrC.jsonp(update.c, update.r, update.m, promises, currentUpdateApplyHandlers, updatedModules); // chunkIds, removedChunks, removedModules, promises, applyHandlers, updatedModulesList return promises; }, []) ).then(function () {return waitForBlockingPromises(function () { // 期待所有的 promise 更新实现 if (applyOnUpdate) {// hotCheck(true) return internalApply(applyOnUpdate); } else {return setStatus("ready").then(function () {return updatedModules;}); } }); }); }); }); } __webpack_require__.hmrM = () => {if (typeof fetch === "undefined") throw new Error("No browser support: need fetch API"); // 保留的是 client 客户端的域名:// __webpack_require__.p = "http://localhost:8080/" // 保留的是上一次的 hash 值:// __webpack_require__.h = () => ("fc1c69066ce336693703") // __webpack_require__.hmrF = () => ("main." + __webpack_require__.h() + ".hot-update.json"); // fetch("http://localhost:8080/main.fc1c69066ce336693703.hot-update.json") return fetch(__webpack_require__.p + __webpack_require__.hmrF()).then((response) => {return response.json(); }); }; 3.
hot-update.json
回调实现后,触发__webpack_require__.hmrC.jsonp()
办法执行:
(1) 创立http://localhost:8080/main.f1bcf354bbddd26daa90.hot-update.js
的 promise 申请,并且退出到 promise 数组中
(2) 创立对应的全局执行函数,期待main.xxx.hot-update.js
回调后,执行对应的module
代码的缓存并且触发对应promise
的resolve
申请,从而顺利回调internalApply()
办法// $hmrDownloadUpdateHandlers$.$key$ => runtime 转化为:__webpack_require__.hmrC.jsonp __webpack_require__.hmrC.jsonp = function (chunkIds, ...) {applyHandlers.push(applyHandler); chunkIds.forEach(function (chunkId) { // 拼接 jsonp 申请的 url promises.push($loadUpdateChunk$(chunkId, updatedModulesList)); }); }; // 拼接 jsonp 申请的 url var waitingUpdateResolves = {}; function loadUpdateChunk(chunkId, updatedModulesList) {return new Promise((resolve, reject) => {waitingUpdateResolves[chunkId] = resolve; // __webpack_require__.hu = ""+ chunkId +"."+ __webpack_require__.h() +".hot-update.js"; var url = __webpack_require__.p + __webpack_require__.hu(chunkId); __webpack_require__.l(url, loadingEnded); }); } // document.body.appendChild(new Script()),正式发动 get 申请(jsonp 申请) var inProgress = {}; __webpack_require__.l = (url, done, key, chunkId) => {inProgress[url] = [done]; var onScriptComplete = (prev, event) => {var doneFns = inProgress[url]; delete inProgress[url]; script.parentNode && script.parentNode.removeChild(script); doneFns && doneFns.forEach((fn) => (fn(event))); }; script.onload = onScriptComplete.bind(null, script.onload); needAttach && document.head.appendChild(script); }; // 返回的 http://localhost:8080/main.f1bcf354bbddd26daa90.hot-update.js 是一个 webpackHotUpdatewebpack_inspect 马上执行的函数 // 如下图所示 self["webpackHotUpdatewebpack_inspect"] = (chunkId, moreModules, runtime) => {for (var moduleId in moreModules) {if (__webpack_require__.o(moreModules, moduleId)) {currentUpdate[moduleId] = moreModules[moduleId]; if (currentUpdatedModulesList) currentUpdatedModulesList.push(moduleId); } } if (runtime) currentUpdateRuntime.push(runtime); if (waitingUpdateResolves[chunkId]) {waitingUpdateResolves[chunkId](); waitingUpdateResolves[chunkId] = undefined; } }; 客户端 -module.hot.apply
1. 解决所有波及模块的热更新策略,有的是当依赖的模块产生更新后,这个模块须要通过 从新加载 去实现本模块的全量更新,有的是局部热更新,有的是不更新
2. 进行须要 update 的模块的热更新解决
3. 进行须要 delete 的模块的热更新解决hotApply 数组遍历解决
function internalApply(options) { // 这里的 currentUpdateApplyHandlers 存储的是下面 jsonp 申请 js 文件所创立的 callback var results = currentUpdateApplyHandlers.map(function (handler) {return handler(options); }); currentUpdateApplyHandlers = undefined; results.forEach(function (result) {if (result.dispose) result.dispose();}); var outdatedModules = []; results.forEach(function (result) {if (result.apply) { // 这里的 result 的是下面 jsonp 申请 js 文件所创立的 callback 所返回 Object 的 apply 办法 var modules = result.apply(reportError); if (modules) {for (var i = 0; i < modules.length; i++) {outdatedModules.push(modules[i]); } } } }); return Promise.all([disposePromise, applyPromise]).then(function () {return setStatus("idle").then(function () {return outdatedModules;}); }); } applyHandler- 理论的 hotApply 解决逻辑
applyHandler()办法位于 /node_modules/webpack/lib/hmr/JavascriptHotModuleReplacement.runtime.js
总体执行逻辑概括
1. 依据 webpack 配置拼接出以后
moduleId
的热更新策略,比方容许热更新,比方不容许热更新等等
2. 依据热更新策略,拼接多个数据结构,为applay()
办法代码服务
3. 从internalApply()
能够晓得,最终会先执行result.dispose()
,而后再执行result.apply()
办法,
4.dispose()
办法次要执行的逻辑是:
(1)删除缓存数据
(2) 移除之前注册的回调函数
(3) 移除目前 module 与其它 module 的绑定关系 (parent 和 children)
5.apply()
办法次要执行的逻辑是:
(1)更新全局的 window.__webpack_require__对象,存储了所有门路 + 内容的对象
(2) 执行 runtime 代码,比方_webpack_require__.h = ()=> {“xxxxxhash 值 ”}
(3)触发之前 hot.accept 部署了依赖变动时的回调 callBack
(4)从新加载标识_selfAccepted 的 module,这种模块会从新 require 一次第一个步骤 -1:拼接数据结构
1. 依据
getAffectedModuleEffects(moduleId)
整顿出该moduleId
的热更新策略,是否须要热更新
2. 依据多个对象拼凑出dispose
和apply
办法所须要的数据结构function applyHandler(options) { currentUpdateChunks = undefined; // at begin all updates modules are outdated // the "outdated" status can propagate to parents if they don't accept the children var outdatedDependencies = {}; // 应用 module.hot.accept 部署了依赖产生更新后的回调函数 var outdatedModules = []; // 以后过期须要更新的 modules var appliedUpdate = {}; // 筹备更新的 modules for (var moduleId in currentUpdate) {var newModuleFactory = currentUpdate[moduleId]; // 获取之前的配置:该 moduleId 是否容许热更新 var result = getAffectedModuleEffects(moduleId); var doApply = false; var doDispose = false; switch (result.type) { // ... case "accepted": if (options.onAccepted) options.onAccepted(result); doApply = true; break; //... } if (doApply) {appliedUpdate[moduleId] = newModuleFactory; //... 代码省略... 拼凑出 outdatedDependencies 过期的依赖,为上面的 module.hot.accept(moduleId, function() {})做筹备 } if (doDispose) {//... 代码省略... 解决配置为 dispose 的状况} } currentUpdate = undefined; // 依据 outdatedModules 拼凑出须要_selfAccepted=true,即热更新是从新加载一次本人的 module 的数据到 outdatedSelfAcceptedModules 中 var outdatedSelfAcceptedModules = []; for (var j = 0; j < outdatedModules.length; j++) {var outdatedModuleId = outdatedModules[j]; // __webpack_require__.c = __webpack_module_cache__ var module = __webpack_require__.c[outdatedModuleId]; if (module && (module.hot._selfAccepted || module.hot._main) && appliedUpdate[outdatedModuleId] !== warnUnexpectedRequire && !module.hot._selfInvalidated ) {// _requireSelf: function () {// currentParents = me.parents.slice(); // currentChildModule = _main ? undefined : moduleId; // __webpack_require__(moduleId); // }, outdatedSelfAcceptedModules.push({ module: outdatedModuleId, require: module.hot._requireSelf, // 从新加载本人 errorHandler: module.hot._selfAccepted }); } } var moduleOutdatedDependencies; return {dispose: function() {...} apply: function(reportError) {...} }; } 第一个步骤 -2:getAffectedModuleEffects 办法解说
function getAffectedModuleEffects(updateModuleId) {var outdatedModules = [updateModuleId]; var outdatedDependencies = {}; var queue = outdatedModules.map(function (id) { return {chain: [id], id: id }; }); while (queue.length > 0) {var queueItem = queue.pop(); var moduleId = queueItem.id; var chain = queueItem.chain; var module = __webpack_require__.c[moduleId]; if (!module || (module.hot._selfAccepted && !module.hot._selfInvalidated)) {continue;} // ************ 解决不热更新的状况 ************ if (module.hot._selfDeclined) { return { type: "self-declined", chain: chain, moduleId: moduleId }; } if (module.hot._main) { return { type: "unaccepted", chain: chain, moduleId: moduleId }; } // ************ 解决不热更新的状况 ************ for (var i = 0; i < module.parents.length; i++) { // module.parents= 依赖这个模块的 modules // 遍历所有依赖这个模块的 modules var parentId = module.parents[i]; var parent = __webpack_require__.c[parentId]; if (!parent) continue; if (parent.hot._declinedDependencies[moduleId]) { // 如果依赖这个模块的 parentModule 设置了不理睬以后 moduleId 热更新的策略,则不解决该 parentModule return { type: "declined", chain: chain.concat([parentId]), moduleId: moduleId, parentId: parentId }; } // 如果曾经蕴含在筹备更新的队列中,则不反复增加 if (outdatedModules.indexOf(parentId) !== -1) continue; if (parent.hot._acceptedDependencies[moduleId]) {if (!outdatedDependencies[parentId]) outdatedDependencies[parentId] = []; // TODO 这个 parentModule 设置了监听其依赖 module 的热更新 addAllToSet(outdatedDependencies[parentId], [moduleId]); continue; } delete outdatedDependencies[parentId]; outdatedModules.push(parentId); // 增加该 parentModuleId 到队列中,筹备更新 // 退出该 parentModuleId 到队列中,进行下一轮循环,把 parentModule 的相干 parent 也退出到更新中 queue.push({chain: chain.concat([parentId]), id: parentId }); } } return { type: "accepted", moduleId: updateModuleId, outdatedModules: outdatedModules, outdatedDependencies: outdatedDependencies }; } function addAllToSet(a, b) {for (var i = 0; i < b.length; i++) {var item = b[i]; if (a.indexOf(item) === -1) a.push(item); } } 第二个步骤:dispose 办法
dispose: function () {currentUpdateRemovedChunks.forEach(function (chunkId) {delete installedChunks[chunkId]; }); currentUpdateRemovedChunks = undefined; var idx; var queue = outdatedModules.slice(); while (queue.length > 0) {var moduleId = queue.pop(); var module = __webpack_require__.c[moduleId]; if (!module) continue; var data = {}; // Call dispose handlers: 回调注册的 disposeHandlers var disposeHandlers = module.hot._disposeHandlers; for (j = 0; j < disposeHandlers.length; j++) {disposeHandlers[j].call(null, data); } // __webpack_require__.hmrD = currentModuleData 置为空 __webpack_require__.hmrD[moduleId] = data; // disable module (this disables requires from this module) module.hot.active = false; // remove module from cache: 删除 module 的缓存数据 delete __webpack_require__.c[moduleId]; // when disposing there is no need to call dispose handler: 删除其它模块对该 moduleId 的 accept 回调 delete outdatedDependencies[moduleId]; // remove "parents" references from all children: // 解除 moduleId 援用的其它模块跟 moduleId 的绑定关系,跟上面的解除关系是相互补充的 // 一个是 children,一个是 parent for (j = 0; j < module.children.length; j++) {var child = __webpack_require__.c[module.children[j]]; if (!child) continue; idx = child.parents.indexOf(moduleId); if (idx >= 0) {child.parents.splice(idx, 1); } } } // remove outdated dependency from module children: // 解除援用该 moduleId 的模块跟 moduleId 的绑定关系,能够了解为 moduleId.parent 删除 children,跟下面的解除关系是相互补充的 // 一个是 children,一个是 parent var dependency; for (var outdatedModuleId in outdatedDependencies) {module = __webpack_require__.c[outdatedModuleId]; if (module) { moduleOutdatedDependencies = outdatedDependencies[outdatedModuleId]; for (j = 0; j < moduleOutdatedDependencies.length; j++) {dependency = moduleOutdatedDependencies[j]; idx = module.children.indexOf(dependency); if (idx >= 0) module.children.splice(idx, 1); } } } } 第三个步骤:apply 办法
apply: function (reportError) { // insert new code for (var updateModuleId in appliedUpdate) { // __webpack_require__.m = __webpack_modules__ // 更新全局的 window.__webpack_require__对象,存储了所有门路 + 内容的对象 __webpack_require__.m[updateModuleId] = appliedUpdate[updateModuleId]; } // run new runtime modules // 执行 runtime 代码,比方_webpack_require__.h = ()=> {"xxxxxhash 值"} for (var i = 0; i < currentUpdateRuntime.length; i++) {currentUpdateRuntime[i](__webpack_require__); } // call accept handlers:触发之前 hot.accept 部署了依赖变动时的回调 callBack for (var outdatedModuleId in outdatedDependencies) {var module = __webpack_require__.c[outdatedModuleId]; if (module) { moduleOutdatedDependencies = outdatedDependencies[outdatedModuleId]; var callbacks = []; var errorHandlers = []; var dependenciesForCallbacks = []; for (var j = 0; j < moduleOutdatedDependencies.length; j++) {var dependency = moduleOutdatedDependencies[j]; var acceptCallback = module.hot._acceptedDependencies[dependency]; var errorHandler = module.hot._acceptedErrorHandlers[dependency]; if (acceptCallback) {if (callbacks.indexOf(acceptCallback) !== -1) continue; callbacks.push(acceptCallback); errorHandlers.push(errorHandler); dependenciesForCallbacks.push(dependency); } } for (var k = 0; k < callbacks.length; k++) {callbacks[k].call(null, moduleOutdatedDependencies); } } } // Load self accepted modules:从新加载标识_selfAccepted 的 module,这种模块会从新 require 一次 for (var o = 0; o < outdatedSelfAcceptedModules.length; o++) {var item = outdatedSelfAcceptedModules[o]; var moduleId = item.module; item.require(moduleId); } return outdatedModules; } 3. 概括总结
1. 构建 bundle.js 的时候,退出一段 HMR runtime 的 js 和一段和本地服务沟通的 WebSocket 的相干 js
2. 文件批改会触发 webpack 从新构建,服务器通过向浏览器发送更新音讯,浏览器通过 jsonp 拉取更新的模块文件,jsonp 回调触发模块热替换逻辑二. 其它问题总结
1. hotApply 是如何运行的?
1. 先进行
dipose()
进行缓存数据的移除
2. 而后再调用apply()
进行数据的更新,以及对应注册的 accept handler 回调如果没有在 accept 中写对应的业务代码,热更新后尽管代码曾经变动,然而并不会引起曾经更新的 module.parent 或者 module.children 的办法从新执行一遍,即不会从新从曾经更新的 module 从新获取值
2. hotApply 中 outdatedModules、appliedUpdate、outdatedSelfAcceptedModules、moduleOutdatedDependencies 有什么作用?
outdatedModules
须要更新的 modules,比方你扭转了 test1.js,test2.js,那么这里的
outDatedModules=["./test1.js", "./test2.js"]
outdatedSelfAcceptedModules
只有在 module 注册了 accetp()这个办法,能力_selfAccepted=true
outdatedModules
解析配置失去的_selfAccepted=true
的modules
outdatedDependencies
只有在 module.parent 注册了 accept(“[以后的 moduleId]”, ()=> {})才有这层关系
存储的是key-value
的对象,其中key
代表的是以后要更新的module
的 parent,value
代表以后的 modulemoduleOutdatedDependencies
outdatedDependencies
的values
,用于在dispose()
和apply()
中进行短暂缓存数据应用appliedUpdate
缓存
moduleId
的数据,如果是apply
则缓存新的代码,如果是dipose
模式,则缓存一个正告 functionif (doApply) {appliedUpdate[moduleId] = newModuleFactory; } if (doDispose) {appliedUpdate[moduleId] = warnUnexpectedRequire; } 3. 本地文件扭转,webpack 是如何晓得并且触发编译的?
1. 将打包内容放入内存中,初始化时触发 compiler 编译,compiler 编译完结时进行文件系统的监听
2. 如果文件发生变化,会触发从新编译的回调
3. 编译实现后,从新注册监听