梳理 Webpack 热更新 | 技术点评

679 阅读4分钟

代码配置

在 webpack.config.js 文件中设置 devServer.hot 为 true,启用 HMR 特性。另外要用上 HMR 特性则还需要配置 HotModuleReplacementPlugin 插件。如果不想配置这个插件,也可以在 webpack-dev-server 启动时添加了 —hot 这个参数,这样 HotModuleReplacementPlugin 插件会被自动配置进来。

问自己的问题

  1. 通过 webpack HMR 进行开发的过程中,并没有在 dist 目录中找打 wegpack 打包好的文件,它们去哪儿了?
  2. webpack-dev-server 中依赖的 webpack-dev-middleware 扮演了什么角色。
  3. 通过 Chrome 开发者工具我知道浏览器是通过 websocket 和 webpack-dev-server 进行通信的,但是 websocket 和 message 中并没有发现新模块代码。打包后的新模块又是通过什么方式发送到浏览器端的呢?为什么新的模块不通过 websocket 随消息一起发送到浏览器端呢?
  4. 当模块的热替换过程中,如果替换模块失败,有什么回退机制吗?

各个模块的职责

  1. Webpack
    1. 在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中。
    2. webpack/hot/dev-server 的工作根据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢,还是进行模块热更新。
  2. webpack-dev-server
    1. 依赖的 webpack-dev-middleware 和 webpack 之间的交互,webpack-dev-middleware 调用 webpack 暴露的接口对代码变化进行监控,并且告诉 webpack,将代码打包到内存中。
    2. webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更新模块操作,而把这些工作交给了 webpack。
    3. webpack-dev-server 与 webpack-dev-server/client(浏览器端)通过 sockjs 进行 websocket 通信。
  3. HotModuleReplacementPlugin
    1. HMRPlugin.runtime 是客户端 HMR 的中枢,它接收到上一步传给它的新模块的 hash 值,通过 JsonpModuleTemplate.runtime 向 webpack-dev-server 端发送 ajax 请求,server 端返回一个 json,该 json 包含了所有要更新模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp,获取到更新的模块代码。
    2. HotModuleReplacementPlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同事更新模块间的依赖引用。
    3. 当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新的打包代码。

具体的步骤

image.png 图源来自:Webpack HMR 原理解析 - 知乎

  1. webpack 在 watch 模式下,对文件系统进行监听,并把文件打包到内存。
  2. webpack-dev-server 监听到文件变化后,通过 sockjs websocket 通信,通知浏览器端发生改变。
  3. webpack-dev-server 在打包的 bundle.js 中添加了 webpack-dev-server/client 的代码,webpack-dev-server/client 接收到 webpack-dev-server 发过来的消息。根据 hot 的配置,是重刷新页面,还是进行热更新。
  4. 如果是热更新,通过 webpack/hot/emitter 通知 webpack/hot/dev-server。调用 webpack/lib/HotModuleReplacementPlugin.runtime 的 check 方法,检查是否有更新。
  5. 在 check 过程中,会调用两个方法:hotDownloadManifest(根据之前的 hash 值调用 ajax 向服务端请求是否更新的文件)和 hotDownloadUpdateChunk(如果有更新,通过 jsonp 请求最新的模块代码,并将代码返回给 HMR runtime)。HMR runtime 会根据返回的新模块的代码做进一步处理,可能是刷新页面,也可能是对模块进行热更新。
  6. HMR.runtime 对模块进行热更新,这一步是关键。其中最主要是 hotApply 方法。
    1. 第一阶段找出 outdateModules 和 outdatedDependencies
    2. 删除模块对应的缓存
    3. 删除模块对应的依赖
    4. 将模块添加到 modules 中,当下次调用 webpack_require 方法的时候,就是获取到了新的模块代码了。
    5. 更新模块缓存
    6. 如果过程中出现错误,热更新将回退到刷新浏览器。
  7. 业务代码需要做些什么?当用新的模块代码替换老的模块后,我们的业务代码并不知道模块代码发生了变化。我们要在 index.js 文件中 HMR 的 accept 方法,添加模块更新后的处理函数,及时将 hello 方法的返回值插入到页面中。
    1. 调用 react-hot-loader 的 hot 方法,包裹一下最上层的 App 组件:hot(module)(App)。

封面图片 by James Harrison on Unsplash