[译] Webpack 用来做模块热替换(hot module replacement)

翻译地粗糙, 英文好请直接看原文

原文 https://github.com/webpack/docs/wiki/hot-module-replacement-with-webpack


注意模块热替换(HMR)依然是试验性的功能

介绍

模块热替换(HMR)交换, 添加, 或者删除模块, 同时应用持续运行, 不需要页面刷新.

准备工作

  • 使用插件: http://webpack.github.io/docs/using-plugins.html

  • 代码分割: http://webpack.github.io/docs/code-splitting.html

  • webpack-dev-server: http://webpack.github.io/docs/webpack-dev-server.html

它是怎样工作的?

从 App 的角度

App 代码请求 HMR 运行时检查更新.
HMR 运行时会下载(异步)更新的代码, 通知 App 代码运行已经可用.
App 代码请求 HMR 运行时应用更新.
HMR 运行时应用更新(同步).
这个过程, App 代码可以也可以不依赖用户操作(看需要).

从编译器(Webpack)角度

除了普通的静态文件, 编译器触发 "Update" 进行版本更新.
"Update" 包含两个部分:

  1. 更对内容的 manifest (JSON)

  2. 一个或者多个关于更新的 chunks (js)

manifest 包含新的编译结果的 Hash 和列表储存的 chunks (2.).

更新的 chunks 包含 chunk 当中所有更新掉的模块的代码
(或者模块已经被移除的标记)

编译器额外会保证模块和 chunk 的 id 在多个建构当中一致
它使用 "records" JSON 文件在建构之间保存它们(或者存储在内存里)

从模块角度

HMR 是个可选功能, 它只影响包含 HMR 代码的模块
文件里描述了模块中可用的 API
通常模块开发者写的处理器代码会在这个模块的依赖更新时被调用
他也可以写个处理器, 在当前模块更新时被调用

大多数情况不会强制在每个模块里写上 HMR 代码
如果一个模块不包含 HMR 处理器, 更新事件就会向上冒泡
意味着单个处理器可以处理整个模块树的更新
如果树当中单个模块被更新, 整个模块树就会重新(刷新而不是 transferred(转移?))

从 HMR运行时角度(技术的)

对模块系统而言, 运行时意味着插入额外的代码来追踪模块的父节点子节点

管理层面, 运行时支持两个方法: checkapply

check 发起 HTTP 请求去获取更新的 manifest
请求失败时, 意味着没有可用的更新
否则将能返回更新的 chunks 的列表, 和当前已加载的 chunks 列表做对比
每个被更新的 chunk 对应的更新后的 chunk 都会被下载
所有存储在运行时当中的代码模块随着代码进行更新
运行时会切换到 ready 状态, 表明更新已经被下载并准备好应用

对于每个 ready 状态的新的 chunk 请求来说, 更新的 chunk 也已经被下载好了

apply 方法标记所有更新过的模块为 invalid
每个 invalid 模块需要有个 update 处理器, 在模块中或者在每个父节点
不然 invalid 向上冒泡, 多有的父节点也被标记为 invalid
这个步骤持续知道不再有"冒泡"出现
如果冒泡到了 entry point 就说明过程失败了

现在所有 invalid 模块会被处置(dispose 处理器)和卸载
随后当前的 hash 被更新, 所有的 "accept" 处理器被调用
运行时切换回到 "idle" 状态, 一切继续正常运行

生成的文件(技术的)

左侧表示初始的编译器流程
右侧表示当模块 4 和 9 更新的流程

用这个能做到什么?

你可以在开发环境中用作 LiveReload 的替代
实际上 webpack-dev-server 支持一个 hot 状态,
会尝试先去通过 HMR 更新然后可能尝试刷新整个页面
你只需要加上一个 webpack/hot/dev-server entry point,
并且在 dev-server 调用时加上参数 --hot

webpack/hot/dev-server 在 HMR 更新失败之后会刷新整个页面
如果你想自己刷新页面, 可以改用 webpack/hot/only-dev-server 这个 entry point.

你也可以将其用作生产环境的更新机制
这里你需要写你自己的管理代码集成 HMR 到你的应用当中

一些模块已经可以生成可以完成热替换的模块
比如 style-loader 可以替换样式. 你不需要特别做事情

使用它需要怎么做?

模块只有在你的 "accept" 他的时候才能被更新
所以你需要在父节点或者父节点的父节点... module.hot.accept 调用模块
比如 router 是个不错的地方, 或者一个 subview 当中

如果你只是要和 webpack-dev-server 一起用,
就加上 webpack/hot/dev-server 这个 entry point
不然你会需要一些 HMR 管理代码去调用 checkapply

你需要在编译器里开启 records 用来记录不同过程的模块 id
(watch 模块和 webpack-dev-server 在内存里追踪 records
所以在开发当中你不需要做)

你需要开启编译器的 HMR 让它加上 HMR 运行时

什么让它很酷?

  • 它是对于每个模块而言的 LiveReload

  • 你可以在生产环境使用

  • 它依据你的代码分割来更新, 只下载应用中需要更新的部分

  • 你可以用在应用的布局, 不会影响到其他的模块

  • 如果 HMR 被禁用, 所有的 HMR 代码会被编译器移除(包括在 if(module.hot) 中)

警告

  • 它属于试验性功能, 测试不够

  • 预计有一些 bug

  • 理论上可用在生成环境, 然而在严肃的场合或许太早

  • 模块 id 需要在多个编译过程被追踪, 你需要存储(records)

  • 优化器在第一次优化之后不再能够优化模块 id. 对 bundle 体积有影响

  • HMR 运行时增加了 bundle 的体积

  • 生成环境需要额外的测试代码检验 HMR 处理器. 挺难做的.

教程

使用 Webpack 代码热替换需要做 4 件事:

  • records (--records-path, recordsPath: ...)

  • 全局开启代码热替换 (HotModuleReplacementPlugin)

  • 在你的代码中插入热替换代码 module.hot.accept

  • 在你的代码中插入热替换代码 module.hot.check, module.hot.apply

一个小测试:

/* style.css */
body {
    background: red;
}
/* entry.js */
require("./style.css");
document.write("");

这已经足够在 dev-server 中使用代码热替换

npm install webpack webpack-dev-server -g
npm install webpack css-loader style-loader
webpack-dev-server ./entry --hot --inline --module-bind "css=style!css"

dev-server 提供内存里的 records, 对开发来说很好

--hot 选项开启了代码热替换.

这能够加上 HotModuleReplacementPlugin. 如果使用命令行就不用写到 webpack.config.js

webpack/hot/dev-server 有 dev-server 特殊的管理代码,
它会通过 --inline 自动加入.
(你不一定要在 webpack.config.js 里加上)

style-loader 已经包含了热替换相关代码.

随后访问 http://localhost:8080/bundle
的你应该能看到红色背景和 input 框, 输入些文字, 编辑下 style.css 改颜色

Voilà... 不需要刷新整个页面, 背景更新了. 输入框的文字和选区应该还在

阅读更多关于怎样写自己的热替换(管理)代码 hot module replacement

访问 example-app 直接查看 demo. (注意: 过时了, 也不要看源码, 因为 HMR API 中间有修改)

你可能感兴趣的:([译] Webpack 用来做模块热替换(hot module replacement))