在web開發中,webpack的hot module replacement(HMR)功能是能夠向運行狀態的應用程序定向的注入並更新已經改變的modules。它的出現能夠避免像LiveReload那樣,任意文件的改變而刷新整個頁面。html
這個特性能夠極大的提高開發擁有運行狀態,以及資源文件廣泛過多的前端應用型網站的效率。完整介紹能夠看官網文檔前端
本文是先從使用者的角度去介紹這個功能,而後從設計者的角度去分析並拆分須要實現的功能和實現的一些細節。node
對於使用者來講,體驗到這個功能須要如下的配置。linux
webpack.config.js:webpack
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); module.exports = { entry: { app: './src/index.js' }, devServer: { contentBase: './dist', hot: true, }, plugins: [ new webpack.HotModuleReplacementPlugin(), new HtmlWebpackPlugin() ], output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') } };
代碼: index.js 依賴print.js,使用module.hot.accept接受print.js的更新:git
import './print'; if (module.hot) { module.hot.accept('./print', function () { console.log('i am updated'); }) }
改變print.js代碼:github
console.log('print2') console.log('i am change');
此時服務端向瀏覽器發送socket信息,瀏覽器收到消息後,開始下載以hash爲名字的下載的json,jsonp文件,以下圖:web
瀏覽器會下載對應的hot-update.js,並注入運行時的應用中:json
webpackHotUpdate(0,{ /***/ 30: /***/ (function(module, exports) { console.log('print2') console.log('i am change'); /***/ }) })
0 表明着所屬的chunkid,30表明着所屬的moduleid。bootstrap
替換完以後,執行module.hot.accept的回調函數,以下圖:
簡單來說,開啓了hmr功能以後,處於accepted狀態的module的改動將會以jsonp的形式定向的注入到應用程序中。
一張圖來表示HMR的總體流程:
當翻開bundle.js的時候,你會發現Runtime代碼多了許多如下的代碼:
/******/ function hotDownloadUpdateChunk(chunkId) { /******/ ... /******/ } /******/ function hotDownloadManifest(requestTimeout) { /******/ ... /******/ } /****** /******/ function hotSetStatus(newStatus) { /******/ ... /******/ } /******/
打包的時候,明明只引用了4個文件,可是整個打包文件卻有30個modules之多:
/* 30 */ /***/ (function(module, exports) { console.log('print3') console.log('i am change'); /***/ })
到如今你可能會有如下幾個疑問:
以上問題,能夠從三個不一樣的角度去解決。server,webpack,brower。
entry:{app:'./src/index.js'}
,轉換爲
entry:{app:['/Users/zhujian/Documents/workspace/webpack/webpack-demos-master/node_modules/_webpack-dev-server@2.11.2@webpack-dev-server/client/index.js?http://localhost:8082'],'webpack/hot/dev-server','./src/index.js'}
構建業務代碼時,附帶上socketjs,hot代碼。
Server.js
if (this.hot) this.sockWrite([conn], 'hot');
瀏覽器
hot: function hot() { _hot = true; log.info('[WDS] Hot Module Replacement enabled.'); }
監聽編譯器的生命週期模塊。
socket
compiler.plugin('compile', invalidPlugin); compiler.plugin('invalid', invalidPlugin); compiler.plugin('done', (stats) => { this._sendStats(this.sockets, stats.toJson(clientStats)); this._stats = stats; });
資源文件鎖定
context.compiler.plugin("done", share.compilerDone); context.compiler.plugin("invalid", share.compilerInvalid); context.compiler.plugin("watch-run", share.compilerInvalid); context.compiler.plugin("run", share.compilerInvalid);
MainTemplate增長module-obj,module-require事件
module-obj事件負責生成如下代碼
/******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {}, /******/ hot: hotCreateModule(moduleId), /******/ parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp), /******/ children: [] /******/ }; /******/
module-require事件負責生成如下代碼
/******/ modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
新增Watching類支持watch模式,並結合watchpack監聽文件變化。
class Watching { .... }
新增updateHash實現
updateHash(hash) { this.updateHashWithSource(hash); this.updateHashWithMeta(hash); super.updateHash(hash); }
新增updateHash實現
updateHash(hash) { hash.update(`${this.id} `); hash.update(this.ids ? this.ids.join(",") : ""); hash.update(`${this.name || ""} `); this._modules.forEach(m => m.updateHash(hash)); }
增長createHash方法,默認調用md5計算compilation hash。調用依賴樹module,chunk的updateHash方法。
createHash() { .... }
如:
if(module.hot){}
編譯後
if(true){}
entry:{app:['/Users/zhujian/Documents/workspace/webpack/webpack-demos-master/node_modules/_webpack-dev-server@2.11.2@webpack-dev-server/client/index.js?http://localhost:8082'],'webpack/hot/dev-server','./src/index.js'}
打包後
/* 5 */ /***/ (function(module, exports, __webpack_require__) { // webpack-dev-server/client/index.js __webpack_require__(6); //webpack/hot/dev-server __webpack_require__(26); // .src/index.js module.exports = __webpack_require__(28); /***/ })
compilation.plugin("record", function(compilation, records) { if(records.hash === this.hash) return; records.hash = compilation.hash; records.moduleHashs = {}; this.modules.forEach(module => { const identifier = module.identifier(); const hash = require("crypto").createHash("md5"); module.updateHash(hash); records.moduleHashs[identifier] = hash.digest("hex"); }); records.chunkHashs = {}; this.chunks.forEach(chunk => { records.chunkHashs[chunk.id] = chunk.hash; }); records.chunkModuleIds = {}; this.chunks.forEach(chunk => { records.chunkModuleIds[chunk.id] = chunk.mapModules(m => m.id); }); });
compilation.plugin("additional-chunk-assets", function() { .... this.assets[filename] = source; });
初始化runtime,將全部附加的模塊代碼統一增長parents,children等屬性。並提供check,以及apply方法去管理hmr的生命週期。
module.hot.check(true).then(function(updatedModules) { .... })
本人的簡易版webpack實現simple-webpack
(完)