webpack HMR是如何工作的?
https://github.com/webpack/docs/wiki/hot-module-replacement-with-webpack
https://www.jianshu.com/p/941bfaf13be1
什么是HMR?
Hot Module Replacement(HMR)在web应用正在运行时在无需整个页面refresh的前提下,实现对特定模块替换,添加,或者删除的操作。
HMR是如何工作的?
webpack会在构建过程中向bundle中添加一小段HMR Runtime代码,这段HMR Runtime将在你的app中运行。当构建结束时,webpack并不会退出,而是在那里监听源代码的任何改变。如果webpack检测到代码的变化,它只对变更过的module执行rebuild。依赖于webpack的配置,要么webpack自己向HRM runtime程序发送一个信号,或者HMR Runtime主动向webpack征询代码变化。无论任何一种模式下,变更过的module将被发往HMR runtime.而这个runtime代码将试图应用这个hot update.首先runtime检查被更新的module是否能够accept这个hot update.如果不能,则runtime继续检查required这个update module的module是否能够accept.如果还不能accept,则继续bubble up往上冒泡,直到找到能够accept的module,或者直到app entry point.而这种情况下,hot update将会fail掉。
从app来看
app代码请求HRM runtime执行检查Update的工作。HMR Runtime下载这个update(async)并且告知app code有一个update已经ready. app代码请求HRM Runtime来apply 这个update. HMR runtime应用上这个update(sync). app代码可能会也可能不会需要用户的交互(这个你自己决定)
从compiler(webpack)来看
除了普通的assets, compiler需要发射"update"事件以便运行系统更新这个update到新的版本。"update"包含两部分内容:
1. the update manifest(json)
2. one or multiple update chunks(js)
manifest包含新的compilation hash和一个所有的updated chunks列表清单;
而update chunks则包含了所有的updated module的代码(或一个flag如果该模块被删除掉的话)
compiler也会确保module和chunk id在这些build之间保持一致性。它使用一个records.json文件来保存这些信息以便保持一致和同步。
从module的角度来看
HMR是一个可选打开的功能,所以HMR仅仅会影响到那些包含HMR Code的模块。总的来说,module developer需要写handlers代码,如果该模块的dependency更新了的话,这个Handlers代码将被调用以便接收这个update。他们也可以写一个handler,当该module本身而不是其dependency更新时调用他。
在大多数情况下,并不需要在每个module中都写HRM code。如果一个module没有HMR handler那么这个update将会bubble up.这意味着仅需一个handler就可以处理这个模块的依赖树上的所有module update。如果该依赖树中的任何单个模块做了更新,那么整个module tree就将得到reloaded(only reloaded not transferred)
从HMR runtime的角度来看
HMR runtime是一段额外的代码注入用于跟踪module parents和module children.
从管理的角度说,这个runtime支持两个方法: check和apply
一个check执行http请求,请求manifest列表文件。如果这个请求失败了,将不会有任何update执行。否则,新的updated chunks将和当前已经加载过的chunks进行比较。针对每个已经loaded的chnks,如果有update,则将下载对应的update chunk.所有的模块更新在runtime看来都作为update来缓存。runtime切换到ready状态,意味着一个update已经下载并且可以用于applied.
对每一个new chunk(无loaded chunk对应),update chunk也需要下载。
apply方法标志所有updated modules为invalid状态。针对每一个invalid module,需要有一个update handler在本模块里或者其父亲模块里存在。否则这个Invalid module状态会bundle up并且设置其所有的父亲组件为invalid.这个流程将一直进行下去直到没有"bubbling up"可以发生为止。如果冒泡到了entry point,则这个process将fail掉。
现在所有invlaid modules将被disposed(处置掉,需要dispose handler)并且卸载掉。然后当前hash更新并且所有"accept" handlers将被调用。runtime最后会切换到idle状态,继续下一轮。
我们能做些什么?
你可以作为LiveReload的替代品将HMR应用在dev过程中。实际上,webpack-dev-server实际上会在执行重新加载整个页面前试图使用HMR来完成热更新。你只需要将webpack/hot/dev-server本身加到你的webpack build的entry point中去,并且在命令行加上--hot参数即可。
webpack/hot/dev-server将在HMR update失败后重新加载整个页面。如果你希望自己来执行reload page动作,你可以增加webpack/hot/only-dev-server到entry point中,而不是dev-server。
你也可以将它用在生产环境下作为一种更新的机制存在。这时,你需要写自己的管理代码以便集成HRM到你的app中。
有一些loaders已经具有了产生hot-updateable module的能力,比如style-loader就可以swap stylesheet.在这种情况下,你无需做任何事情。
需要什么方可使用HMR呢?
一个module只有你"accpet",才能实现HMR updated。所以,你需要"module.hot.accept"存在于你的模块或模块的父亲链中。比如,一个router或者一个subview将是一个实现accept的好地方。
如果你仅仅希望和webpack-dev-server集成使用HMR而不是自己来写代码,你可以增加webpack/hot/dev-server作为一个entry point, 否则,你需要写一些HMR management code来调用check和apply.
你需要在compiler中enable records以便在不同的进程间跟踪module id.(watch mode和webpack-dev-server保存这些records在内存中,所以你开发时无需)
你需要在compiler中enable HMR,以便webpack向bundle中加入HMR runtime.
为什么HMR这么cool?
它就像这对每个module实现了liveReload一样,而不是针对页面来做liveReload
你可以在生产环境中使用它;
updates也会尊重code splitting,只会下载你的app变更部分
你可以仅在应用的部分中应用,而不会影响其他的模块。
如果HMR disabled,那么所有的HMR code都将被webpack清除掉(wrap it in "
if(module.hot)
中,以便能被webpack轻松剔除
")
Tutorial
To use hot code replacement with webpack you need four things:
- records (
--records-path
,recordsPath: ...
) - globally enable hot code replacement (
HotModuleReplacementPlugin
) - hot replacement code in your code
module.hot.accept
- hot replacement management code in your code
module.hot.check
,module.hot.apply
A small testcase:
/* style.css */
body {
background: red;
}
/* entry.js */
require("./style.css");
document.write("<input type='text' />");
That's enough to use hot code replacement with the 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"
The dev server provides in memory records, which is good for development.
The --hot
switch enables hot code replacement.
This adds the
HotModuleReplacementPlugin
. Make sure to use either the--hot
flag, or theHotModuleReplacementPlugin
in yourwebpack.config.js
, but never both at the same time as in that case, the HMR plugin will actually be added twice, breaking the setup.
There is special management code for the dev-server at webpack/hot/dev-server
, which is automatically added by --inline
. (You don't have to add it to your webpack.config.js
)
The style-loader
already includes hot replacement code.
If you visit http://localhost:8080/bundle you should see the page with a red background and a input box. Type some text into the input box and edit style.css
to have another background color.
Voilà... The background updates but without full page refresh. Text and selection in the input box should stay.
Read more about how to write you own hot replacement (management) code: hot module replacement
Check the example-app for a demo without coding. (Note: It's a bit old, so don't look at the source code, because the HMR API changed a bit in between)