HMR或者hot模式下,启动webpack会在浏览器与服务器之间会建立一个websocket连接,使得浏览器可以和服务端建立全双工通信;当应用程序的代码更新时,会要求HMR runtime检查更新,有更新时,在websoket连接中会返回原文件的hash以及更新后代码的hash,并在[oldhash].hot-update.json文件中返回如下结构:
{
h: "fe94d54f9adaaa2831b2", // new hash, h: hash
c: { // c: chunk, updated chunck
app: true
}
}
json中包含了新的模块标识符的hash以及需要更新的chunk name。拿到新的hash后,还需要更新后的chunk的代码,通过[newhash].hot-update.js拿到对应更新后的代码;最后根据新的hash请求重新打包出来的bundle.js文件;并重新建立websocket以便监测下一次更新;
// http://localhost:3000/app.9b34b0372e9214ced0b7.hot-update.js
// [newhash].hot-update.js
webpackHotUpdate("app",{
/***/ "./index.js":
/*!******************!*\
!*** ./index.js ***!
\******************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = __webpack_require__(/*! react */ "./node_modules/react/index.js");
var _react2 = _interopRequireDefault(_react);
var _reactDom = __webpack_require__(/*! react-dom */ "./node_modules/react-dom/index.js");
__webpack_require__(/*! ./style.css */ "./style.css");
__webpack_require__(/*! ./index.css */ "./index.css");
var _console = __webpack_require__(/*! ./console.js */ "./console.js");
var _console2 = _interopRequireDefault(_console);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var App = function (_Component) {
_inherits(App, _Component);
function App(props) {
_classCallCheck(this, App);
var _this = _possibleConstructorReturn(this, (App.__proto__ || Object.getPrototypeOf(App)).call(this, props));
_this.handleClick = _this.handleClick.bind(_this);
return _this;
}
_createClass(App, [{
key: 'handleClick',
value: function handleClick() {
var fun = function fun() {
return console.log('123');
};
fun();
}
}, {
key: 'render',
value: function render() {
return _react2.default.createElement(
'div',
{ onClick: _console2.default },
'Hello World, I am changed again again...'
);
}
}]);
return App;
}(_react.Component);
(0, _reactDom.render)(_react2.default.createElement(App, null), document.getElementById('root'));
if (true) {
module.hot.accept(/*! ./console.js */ "./console.js", function () {
(0, _console2.default)();
});
}
/***/ })
})
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9pbmRleC5qcyJdLCJuYW1lcyI6WyJBcHAiLCJwcm9wcyIsImhhbmRsZUNsaWNrIiwiYmluZCIsImZ1biIsImNvbnNvbGUiLCJsb2ciLCJteUNvbnNvbGUiLCJDb21wb25lbnQiLCJkb2N1bWVudCIsImdldEVsZW1lbnRCeUlkIiwibW9kdWxlIiwiaG90IiwiYWNjZXB0Il0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7OztBQUFBOzs7O0FBQ0E7O0FBRUE7O0FBQ0E7O0FBQ0E7Ozs7Ozs7Ozs7OztJQUVPQSxHOzs7QUFDTCxlQUFhQyxLQUFiLEVBQW9CO0FBQUE7O0FBQUEsMEdBQ2JBLEtBRGE7O0FBRW5CLFVBQUtDLFdBQUwsR0FBbUIsTUFBS0EsV0FBTCxDQUFpQkMsSUFBakIsT0FBbkI7QUFGbUI7QUFHbkI7Ozs7a0NBQ2M7QUFDZCxVQUFJQyxNQUFNLFNBQU5BLEdBQU07QUFBQSxlQUFNQyxRQUFRQyxHQUFSLENBQVksS0FBWixDQUFOO0FBQUEsT0FBVjtBQUNBRjtBQUNBOzs7NkJBQ1E7QUFDVCxhQUFRO0FBQUE7QUFBQSxVQUFLLFNBQVNHLGlCQUFkO0FBQUE7QUFBQSxPQUFSO0FBQ0E7Ozs7RUFYaUJDLGdCOztBQWNuQixzQkFDQyw4QkFBQyxHQUFELE9BREQsRUFFQ0MsU0FBU0MsY0FBVCxDQUF3QixNQUF4QixDQUZEOztBQUtBLElBQUlDLElBQUosRUFBZ0I7QUFDZkEsU0FBT0MsR0FBUCxDQUFXQyxNQUFYLENBQWtCLGtDQUFsQixFQUFrQyxZQUFZO0FBQzdDO0FBQ0EsR0FGRDtBQUdBLEMiLCJmaWxlIjoiYXBwLjliMzRiMDM3MmU5MjE0Y2VkMGI3LmhvdC11cGRhdGUuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgUmVhY3QsIHtDb21wb25lbnR9IGZyb20gJ3JlYWN0JztcclxuaW1wb3J0IHtyZW5kZXJ9IGZyb20gJ3JlYWN0LWRvbSc7XHJcblxyXG5pbXBvcnQgJy4vc3R5bGUuY3NzJ1xyXG5pbXBvcnQgJy4vaW5kZXguY3NzJ1x0XHJcbmltcG9ydCBteUNvbnNvbGUgZnJvbSAnLi9jb25zb2xlLmpzJ1xyXG5cclxuIGNsYXNzIEFwcCBleHRlbmRzIENvbXBvbmVudCB7XHJcbiBcdGNvbnN0cnVjdG9yIChwcm9wcykge1xyXG4gXHRcdHN1cGVyKHByb3BzKTtcclxuIFx0XHR0aGlzLmhhbmRsZUNsaWNrID0gdGhpcy5oYW5kbGVDbGljay5iaW5kKHRoaXMpO1xyXG4gXHR9XHJcbiBcdGhhbmRsZUNsaWNrICgpIHtcclxuIFx0XHRsZXQgZnVuID0gKCkgPT4gY29uc29sZS5sb2coJzEyMycpO1xyXG4gXHRcdGZ1bigpO1xyXG4gXHR9XHJcblx0cmVuZGVyICgpIHtcclxuXHRcdHJldHVybiAgPGRpdiBvbkNsaWNrPXtteUNvbnNvbGV9PkhlbGxvIFdvcmxkLCBJIGFtIGNoYW5nZWQgYWdhaW4gYWdhaW4uLi48L2Rpdj5cclxuXHR9XHJcbn1cclxuXHJcbnJlbmRlcihcclxuXHQ8QXBwLz4sXHJcblx0ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3Jvb3QnKVxyXG4pXHJcblxyXG5pZiAobW9kdWxlLmhvdCkge1xyXG5cdG1vZHVsZS5ob3QuYWNjZXB0KCcuL2NvbnNvbGUuanMnLCBmdW5jdGlvbiAoKSB7XHJcblx0XHRteUNvbnNvbGUoKTtcclxuXHR9KVxyXG59Il0sInNvdXJjZVJvb3QiOiIifQ==
sourcemap定义了源代码与编译后代码的映射关系,从而可以方便我们debug,其结构如下,包括version、sources、names、mappings、file、sourcesContent及sourceRoot字段;sources指定需要映射的文件,names和mappings则建立其中关键属性的对应映射关系,mappdings使用VLQ编码指定了具体字段的位置信息;file字段指定需要更新的js文件[hash].hot-update.js;sourceContent则是做mapping的js的具体源代码内容;
{
"version": 3,
"sources": ["webpack:///./index.js"],
"names": ["App", "props", "handleClick", "bind", "fun", "console", "log", "myConsole", "Component", "document", "getElementById", "module", "hot", "accept"],
"mappings": ";;;;;;;;;;;;;;AAAA;;;;AACA;;AAEA;;AACA;;AACA;;;;;;;;;;;;IAEOA,G;;;AACL,eAAaC,KAAb,EAAoB;AAAA;;AAAA,0GACbA,KADa;;AAEnB,UAAKC,WAAL,GAAmB,MAAKA,WAAL,CAAiBC,IAAjB,OAAnB;AAFmB;AAGnB;;;;kCACc;AACd,UAAIC,MAAM,SAANA,GAAM;AAAA,eAAMC,QAAQC,GAAR,CAAY,KAAZ,CAAN;AAAA,OAAV;AACAF;AACA;;;6BACQ;AACT,aAAQ;AAAA;AAAA,UAAK,SAASG,iBAAd;AAAA;AAAA,OAAR;AACA;;;;EAXiBC,gB;;AAcnB,sBACC,8BAAC,GAAD,OADD,EAECC,SAASC,cAAT,CAAwB,MAAxB,CAFD;;AAKA,IAAIC,IAAJ,EAAgB;AACfA,SAAOC,GAAP,CAAWC,MAAX,CAAkB,kCAAlB,EAAkC,YAAY;AAC7C;AACA,GAFD;AAGA,C",
"file": "app.9b34b0372e9214ced0b7.hot-update.js",
"sourcesContent": [
"import React, {Component} from 'react';\r\nimport {render} from 'react-dom';\r\n\r\nimport './style.css'\r\nimport './index.css'\t\r\nimport myConsole from './console.js'\r\n\r\n class App extends Component {\r\n \tconstructor (props) {\r\n \t\tsuper(props);\r\n \t\tthis.handleClick = this.handleClick.bind(this);\r\n \t}\r\n \thandleClick () {\r\n \t\tlet fun = () => console.log('123');\r\n \t\tfun();\r\n \t}\r\n\trender () {\r\n\t\treturn Hello World, I am changed again again...\r\n\t}\r\n}\r\n\r\nrender(\r\n\t ,\r\n\tdocument.getElementById('root')\r\n)\r\n\r\nif (module.hot) {\r\n\tmodule.hot.accept('./console.js', function () {\r\n\t\tmyConsole();\r\n\t})\r\n}"],
"sourceRoot": ""
}
启用HMR模式的情况下,更新前后控制台的输出分别如如图4和图5所示,对比entrypoint可以看到,当有代码更新时,webpack的代码更新机制依赖于[hash].hot-update.json及[hash].hot-update.js。