webpack hot-module-replacement 原理&踩坑

webpack hot-module-replacement 原理&踩坑

原由

最近在作san框架的熱更新,記錄一下webpack HMR的原理和小坑。vue

什麼是HMR?

熱更新是webpack的一個特性,經過無刷新實現代碼更新。webpack

在HMR以前,大多數開發體驗是live reload,保存後自動刷新瀏覽器,已是比刀耕火種的年代強不少了,可是自從某天在油管上看到dan神的redux時間穿梭,瞬間被驚豔到(固然,HMR已是這以前好久就出現了)。web

HMR大幅提升了開發體驗,只更新變動內容,調整樣式迅速,避免了大部分的網絡請求、瀏覽器從新渲染、app解析編譯顯示,讓咱們來看下他是如何作到的。json

hmr基本法

概念

compile: webpack的核心。js編譯、拆包。
hmr-server: 創建鏈接並完成模塊熱更新的推送。
bundle-server: 靜態服務器。
bundle.js: client端。
hmr-runtime: 注入到bundle.js中的代碼。redux

更新流程

熱更新開啓後,當webpack打包時,會向client端注入一段HMR runtime代碼,同時server端(webpack-dev-server或是webpack-hot-middware)啓動了一個HMR服務器,它經過websocket和注入的runtime進行通訊。api

當webpack檢測到文件修改後,會從新構建,並經過ws向client端發送更新消息,瀏覽器經過jsonp拉取更新過的模塊,回調觸發模塊熱更新邏輯。瀏覽器

1.修改了一個或多個文件。
2.文件系統接收更改並通知Webpack。
3.Webpack從新編譯構建一個或多個模塊,並通知HMR服務器進行了更新。
4.HMR Server使用websockets通知HMR Runtime須要更新。HMR運行時經過HTTP請求這些更新(jsonp)。
5.HMR運行時替換更新中的模塊,若是肯定這些模塊沒法更新,則觸發整個頁面刷新(這是個大坑。。)。服務器

觸發頁面刷新

// webpack/hot/dev-server
if(module.hot) {
    var lastHash;
    //__webpack_hash__是每次編譯的hash值是全局的
    //Only available with the HotModuleReplacementPlugin or the ExtendedAPIPlugin
    var upToDate = function upToDate() {
        return lastHash.indexOf(__webpack_hash__) >= 0;
    };
    var check = function check() {
   // check([autoApply], callback: (err: Error, outdatedModules: Module[]) => void
   // If autoApply is truthy the callback will be called with all modules that were disposed. apply() is automatically called with autoApply as options parameter.(傳入哪些代碼已經被更新的模塊)
   //If autoApply is not set the callback will be called with all modules that will be disposed on apply(). (不是true那麼傳入的是哪些須要被apply處理的模塊)
        module.hot.check(true).then(function(updatedModules) {
            //檢查全部要更新的模塊,若是沒有模塊要更新那麼回調函數就是null
            if(!updatedModules) {
                console.warn("[HMR] Cannot find update. Need to do a full reload!");
                console.warn("[HMR] (Probably because of restarting the webpack-dev-server)");
                window.location.reload();
                return;
            }
            //若是還有更新
            if(!upToDate()) {
                check();
            }
            require("./log-apply-result")(updatedModules, updatedModules);
            //已經被更新的模塊都是updatedModules
            if(upToDate()) {
                console.log("[HMR] App is up to date.");
            }

        }).catch(function(err) {
            var status = module.hot.status();
            //若是報錯直接全局reload
            if(["abort", "fail"].indexOf(status) >= 0) {
                console.warn("[HMR] Cannot apply update. Need to do a full reload!");
                console.warn("[HMR] " + err.stack || err.message);
                window.location.reload();
            } else {
                console.warn("[HMR] Update failed: " + err.stack || err.message);
            }
        });
    };
    var hotEmitter = require("./emitter");
    //獲取MyEmitter對象
    hotEmitter.on("webpackHotUpdate", function(currentHash) {
        lastHash = currentHash;
        if(!upToDate() && module.hot.status() === "idle") {
            //調用module.hot.status方法獲取狀態
            console.log("[HMR] Checking for updates on the server...");
            check();
        }
    });
    console.log("[HMR] Waiting for update signal from WDS...");
} else {
    throw new Error("[HMR] Hot Module Replacement is disabled.");
}

正常狀況下,hmr只會更新模塊,不會觸發頁面刷新。websocket

可是當bundle.js中的代碼拋出異常時,若是開發者沒有手動接收並處理,這個錯誤會冒泡到webpack-hmr-runtime中。網絡

runtime接收error後會console.log一些信息並當即刷新,一般狀況下是沒辦法看到那些log的,由於太快了。

// vue-hot-reload-api.js
// 不得不說,這個一開始確實沒搞懂是爲啥要包一層
// 本身實現的時候才知道,當有error彈出時
// 若是不手動這樣接住error,webpack會接到而後當即location.reload()
// 根原本不及看reload以前給出的提示
// 因此要手動處理下error
function tryWrap (fn) {
  return function (id, arg) {
    try { fn(id, arg) } catch (e) {
      console.error(e)
      console.warn('Something went wrong during Vue component hot-reload. Full reload required.')
    }
  }
}

因此開發者須要自定義一個相似的高階函數手動處理下錯誤,防止看不到錯誤信息而刷新。

反作用

模塊的熱更新是好事,可是老模塊仍然有可能在client端留下了痕跡。試想一個組件被熱更新後,若是不處理以前的組件,那麼新老兩個組件都會在瀏覽器中出現。

因此別忘了在module.hot.accept中清除掉舊的組件。
相似的問題還有不少,事件綁定、手動插入而且沒有銷燬的dom、定時器等,記得把這些反作用一塊兒幹掉。

若是作不到的話,老老實實刷新你的瀏覽器吧。

參考

webpack官方文檔

Understanding Webpack HMR

webpack-dev-server原理分析與HMR實現

相關文章
相關標籤/搜索