前置问题

  1. 本地文件扭转,webpack是如何晓得并且触发编译的?
  2. 浏览器是如何晓得本地代码从新编译,并且迅速申请了新生成的文件的?
  3. webpack本地服务器是如何告知浏览器?
  4. 浏览器取得这些文件又是如何热更新的?热更新的流程是什么?

前置知识点

一. 代码扭转时主动编译的几种办法

摘录自 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.jswebpack/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. 流程图文字剖析

初始化:本地服务器和客户端初始化
  1. Server:new Server()后会间接调用server.start(),进行服务的启动
  2. Client:初始化过程中注入webpack-dev-server/client/index.jswebpack/hot/dev-server.js到入口文件中
  3. Server:在Server.js中,进行webpack-dev-middleware插件的注册,触发编译以及文件变动的监听
  4. Server:应用express开启本地node服务器
  5. 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.jssetupDevMiddleware() {   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.jsfunction wdm() {   const context = { compiler };   setupOutputFileSystem(context); // Start watching   context.compiler.watch(watchOptions, errorHandler);}// node_modules/webpack/lib/Compiler.jswatch(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.jscompile(callback) {   this.hooks.make.callAsync(compilation, err => {}); //触发编译}
    本地服务端-文件变动运行逻辑

    Server.jsWatching.js注册了文件内存零碎的监听,文件发生变化时,会触发从新编译

    // node_modules/webpack/lib/webpack.jsnew NodeEnvironmentPlugin({     infrastructureLogging: options.infrastructureLogging}).apply(compiler);// node_modules/webpack/lib/node/NodeEnvironmentPlugin.jscompiler.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.jscompile(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=hashtype=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,进行createRequirecreateModuleHotObject
    createRequire

    构建以后requestparentchildren,实质是在require的根底上保留各个模块之间的依赖关系,为前面的热更新做筹备,因为一个文件的更新必然波及到另外依赖模块的相干更新

    createModuleHotObject

    构建以后modulehotAPI,前面的热更新都须要通过hotCheckhotApply进行操作

    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代码的缓存并且触发对应promiseresolve申请,从而顺利回调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申请的urlvar 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.依据多个对象拼凑出disposeapply办法所须要的数据结构

    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=truemodules
    outdatedDependencies
    只有在module.parent注册了accept("[以后的moduleId]", ()=> {})才有这层关系
    存储的是key-value的对象,其中key代表的是以后要更新的module的parent,value代表以后的module
    moduleOutdatedDependencies

    outdatedDependenciesvalues,用于在dispose()apply()中进行短暂缓存数据应用

    appliedUpdate

    缓存moduleId的数据,如果是apply则缓存新的代码,如果是dipose模式,则缓存一个正告function

    if (doApply) { appliedUpdate[moduleId] = newModuleFactory;}if (doDispose) { appliedUpdate[moduleId] = warnUnexpectedRequire;}

    3. 本地文件扭转,webpack是如何晓得并且触发编译的?

    1.将打包内容放入内存中,初始化时触发compiler编译,compiler编译完结时进行文件系统的监听
    2.如果文件发生变化,会触发从新编译的回调
    3.编译实现后,从新注册监听