Take you to understand the HMR thermal renewal principle
Hot Module Replacement (HMR) is one of the most exciting features webPack has introduced so far. When you make changes to your code and save it, WebPack repackages it and sends the new Module to the browser. Browsers replace old modules with new ones so that applications can be updated without refreshing the browser. When we in the development of web sites, for example, found that need to adjust the style of an element, this time you only need to modify the corresponding style in the code, then save, under the premise of no refresh the browser, the style of this element has the change, the development experience as if directly in the Chrome directly to change elements inside, It’s very comfortable.
HMR doubts
Now we are basically using webpack mode development, after modifying the code, the page will be directly changed, but few people think, why the page will be directly changed without refreshing?
When I met HMR for the first time, I felt amazing and had some questions in my mind:
- In general,
webpack
Different modules are packaged into different typesbundle
或chunk
File, but in usewebpack
fordev
Mode development when I did not in mydist
Found in the directorywebpack
Packed files. Where did they go? - In the view
webpack-dev-server
的package.json
I found it in the filewebpack-dev-middleware
This dependency, this dependencywebpack-dev-middleware
What does it do? - In the study
HMR
Through the processChrome
Developer tools that I know the browser is throughwebsocket
和webpack-dev-server
Corresponded, but I was therewebsocket
No code blocks were found in. So how does the new code block get to the browser? thiswebsocket
What does it do? Why not?websocket
Replacing old and new blocks of code? - How does the browser replace the latest code? Does the substitution process now handle dependencies? What happens if the substitution fails?
With these doubts in mind, I went to explore HMR.
HMR principle diagram
Yes, this is a flowchart of how HMR works.
The figure above shows us the process of triggering webpack packaging from code changes to hot updates on the browser side, and I’ve marked the steps with small flags.
- in
webpack
thedev
Mode,webpack
willwatch
File changes in the file system. Once the file changes are detected,webpack
The relevant modules are repackaged, and the code is stored in memory. webpack
andwebpack-dev-server
The interaction between, among others, is mainly utilizedwebpack-dev-server
In thewebpack-dev-middleware
This middleware callwebpack
Exposed to the outsideAPI
Monitoring code changes.- The third step is to
webpack-dev-server
Monitoring static file changes, unlike the first step, does not monitor code for repackaging. Instead, it listens for static file changes in the configuration file and notifies the browser that it needs to reload if changes occur, i.elive reload
(refresh), andHMR
Not the same. For details, see the related configuration filedevServer.watchContentBase. - Server side amount
webpack-dev-server
usingsockjs
Create one between the browser and the serverwebsocket
Long link, willwebpack
Package changes to inform the browser sidewebpack-dev-server
This also includes static file changes, of course, the most important of which is the difference generated by each packagehash
Value. - Browser side
webpack-dev-server
When it receives a request from the server, it doesn’t do the code replacement itself, it’s just amiddlemen
When the received information changes, he will notifywebpack/hot/dev-server
, this iswebpack
The function module he will be based on the browser sidewebpack-dev-server
The message anddev-server
Determines whether the browser will refresh or hot update. - If the operation is refresh, the browser is directly notified to refresh. If it is a hot update operation, the hot load module is notified
HotModuleReplacement.runtime
theHotModuleReplacement.runtime
It’s the browser sideHMR
Is responsible for receiving the message from the previous stephash
Value, and then notifies and waits for the next module namelyJsonpMainTemplate.runtime
Send the result of the request to the server. HotModuleReplacement.runtime
noticeJsonpMainTemplate.runtime
The module makes a new code request and waits for the code block it returns.JsonpMainTemplate.runtime
The request is first sent to the server and containshash
worthjson
File.- Get all the modules to update
hash
Value, sends a request to the server again, passesjsonp
To get the latest code block and send it toHotModulePlugin
. HotModulePlugin
The new and old modules are compared to determine whether they need to be updated, and if they need to be updated, their dependencies are checked and references between modules are updated as well.
HMR use case details
In the previous section, the author according to the diagram describes the working principle of HMR, of course, you may think to know a probably, the details are still vague, to appear on the English words also feel very strange, it doesn’t matter, then, I will pass one of the most pure chestnut, step by step in combination with the source code to explain the contents of each part.
Let’s start with the simplest example. Here are the demo files
--hello.js;
--index.js;
--index.html;
--package.json;
--webpack.config.js;
Copy the code
Hello.js is the following code
const hello = (a)= > 'hello world';
export default hello;
Copy the code
The index.js file is the following code
import hello from './hello.js';
const div = document.createElemenet('div');
div.innerHTML = hello();
document.body.appendChild(div);
Copy the code
The webpack.config.js file is the following code
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: './index.js'.output: {
filename: 'bundle.js'.path: path.join(__dirname, '/')},devServer: {
hot: true}};Copy the code
We use NPM install to finish installing the dependencies and start devServer.
Next, we move to the key, changing the file contents to trigger the HMR.
// hello.js
- const hello = (a)= > 'hello world'
+ const hello = (a)= > 'hello nice'
Copy the code
At this point the page is rendered from Hello World to Hello Nice. From this process, we analyze the process and principle of hot update step by step.
First, WebPack monitors and packages files
Webpack-dev-middleware calls the API in Webpack to detect file system changes, and when a hello.js file changes, WebPack repackages it and stores the contents in memory.
The specific code is as follows:
// webpack-dev-middleware/lib/Shared.js
if(! options.lazy) {var watching = compiler.watch(
options.watchOptions,
share.handleCompilerCallback
);
context.watching = watching;
}
Copy the code
So why doesn’t Webpack pack files directly into output.path? How can the system access these files without them?
It turns out that Webpack packs files into memory.
Why save the package contents to memory
The reason for this is faster file access and less code writing overhead. This is thanks to the memory-js library that webpack-dev-middleware relies on and replaces the outputFileSystem in Webpack with a MemoryFileSystem instance. This code output to memory, which is part of the source code as follows:
// webpack-dev-middleware/lib/Shared.js
// First determine whether the current fileSystem is already an instance of a MemoryFileSystem
varisMemoryFs = ! compiler.compilers && compiler.outputFileSysteminstanceof MemoryFileSystem;
if (isMemoryFs) {
fs = compiler.outputFileSystem;
} else {
// If not, replace the outputFileSystem before the Compiler with an instance of the MemoryFileSystem
fs = compiler.outputFileSystem = new MemoryFileSystem();
}
Copy the code
Second, devServer notifies the browser that the file has changed
This stage, is to use SOCKJS to carry on the communication between the browser and the server.
When devServer is started, SockJS creates a long webSocket link between the browser and the server so that the browser can be notified of the packaging in real time. The most important part of this is that webpack-dev-server calls the Webpack API to listen for the compile done event. When the compile is complete, Webpack-dev-server sends the hash value of the new module to the browser using the _sendStatus method. The key codes are as follows:
// webpack-dev-server/lib/Server.js
compiler.plugin('done', (stats) => {
// stats.hash is the hash value of the latest packaged file
this._sendStats(this.sockets, stats.toJson(clientStats));
this._stats = stats; }); . Server.prototype._sendStats =function (sockets, stats, force) {
if(! force && stats && (! stats.errors || stats.errors.length ===0) && stats.assets && stats.assets.every(asset= >! asset.emitted)) {return this.sockWrite(sockets, 'still-ok');
}
// Call the sockWrite method to send the hash value through the websocket to the browser
this.sockWrite(sockets, 'hash', stats.hash);
if (stats.errors.length > 0) {
this.sockWrite(sockets, 'errors', stats.errors);
else if (stats.warnings.length > 0) {
this.sockWrite(sockets, 'warnings', stats.warnings);
} else {
this.sockWrite(sockets, 'ok'); }};Copy the code
In step 3, webpack-dev-server/client on the browser side accepts the message and responds
When webpack-dev-server/client receives a hash message of type, it stores the hash temporarily. When the message of type OK is received, the reload operation is performed
In Reload, webpack-dev-server/client determines whether HMR hot update or browser refresh is performed based on the hot configuration:
// webpack-dev-server/client/index.js
hash: function msgHash(hash) {
currentHash = hash;
},
ok: function msgOk() {
// ...
reloadApp();
},
// ...
function reloadApp() {
// If the load is hot
if (hot) {
log.info('[WDS] App hot update... ');
const hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash);
// Otherwise refresh
} else {
log.info('[WDS] App updated. Reloading... '); self.location.reload(); }}Copy the code
Fourth, Webpack receives the latest hash value validation and requests the module code
This step is mainly the coordination between the three modules of Webpack:
webpack/hot/dev-derver
Listening to thewebpack-dev-server/client
Send thewebpackHotUpdate
And callHotModuleReplacement.runtime
In thecheck
Method to monitor for updates.- It will be used in the monitoring process
JsonpMainTemplate.runtime
Two methods inhotDownloadUpdateChunk
.hotDownloadManifest
, the second method is to call Ajax to the server to send a request whether there is an update file, if there is, the update file list will be returned to the browser side, the first method is throughjsonp
Request the latest code block and return the code toHotModuleReplacement
. HotModuleReplacement
Do what you get at the block of code.
As shown in the figure above, both requests are file names that are concatenated using the previous hash value, one for the corresponding hash value and one for the corresponding code block.
Why not just use Websocket to update the code
Personally, I think the author probably wants to decouple the functions, and each module does its own thing. The design of WebSocket here is just for message passing. One interesting thing about using Webpack-hot-middleware is that instead of using websockets, it uses an EventSource for those interested.
Fifth, HotModuleReplacement. Hot update the runtime
This step is critical, because all the heat update operations are done in this step, the main process in HotModuleReplacement. The runtime hotApply this method, I removed the part code snippet below:
// webpack/lib/HotModuleReplacement.runtime
function hotApply() {
// ...
var idx;
var queue = outdatedModules.slice();
while (queue.length > 0) {
moduleId = queue.pop();
module = installedModules[moduleId];
// ...
// Delete expired modules
delete installedModules[moduleId];
// Delete expired dependencies
delete outdatedDependencies[moduleId];
// Remove all child nodes
for (j = 0; j < module.children.length; j++) {
var child = installedModules[module.children[j]];
if(! child)continue;
idx = child.parents.indexOf(moduleId);
if (idx >= 0) {
child.parents.splice(idx, 1); }}}// ...
// Insert new code
for (moduleId in appliedUpdate) {
if (Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) { modules[moduleId] = appliedUpdate[moduleId]; }}// ...
}
Copy the code
As you can see from the above method, there are three stages
- Find expired modules and expired dependencies
- Delete expired modules and expired dependencies
- Add the new module to
modules
, the next call__webpack_require__
“, it is time to get the new module.
What happens when hot updates go wrong
If an error occurs during the hot update, the hot update will fall back to refresh the browser. This is in the dev-server code, which is briefly as follows:
module.hot
.check(true)
.then(function(updatedModules) {
// Is there an update
if(! updatedModules) {// Refresh if not
return window.location.reload();
}
// ...
})
.catch(function(err) {
var status = module.hot.status();
// If something goes wrong
if (['abort'.'fail'].indexOf(status) >= 0) {
/ / refresh
window.location.reload(); }});Copy the code
Update the page last
After replacing the old module with the new module code, our business code cannot know that the code has changed. That is to say, when the hello.js file is modified, we need to call the HMR Accept method in the index.js file to add the processing function after the module update. Insert the return value of the Hello method into the page in time. Here is part of the code:
// index.js
if (module.hot) {
module.hot.accept('./hello.js'.function() {
div.innerHTML = hello();
});
}
Copy the code
That’s how HMR works.
conclusion
This article does not give a detailed analysis of HMR, nor does it explain many details. It just gives a brief overview of the working process of HMR. I hope this article can help you have a better understanding of WebPack.
Latest HMR changes
The P.S version of HMR has been changed to use WebSocket for code replacement. Instead of inserting code blocks through JSONP, jSONP still gets hot updated JSON files to determine hash values.