实现构建工具之热更新插件

衔接上文,获取修改文件的路径后,我们便可以开始开发热更新插件了

思路

  • 文件监听到修改文件的路径,执行回调,执行打包命令,带上包名
  • 增量打包更改的文件
  • 构建工具埋点update事件,热更新在update时通知前端
  • 前端接收变更内容,增量更新

思考

  • 增量打包前面已经实现了
  • 埋点的依据是是否为增量打包,即是否传入了入口文件和修改文件
  • 前端如何直到服务端发生更新?
    1.用户触发行为,也就是事件监听
    2.递归setTimeout
    3.setIntVal
    4.http长连接
    5.websocket
    毫无疑问,我们选择websocket通知前端更新
  • 通知什么内容呢?
    1.更改的代码
    2.新产生的包名
  • 前端获取代码后如何操作更新呢?
    此处做法是有入口文件重新执行一遍,由于未更改的包都有module缓存,所以会使用缓存的值,我们把修改文件的代码块替换,重新执行一遍即可获取新module

实现

构建工具埋点

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

    execHook("update", moduleFuncCache, pathIndex);
  } else {
    // 为每个require的模块拼接代码,为其提供module实例,并返回module.exports
    codeSplicing(path);
  }

构建工具添加update事件插件处理
即https://www.jianshu.com/p/d120cde6dae0的ctx添加update项

  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}
          //执行入口文件代码
          formatModuleFuncCache[${moduleFuncCache.length - 1}]()
        `;

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

我们可以看到,在打包时会初始化该插件,则开启一个websocket服务,在update时依然会初始化该实例,会报错端口占用(被之前的websocket),我选择拦截报错。
热更新时会触发update事件,执行我们所有插件注册的update回调,此时我们获取更改的路径,读取其内容,替换原来的模块代码,并发送给前端重新执行。

测试

SDGIF_Rusult_1.gif

由图我们可以看到:修改文件并保存,触发文件监听,内部会重新打包修改内容,然后将变更内容通过websocket发送给前端,前端只处理变更内容,不会刷新页面(input框的文字还保留),至此我们已经实现了一个commonjs构建工具最常用的功能!

你可能感兴趣的:(实现构建工具之热更新插件)