前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Webpack 的 HMR 运行机制

Webpack 的 HMR 运行机制

作者头像
CSDN技术头条
发布2018-07-30 11:17:14
1K0
发布2018-07-30 11:17:14
举报

调试工具

首先利用 Chrome 的 dev-tools 中的 network 观察,代码改变的时候,页面与后端之间发生了什么?示例中的项目代码地址已上传 Github。

页面初始加载

运行 dev 模式,本地打开页面并开启 dev-tools,我们看到除了加载页面所依赖的文件外,多了一个连接__webpack_hmr,这是一个叫做 Server-sent Events 的长连接,主要用于后端不断的向前端推送数据,其原理图如下所示,相关的介绍可以参考这篇文章。

后端每次推送的数据内容主要在 EventStream 中的 Data 字段中,如下图所示:

Data 中存储的是一个对象,对象有 action、hash、modules 等字段。

  • action:sync 操作;
  • hash:0c0d327c2abaa1fd4b88,是 bundle 的 hash,因为和产出文件 app.0c0d327c2abaa1fd4b88.js 的内容 hash 值相同;
  • modules:产出 bundle 中的 module id 和对应的文件地址。

修改代码

修改一处代码,webpack 自动编译后,发现 network 中发生了几处变化,首先是客户端收到后端发出的若干事件。

  • action:built 操作,通知浏览器 webpack 完成了编译;
  • hash:最新产出 bundle 的内容 hash 值为215d3b813666fbaea5a3;
  • modules:bundle 中的模块id 和对应模块的文件地址。

在前端收到 built 事件之后,前端向后端发起了两个请求,请求了 0c0d327c2abaa1fd4b88.hot-update.json 和 0.0c0d327c2abaa1fd4b88.hot-update.js 两个文件,文件的 hash 值正好是未发生修改之前后端发送前端的 bundle hash 值。

json文件的内容:

  • h:215d3b813666fbaea5a3,bundle 内容的最新 hash 值;
  • c:"0": true, 表示 bundle id 为 0 的文件被修改了;

js文件的内容:

内容是一个函数,类似 jsonp 的返回形式,也就是页面收到请求后执行了 webpackHotUpdate 函数,对 bundle id 为 0 的文件中的 moudle id 为 11 的模块进行修改。

推测结论

根据上面 network 中的信息,我们可以推测出这个交互过程:

  1. webpack 首次编译时,为前端页面注入后端推送事件监听(event-source)和拉取、更新模块的方法(update-method)的代码,并打包到 bundle 之中;
  2. webpack 进入 watch 模式,在项目代码发生变化的时候重新编译,并将编辑的进展实时通知前端;
  3. 将编译产出存放在 dev-server,此处的编译只针对变动的模块,产出应该包含上文中提到的 oldbundlehash.hot-update.json 和 oldbundlehash.hot-update.js 文件;
  4. dev-server 中使用 hot-middleware 中间件向前端发送 built 事件;
  5. 前端收到通知后,向后端请求最新的变动文件,请求到的 js 文件通过 script 标签加载后执行(类似 Jsonp),其实就是执行已经预埋到 bundle 中的函数(update-method),从而修改 bundle 文件。

以上过程可以用下图表示:

配置文件

接下来我们从项目的配置文件来验证一下,配置文件主要参考 vue-cli 中的 webapck 项目(1.1.2),不同的版本会存在差异。

  • webpack.dev.conf.js

涉及到 Hot Module Replacement 的地方主要有两处:

1. entry 的配置:

