hmr webpack 不编译_Webpack的HMR原理分析

Webpack的HMR原理分析

module.exports = {

entry : {

main : './src/main.js',

home : './src/home.js',

common : ['jquery'],

common2 : ['react']

},

output : {

path: path.join(__dirname, 'build'),

filename: '[name].js'

},

plugins: [

new webpack.HotModuleReplacementPlugin(),

new CommonsChunkPlugin({

name: "chunk",

minChunks: function(module, count) {

return module.resource && (/common/).test(module.resource) && count === 2;

},

}),

new SetVersion()

],

devServer: {

contentBase: './build',

hot: true

//支持 HMR

}

}

在 Webpack 的 devServer 配置中设置了 hot 为 true,而且在 Webpack 的 plugin 中添加了 new webpack.HotModuleReplacementPlugin() 这个插件。

Webpack 的 HMR 的实现原理

compiler.plugin("done", function(stats) {

//clientStats 表示需要保存 stats 中的那些属性,可以允许配置,参见 Webpack 官网

this._sendStats(this.sockets, stats.toJson(clientStats));

this._stats = stats;

}.bind(this));

每次 compiler 的 'done' 钩子函数被调用的时候就会要求客户端去检查模块更新,如果客户端不支持 HMR,那么就会全局加载。

Server.prototype._sendStats = function(sockets, stats, force) {

if(!force &&

stats &&

(!stats.errors || stats.errors.length === 0) &&

stats.assets &&

stats.assets.every(function(asset) {

return !asset.emitted;

//(1)每一个 asset 都是没有 emitted 属性,表示没有发生变化。如果发生变化那么这个 assets 肯定有 emitted 属性

})

)

return this.sockWrite(sockets, "still-ok");

//(1)将 stats 的 hash 写给 socket 客户端

this.sockWrite(sockets, "hash", stats.hash);

//设置 hash

if(stats.errors.length > 0)

this.sockWrite(sockets, "errors", stats.errors);

else if(stats.warnings.length > 0)

this.sockWrite(sockets, "warnings", stats.warnings);

else

this.sockWrite(sockets, "ok");

}

通过 webpack-dev-server 提供的 websocket 服务端代码通知 websocket 客户端)发送的 ok 和 warning 信息的时候会要求更新。如果支持 HMR 的情况下就会要求检查更新,同时发送过来的还有服务器端本次编译的 compilation 的 hash 值。如果不支持 HMR,那么要求刷新页面。

ok: function() {

sendMsg("Ok");

if(useWarningOverlay || useErrorOverlay) overlay.clear();

if(initial) return initial = false;

reloadApp();

},

warnings: function(warnings) {

log("info", "[WDS] Warnings while compiling.");

var strippedWarnings = warnings.map(function(warning) {

return stripAnsi(warning);

});

sendMsg("Warnings", strippedWarnings);

for(var i = 0; i < strippedWarnings.length; i++)

console.warn(strippedWarnings[i]);

if(useWarningOverlay) overlay.showMessage(warnings);

if(initial) return initial = false;

reloadApp();

},

function reloadApp() {

//(1)如果开启了 HMR 模式

if(hot) {

log("info", "[WDS] App hot update...");

var hotEmitter = require("webpack/hot/emitter");

hotEmitter.emit("webpackHotUpdate", currentHash);

//重新启动 webpack/hot/emitter,同时设置当前 hash,通知上面的 webpack-dev-server 的 webpackHotUpdate 事件,告诉它打印哪些模块的更新信息

if(typeof self !== "undefined" && self.window) {

// broadcast update to window

self.postMessage("webpackHotUpdate" + currentHash, "*");

}

} else {

//(2)如果不是 Hotupdate 那么直接 reload 我们的 window 就可以了

log("info", "[WDS] App updated. Reloading...");

self.location.reload();

}

}

如果 ok 则调用reloadApp方法,而 reloadApp 方法 判断如果开启了 HMR 模式, 通过hotEmitter 执行webpackHotUpdate方法,如果不是 Hotupdate 那么直接 reload刷新网页。

