hmr webpack 不编译_webpack - hmr热更新

文章首发于个人blog,欢迎关注~

webpack hmr

webpack-dev-server

在使用 webpack-dev-server 的过程中,如果指定了 hot 配置的话(使用 inline mode 的前提下), wds 会在内部更新 webpack 的相关配置,即将 HotModuleReplacementPlugin 加入到 webpack 的 plugins 当中。

HotModuleReplacementPlugin

在 HotModuleReplacementPlugin 执行的过程中主要是完成了以下几个工作:在创建 normalModule 的阶段添加 parser 的 hook,即在之后的 module 编译解析阶段 parser 处理不同的语法时可以交由在这个阶段添加的 hook 回调来进行相关的处理。normalModuleFactory.hooks.parser

.for("javascript/auto")

.tap("HotModuleReplacementPlugin", addParserPlugins);

normalModuleFactory.hooks.parser

.for("javascript/dynamic")

.tap("HotModuleReplacementPlugin", addParserPlugins);

其中在 addParserPlugins 方法当中添加了具体有关 parser hook 的回调,有几个比较关键的 hook 单独拿出来说下:parser.hooks.call

.for("module.hot.accept")

.tap("HotModuleReplacementPlugin")

这个 hook 主要是在 parser 编译代码过程中遇到module.hot.accept的调用的时候会触发,主要的工作就是处理当前模块部署依赖模块的依赖分析,在编译阶段处理好依赖的路径替换等内容。parser.hooks.call

.for("module.hot.decline")

.tap("HotModuleReplacementPlugin")

这个 hook 同样是在 parser 编译代码过程中遇到module.hot.decline的调用的时候触发,所做的工作和上面的 hook 类似。在 mainTemplate 上添加不同 hook 的处理回调来完成对于 webpack 在生成 bootstrap runtime 的代码阶段去注入和 hmr 相关的运行时代码,有几个比较关键的 hook 单独拿出来说下:const mainTemplate = compilation.mainTemplate

mainTemplate.hooks.moduleRequire.tap(

"HotModuleReplacementPlugin",

(_, chunk, hash, varModuleId) => {

return `hotCreateRequire(${varModuleId})`;

})

这个 hook 主要完成的工作是在生成 webpack bootstrap runtime 代码当中对加载 module 的 require function进行替换,变为hotCreateRequire(${varModuleId})的形式,这样做的目的其实就是对于 module 的加载做了一层代理,在加载 module 的过程当中建立起相关的依赖关系(需要注意的是这里的依赖关系并非是 webpack 在编译打包构建过程中的那个依赖关系,而是在 hmr 模式下代码执行阶段,一个 module 加载其他 module 时在 hotCreateRequire 内部会建立起相关的加载依赖关系,方便之后的修改代码之后进行的热更新操作),具体这块的分析可以参见下面的章节。mainTemplate.hooks.bootstrap.tap(

"HotModuleReplacementPlugin",

(source, chunk, hash) => {

// 在生成 runtime 最终的代码前先通过 hooks.hotBootstrap 钩子生成相关的 hmr 代码然后再完成代码的拼接

source = mainTemplate.hooks.hotBootstrap.call(source, chunk, hash);

return Template.asString([

source,

"",

hotInitCode

.replace(/\$require\$/g, mainTemplate.requireFn)

.replace(/\$hash\$/g, JSON.stringify(hash))

.replace(/\$requestTimeout\$/g, requestTimeout)

.replace(

/\/\*foreachInstalledChunks\*\//g, // 通过一系列的占位字符串,在生成代码的阶段完成代码的替换工作

needChunkLoadingCode(chunk)

? "for(var chunkId in installedChunks)"

: `var chunkId = ${JSON.stringify(chunk.id)};`

)

]);

}

)

在这个 hooks.bootstrap 当中所做的工作是在 mainTemplate 渲染 bootstrap runtime 的代码的过程中,对于hotInitCode代码进行字符串的匹配和替换工作。hotInitCode这部分的代码其实就是下面章节所要讲的HotModuleReplacement.runtime向 bootstrap runtime 代码里面注入的 hmr 运行时代码。mainTemplate.hooks.moduleObj.tap(

"HotModuleReplacementPlugin",

(source, chunk, hash, varModuleId) => {

return Template.asString([

`${source},`,

`hot: hotCreateModule(${varModuleId}),`, // 这部分的内容即这个 hook 对相关内容的拓展

"parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),",

"children: []"

]);

}

)

在这个 hooks.moduleObj 当中所做的工作是对__webpack_require__这个函数体内部的 installedModules 缓存模块变量进行拓展。几个非常关键的点就是:新增了 module 上的 hot: hotCreateModule(${varModuleId}) 配置。这个 module.hot api 即对应这个 module 有关热更新的 api,可以看到这个部署 hot api 的工作是由 hotCreateModule 这个方法来完成的(这个方法是由 hmr runtime 代码提供的,下面的章节会讲)。最终和这个 module 所有有关热更新相关的接口都通过module.hot.*去访问。

新增 parents 属性配置:初始化有关这个 module 在 hmr 下,它的 parents(这个 module 被其他 module 依赖);

新增 children 属性配置:初始化有关这个 module 在 hmr 下,它的 children(这个 module 所依赖的 module)

HotModuleReplacement.runtime

Webpack 内部提供了 HotModuleReplacement.runtime 即热更新运行时部分的代码。这部分的代码并不是通过通过添加 webpack.entry 入口文件的方式来注入这部分的代码,而是通过 mainTemplate 在渲染 boostrap runtime 代码的阶段完成代码的注入工作的(对应上面的 mainTemplate.hooks.boostrap 所做的工作)。

在这部分热更新运行时的代码当中所做的工作主要包含了以下几个点:提供运行时的hotCreateRequire方法,用以对__webpack_require__模块引入方法进行代理,当一个模块依赖其他模块,并将其引入的时候,会建立起宿主模块和依赖模块之间的相互依赖关系,这个依赖关系也是作为之后某个模块发生更新后,寻找与其有依赖关系的模块的凭证。function hotCreateRequire(moduleId) {

var me = installedModules[moduleId];

if (!me) return $require$;

var fn = function(request) { // 这个是 hmr 模式下,对原来的 __webpack_require__ 引入模块的函数做的一层代理

// 通过 depModule.parents 和 module.children 来双向建立起 module 之间的依赖关系

if (me.hot.active) {

if (installedModules[request]) {

if (installedModules[request].parents.indexOf(moduleId) === -1) {

installedModules[request].parents.push(moduleId); // 建立 module 之间的依赖关系,在被引入的 module 的 module.parents 当中添加当前这个 moduleId

}

} else {

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值