在每个入口 bundle 开头引入了 event-source,即在页面中接收后端发送的事件

    /*********./build/webpack.dev.conf.js********/
    // 将 event-source 相关代码,添加到每个入口 chunk 中,作为 HRM Runtime 的一部分。
    // 后端相应的配置见 dev-server 的 hotMiddleware 部分
    Object.keys(baseWebpackConfig.entry).forEach(function (name) {
        baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
    })    /*********./build/dev-client.js********/

    // Event-Source 对象用于接收服务器端推送事件
    // eventsource-polyfill 用于扩展 Event-Source 对象在 IE 浏览器下的兼容性
    require('eventsource-polyfill')    var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')    // 主要用于接受后端 hotMiddleware 的 reload 通知,执行相应操作
    hotClient.subscribe(function (event) {        if (event.action === 'reload') {            window.location.reload()
        }
    }

2. 插件的配置:

引入 HotModuleReplacementPlugin 插件,将 update-method 的代码打入 bundle

    plugins: [
            ...            // HMR 插件将 HMR Runtime 代码嵌入到 bundle 中,能够操作 APP 代码,完成代码替换
            new webpack.HotModuleReplacementPlugin(),            // 报错提示插件:报错不阻塞,但是编译后给出提示
            new webpack.NoEmitOnErrorsPlugin(),            new FriendlyErrorsPlugin()
        ]

 - dev-server.js

涉及到 Hot Module Replacement 的地方主要有两处:

1. 将 compiler 挂载在 devMiddleware 上:

对编译产出提供静态文件服务

    // 将 compiler 挂载在 dev-server 上,监听本地代码变化,变化则启动编译并将编译后的文件暂存到内存中
    var devMiddleware = require('webpack-dev-middleware')(compiler, {        publicPath: config.dev.assetsPublicPath === './' ? '' : config.dev.assetsPublicPath,        quiet: true
    })

2. 将 compiler 挂载在 hotMiddleware 上:

通知前端 event-source 对象发生了 rebuilt

    // 编译后发送通知到 HRM Runtime,HRM Runtime 收到 update 通知后,下载更新的模块,通知 APP 更新,APP 收到通知,然后要求 HRM Runtime 执行模块替换
    var hotMiddleware = require('webpack-hot-middleware')(compiler, {        log: () => {}
    })

由配置文件可以基本验证之前通过 network debug 得到的推论,接下来去看一

下官方文档验证一下。

官方文档

官方文档中先是总体介绍了一下 Hot Module Replacement 的基本原理,然后将原理中涉及到几个知识点进行了介绍。

1. 基本原理

webapck 在编译的过程中,将 HMR Runtime 嵌入到 bundle 中;编译结束后,webpack 对项目代码文件进行监视,发现文件变动重新编译变动的模块,同时通知 HMR Runtime,然后 HMR Runtime 加载变动的模块文件,尝试执行热更新操作。更新的逻辑是:先检查模块是否能支持 accept 方法,不支持的话,则冒泡查找模块树的父节点,直到入口模块,accept 方法也就是模块 hot-replace 的 handler。

2. 知识点

(1)compiler

这里的 compiler 也就是指 webapck,主要提供 update 的信息,也就是 update menifest(json 文件格式)和 update chunks(js文件格式);

(2)app

app 也就是指前端页面,app 中的代码主要调用 HMR Runtime 下载最新的模块代码,然后调用 HMR Runtime 执行 update 操作;

(3)HMR Runtime

HMR Runtime 是 webapck 内嵌到前端页面的代码,主要提供来能给个职能 check 和 apply。check 用来下载最新模块代码,runtime 能够接收后端发送的事件和发送请求;apply 用于更新模块,主要将要更新的模块打上 tag,然后调用模块的(也有可能是父模块)的更新 handler 执行更新。

(4)module

HRM是一个可插拔的工具,只能影响包含HMR code的模块。通常情况下,没有必要为每个模块写入HMR code,更新的时候会进行冒泡检查HMR code的是否存在。

根据官方文档的介绍,基本和我们的推论吻合,区别在于官方文档引入了 HMR Runtime 的概念,这个可以看作是推论中的 event-source 和update-method的结合体。

现在大家应该清楚了 webpack的Hot Module Replacement 的基本原理了,官方文档中提到了如何根据最新的模块替换旧模块的方法,这个知识点暂不在本文进行介绍。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-05-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 GitChat精品课 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 基本原理
  • 2. 知识点
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档