銜接上文荞下,獲取修改文件的路徑后伶选,我們便可以開始開發(fā)熱更新插件了
思路
- 文件監(jiān)聽到修改文件的路徑,執(zhí)行回調(diào)尖昏,執(zhí)行打包命令仰税,帶上包名
- 增量打包更改的文件
- 構(gòu)建工具埋點update事件,熱更新在update時通知前端
- 前端接收變更內(nèi)容抽诉,增量更新
思考
- 增量打包前面已經(jīng)實現(xiàn)了
- 埋點的依據(jù)是是否為增量打包陨簇,即是否傳入了入口文件和修改文件
- 前端如何直到服務端發(fā)生更新?
1.用戶觸發(fā)行為迹淌,也就是事件監(jiān)聽
2.遞歸setTimeout
3.setIntVal
4.http長連接
5.websocket
毫無疑問河绽,我們選擇websocket通知前端更新 - 通知什么內(nèi)容呢己单?
1.更改的代碼
2.新產(chǎn)生的包名 - 前端獲取代碼后如何操作更新呢?
此處做法是有入口文件重新執(zhí)行一遍耙饰,由于未更改的包都有module緩存纹笼,所以會使用緩存的值,我們把修改文件的代碼塊替換苟跪,重新執(zhí)行一遍即可獲取新module
實現(xiàn)
構(gòu)建工具埋點
/**
* 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);
}
構(gòu)建工具添加update事件插件處理
即http://www.reibang.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}
//執(zhí)行入口文件代碼
formatModuleFuncCache[${moduleFuncCache.length - 1}]()
`;
const ws = new WebSocket("ws://localhost:8080");
ws.on("open", () => {
ws.send(JSON.stringify({ code: updateCode }));
});
}
);
}
}
我們可以看到牍疏,在打包時會初始化該插件,則開啟一個websocket服務拨齐,在update時依然會初始化該實例鳞陨,會報錯端口占用(被之前的websocket),我選擇攔截報錯瞻惋。
熱更新時會觸發(fā)update事件厦滤,執(zhí)行我們所有插件注冊的update回調(diào),此時我們獲取更改的路徑歼狼,讀取其內(nèi)容掏导,替換原來的模塊代碼,并發(fā)送給前端重新執(zhí)行羽峰。
測試
由圖我們可以看到:修改文件并保存趟咆,觸發(fā)文件監(jiān)聽,內(nèi)部會重新打包修改內(nèi)容梅屉,然后將變更內(nèi)容通過websocket發(fā)送給前端值纱,前端只處理變更內(nèi)容,不會刷新頁面(input框的文字還保留)坯汤,至此我們已經(jīng)實現(xiàn)了一個commonjs構(gòu)建工具最常用的功能虐唠!