if(module.hot) {

var lastHash;

var upToDate = function upToDate() {

return lastHash.indexOf(__webpack_hash__) >= 0;

//(1)如果两个 hash 相同那么表示没有更新,其中 lastHash 表示上一次编译的 hash,记住是 compilation 的 hash

//只有在 HotModuleReplacementPlugin 开启的时候存在。任意文件变化后 compilation 都会发生变化

};

//(2)下面是检查更新的模块

var check = function check() {

module.hot.check().then(function(updatedModules) {

//(2.1)没有更新的模块直接返回,通知用户无需 HMR

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)");

return;

}

//(2.2)开始更新

return module.hot.apply({

ignoreUnaccepted: true,

//和 accept 函数指定热加载那些模块

ignoreDeclined: true,

//decline 表示不支持这个模块热加载

ignoreErrored: true,

//error 表示出错的模块

onUnaccepted: function(data) {

console.warn("Ignored an update to unaccepted module " + data.chain.join(" -> "));

},

onDeclined: function(data) {

console.warn("Ignored an update to declined module " + data.chain.join(" -> "));

},

onErrored: function(data) {

console.warn("Ignored an error while updating module " + data.moduleId + " (" + data.type + ")");

}

//(2.2.1)renewedModules 表示哪些模块已经更新了

}).then(function(renewedModules) {

//(2.2.2)如果有模块没有更新完成,那么继续检查

if(!upToDate()) {

check();

}

//(2.2.3)更新的模块 updatedModules,renewedModules 表示哪些模块已经更新了

require("./log-apply-result")(updatedModules, renewedModules);

//通知已经热加载完成

if(upToDate()) {

console.log("[HMR] App is up to date.");

}

});

}).catch(function(err) {

//(2.3)更新异常,输出 HMR 信息

var status = module.hot.status();

if(["abort", "fail"].indexOf(status) >= 0) {

console.warn("[HMR] Cannot check for update. Need to do a full reload!");

console.warn("[HMR] " + err.stack || err.message);

} else {

console.warn("[HMR] Update check failed: " + err.stack || err.message);

}

});

};

var hotEmitter = require("./emitter");

//(3)emitter 模块内容,也就是导出一个 events 实例

/*

var EventEmitter = require("events");

module.exports = new EventEmitter();

*/

hotEmitter.on("webpackHotUpdate", function(currentHash) {

lastHash = currentHash;

//(3.1)表示本次更新后得到的 hash 值

if(!upToDate()) {

//(3.1.1)有更新

var status = module.hot.status();

if(status === "idle") {

console.log("[HMR] Checking for updates on the server...");

check();

} else if(["abort", "fail"].indexOf(status) >= 0) {

console.warn("[HMR] Cannot apply update as a previous update " + status + "ed. Need to do a full reload!");

}

}

});

console.log("[HMR] Waiting for update signal from WDS...");

} else {

throw new Error("[HMR] Hot Module Replacement is disabled.");

}

上面看到了 log-apply-result 模块,该模块是在所有的内容已经更新完成后调用的,下面继续看一下它到底做了什么事情:

module.exports = function(updatedModules, renewedModules) {

//(1)renewedModules 表示哪些模块需要更新,剩余的模块 unacceptedModules 表示,哪些模块由于 ignoreDeclined,ignoreUnaccepted 配置没有更新

var unacceptedModules = updatedModules.filter(function(moduleId) {

return renewedModules && renewedModules.indexOf(moduleId) < 0;

});

//(2)unacceptedModules 表示该模块无法 HMR,打印 log

if(unacceptedModules.length > 0) {

console.warn("[HMR] The following modules couldn't be hot updated: (They would need a full reload!)");

unacceptedModules.forEach(function(moduleId) {

console.warn("[HMR] - " + moduleId);

});

}

//(2)没有模块更新,表示模块是最新的

if(!renewedModules || renewedModules.length === 0) {

console.log("[HMR] Nothing hot updated.");

} else {

console.log("[HMR] Updated modules:");

//(3)打印那些模块被热更新。每一个 moduleId 都是数字,那么建议使用 NamedModulesPlugin(webpack 2 建议)

renewedModules.forEach(function(moduleId) {

console.log("[HMR] - " + moduleId);

});

var numberIds = renewedModules.every(function(moduleId) {

return typeof moduleId === "number";

});

if(numberIds)

console.log("[HMR] Consider using the NamedModulesPlugin for module names.");

}

};

所以"webpack/hot/only-dev-server"的文件内容就是检查哪些模块更新了(通过 webpackHotUpdate 事件完成,而该事件依赖于compilation的 hash 值),其中哪些模块更新成功,而哪些模块由于某种原因没有更新成功。

接下来看看 "webpack/hot/dev-server":

f(module.hot) {

var lastHash;

//__webpack_hash__ 是每次编译的 hash 值是全局的

var upToDate = function upToDate() {

return lastHash.indexOf(__webpack_hash__) >= 0;

};

var check = function check() {

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.");

}

两者的主要代码区别在于 check() 函数的调用方式:

如果 autoApply 设置为 true,那么回调函数传入的就是所有被自己 dispose 处理 过的模块,同时 apply 方法也会自动调用,如果 auApply 设置为 false,那么所有的模块更新都会通过手动调用 apply 来完成。而所说的被自己 dispose 处理就是通过如下的方式来完成的:

if (module.hot) {

module.hot.accept();

//支持热更新

//当前模块代码更新后的回调,常用于移除持久化资源或者清除定时器等操作,如果想传递数据到更新后的模块,可以通过传入 data 参数,后续参数可以通过 module.hot.data 获取

module.hot.dispose(() => {

window.clearInterval(intervalId);

});

}

而一般调用 webpack-dev-server 只会添加 --hot 而已,即内部不需要调用 apply,而传入的都是被 dispose 处理过的模块:

f(devServerOptions.hotOnly)

devClient.push("webpack/hot/only-dev-server");

else if(devServerOptions.hot)

devClient.push("webpack/hot/dev-server");

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值