热更新(HMR)相关原理介绍

2022-09-29 19:15:15 浏览数 (1)

在有些项目中,我们会遇到某些页面由特别多的模块组成,比如m1,m2,m3...mn

在开发工程中,如果我们修改了其中一个模块,页面都是重新加载,那么之前填写的数据都清空了。测试整体流程又需要重新填写数据,过程比较繁琐。那能否实现某个模块更新而又不清空其他模块的数据呢,其实就是webpack 实现的热更新功能。

定义

热更新(HMR: Hot Module Replacement)指当对代码修改并保存后,webpack将会对代码进行重新打包,并将改动的模块发送到浏览器端,浏览器用新的模块替换掉旧的模块,去实现局部更新页面。我们看下面的一个例子。

与监听代码变更刷新整个页面不同,HMR具有以下两个特征:

  • 不需要刷新页面,
  • 可以保存应用的当前状态

实现原理

要实现更新,需要解决两个问题,一个是服务端能将更新资源实时推送给浏览器;另一个是浏览器实现局部更新。

1.通知浏览器端文件发生改变

在开发中通常使用的http协议,但是HTTP 协议有一个缺陷:通信只能由客户端发起。我们也可以通过轮询来实现,但是效率低下,而且浪费资源。这个时候 websoket[1] 应运而生。websocket的最大特点是服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,实现双向通信息;在webpack 中,就是使用websocket ,以下是具体实现:

代码语言:javascript复制
// server.js
createSocketServer() {
 // 实现一个websocket长链接
    const io = socket(this.server);
    io.on("connection", (socket) => {
    console.log("a new client connect server");

    this.clientSocketList.push(socket);
    socket.on("disconnect", () => {
        let num = this.clientSocketList.indexOf(socket);
        this.clientSocketList = this.clientSocketList.splice(num, 1);
    });
    // 向客户端发送最新的一个编译hash
    socket.emit('hash', this.currentHash);
        // 再向客户端发送一个ok
        socket.emit('ok');
    });
}

代码语言:javascript复制
// client/index.js
const onSocketMessage = {
 hash(hash) {
        console.log("hash",hash);
        currentHash = hash;// 获取最新hash
    },
 ok() {
        console.log("ok");  
        reloadApp();// 开始热更新
    },
 connect() {
        console.log("client connect successful");
    }
};

2.浏览器端实现更新

在浏览器端收到服务端资源更新通知后,会进行以下步骤:

  • 使用XHR 拉取更新的模块和文件hash
代码语言:javascript复制
let hotDownloadManifest = () => {
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        let hotUpdatePath = `${lastHash}.hot-update.json`
        xhr.open("get", hotUpdatePath);
        xhr.onload = () => {
            let hotUpdate = JSON.parse(xhr.responseText);
            // {"h":"58ddd9a7794ab6f4e750","c":{"main":true}}
            resolve(hotUpdate);
        };
        xhr.onerror = (error) => {
            reject(error);
        }
        xhr.send();
    })
}

  • 根据获取的hash 和chunkId拉取最新的js 代码,此时使用的是JSONP,可以即刻执行js代码。
代码语言:javascript复制
let hotDownloadUpdateChunk = (chunkID) => {
    let script = document.createElement("script")
    script.charset = "utf-8";
    //chunkID.xxxlasthash.hot-update.js
    script.src = `${chunkID}.${lastHash}.hot-update.js`
    document.head.appendChild(script);
}

  • webpackHotUpdate实现热更新
代码语言:javascript复制
window.webpackHotUpdate = (chunkID, moreModules) => {
      // 循环新拉来的模块
    Object.keys(moreModules).forEach(moduleID => {
        // 1、通过__webpack_require__.c 模块缓存可以找到旧模块
        let oldModule = __webpack_require__.c[moduleID];

        // 2、更新__webpack_require__.c,利用moduleID将新的拉来的模块覆盖原来的模块
        let newModule = __webpack_require__.c[moduleID] = {
            i: moduleID,
            l: false,
            exports: {},
            hot: hotCreateModule(moduleID),
            parents: oldModule.parents,
            children: oldModule.children
        };

        // 3、执行最新编译生成的模块代码
        moreModules[moduleID].call(newModule.exports, newModule, newModule.exports, __webpack_require__);
        newModule.l = true;

        // 4、让父模块中存储的_acceptedDependencies执行
        newModule.parents && newModule.parents.forEach(parentID => {
            let parentModule = __webpack_require__.c[parentID];
            parentModule.hot._acceptedDependencies[moduleID] && parentModule.hot._acceptedDependencies[moduleID]()
        });
    })
}

总结

本文主要讲了热更新的实现要点:使用websoket 进行通信;使用jsonp拉取更新模块执行。如果想了解更多,欢迎读者去阅读相关源码。

[1]阮一峰,WebSocket 教程: http://www.ruanyifeng.com/blog/2017/05/websocket.html

[2]https://zhuanlan.zhihu.com/p/138446061

[3]https://juejin.cn/post/6844904020528594957#heading-59

[4]https://segmentfault.com/a/1190000020310371

0 人点赞