关于webpack5:图解webpack5热更新HRM

58次阅读

共计 24006 个字符,预计需要花费 61 分钟才能阅读完成。

前置问题

  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.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.jsWatching.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=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 申请的 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. 依据多个对象拼凑出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. 编译实现后,从新注册监听

正文完
 0