實(shí)現(xiàn)構(gòu)建工具之熱更新插件

銜接上文,獲取修改文件的路徑后,我們便可以開(kāi)始開(kāi)發(fā)熱更新插件了

思路

  • 文件監(jiān)聽(tīng)到修改文件的路徑,執(zhí)行回調(diào),執(zhí)行打包命令,帶上包名
  • 增量打包更改的文件
  • 構(gòu)建工具埋點(diǎn)update事件,熱更新在update時(shí)通知前端
  • 前端接收變更內(nèi)容,增量更新

思考

  • 增量打包前面已經(jīng)實(shí)現(xiàn)了
  • 埋點(diǎn)的依據(jù)是是否為增量打包,即是否傳入了入口文件和修改文件
  • 前端如何直到服務(wù)端發(fā)生更新?
    1.用戶觸發(fā)行為,也就是事件監(jiān)聽(tīng)
    2.遞歸setTimeout
    3.setIntVal
    4.http長(zhǎng)連接
    5.websocket
    毫無(wú)疑問(wèn),我們選擇websocket通知前端更新
  • 通知什么內(nèi)容呢?
    1.更改的代碼
    2.新產(chǎn)生的包名
  • 前端獲取代碼后如何操作更新呢?
    此處做法是有入口文件重新執(zhí)行一遍,由于未更改的包都有module緩存,所以會(huì)使用緩存的值,我們把修改文件的代碼塊替換,重新執(zhí)行一遍即可獲取新module

實(shí)現(xiàn)

構(gòu)建工具埋點(diǎn)

  /**
   * 1.存在更改文件路徑
   * 2.文件非新增文件
   * 3.本地緩存還在,能夠增量更新
   */
  const pathIndex = pathIndexMap[getFilePath(changeFilePath)];
  if (changeFilePath && pathIndex !== undefined && moduleFuncCache.length) {
    moduleFuncCache[pathIndex] = singleCodeSplicing(changeFilePath);

    execHook("update", moduleFuncCache, pathIndex);
  } else {
    // 為每個(gè)require的模塊拼接代碼,為其提供module實(shí)例,并返回module.exports
    codeSplicing(path);
  }

構(gòu)建工具添加update事件插件處理
http://www.itdecent.cn/p/d120cde6dae0的ctx添加update項(xiàng)

  update: {
    pluginMap: {},
    tap(pluginName, callback) {
      this.pluginMap[pluginName] = callback;
    },
  },

插件代碼

class HotModuleReplacementPlugin {
  constructor() {
    const wsConnectList = [];

    //接收
    const wss = new WebSocketServer({ port: 8080 });
    wss.on("connection", (ws) => {
      wsConnectList.push(ws);
      ws.on("message", (data) => {
        // fs.writeFileSync("./log.conf", data);
        log("messagedsadas");
        log(data);
        wsConnectList.forEach((curWs) => {
          curWs.send(data);
        });
      });
    });

    wss.on("error", (err) => {
      console.log(err);
    });
  }

  apply(compiler) {
    const that = this;
    compiler.update.tap(
      "HotModuleReplacementPlugin",
      (moduleFuncCache, pathIndex) => {
        const newCode = moduleFuncCache[pathIndex];
        const updateCode = `
          moduleCache[${pathIndex}] = undefined
          formatModuleFuncCache[${pathIndex}] = ${newCode}
          //執(zhí)行入口文件代碼
          formatModuleFuncCache[${moduleFuncCache.length - 1}]()
        `;

        const ws = new WebSocket("ws://localhost:8080");
        ws.on("open", () => {
          ws.send(JSON.stringify({ code: updateCode }));
        });
      }
    );
  }
}

我們可以看到,在打包時(shí)會(huì)初始化該插件,則開(kāi)啟一個(gè)websocket服務(wù),在update時(shí)依然會(huì)初始化該實(shí)例,會(huì)報(bào)錯(cuò)端口占用(被之前的websocket),我選擇攔截報(bào)錯(cuò)。
熱更新時(shí)會(huì)觸發(fā)update事件,執(zhí)行我們所有插件注冊(cè)的update回調(diào),此時(shí)我們獲取更改的路徑,讀取其內(nèi)容,替換原來(lái)的模塊代碼,并發(fā)送給前端重新執(zhí)行。

測(cè)試

SDGIF_Rusult_1.gif

由圖我們可以看到:修改文件并保存,觸發(fā)文件監(jiān)聽(tīng),內(nèi)部會(huì)重新打包修改內(nèi)容,然后將變更內(nèi)容通過(guò)websocket發(fā)送給前端,前端只處理變更內(nèi)容,不會(huì)刷新頁(yè)面(input框的文字還保留),至此我們已經(jīng)實(shí)現(xiàn)了一個(gè)commonjs構(gòu)建工具最常用的功能!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容