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,webpackDifferent modules are packaged into different typesbundlechunkFile, but in usewebpackfordevMode development when I did not in mydistFound in the directorywebpackPacked files. Where did they go?
  • In the viewwebpack-dev-serverpackage.jsonI found it in the filewebpack-dev-middlewareThis dependency, this dependencywebpack-dev-middlewareWhat does it do?
  • In the studyHMRThrough the processChromeDeveloper tools that I know the browser is throughwebsocketwebpack-dev-serverCorresponded, but I was therewebsocketNo code blocks were found in. So how does the new code block get to the browser? thiswebsocketWhat does it do? Why not?websocketReplacing 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.

  1. inwebpackthedevMode,webpackwillwatchFile changes in the file system. Once the file changes are detected,webpackThe relevant modules are repackaged, and the code is stored in memory.
  2. webpackandwebpack-dev-serverThe interaction between, among others, is mainly utilizedwebpack-dev-serverIn thewebpack-dev-middlewareThis middleware callwebpackExposed to the outsideAPIMonitoring code changes.
  3. The third step is towebpack-dev-serverMonitoring 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), andHMRNot the same. For details, see the related configuration filedevServer.watchContentBase.
  4. Server side amountwebpack-dev-serverusingsockjsCreate one between the browser and the serverwebsocketLong link, willwebpackPackage changes to inform the browser sidewebpack-dev-serverThis also includes static file changes, of course, the most important of which is the difference generated by each packagehashValue.
  5. Browser sidewebpack-dev-serverWhen it receives a request from the server, it doesn’t do the code replacement itself, it’s just amiddlemenWhen the received information changes, he will notifywebpack/hot/dev-server, this iswebpackThe function module he will be based on the browser sidewebpack-dev-serverThe message anddev-serverDetermines whether the browser will refresh or hot update.
  6. If the operation is refresh, the browser is directly notified to refresh. If it is a hot update operation, the hot load module is notifiedHotModuleReplacement.runtimetheHotModuleReplacement.runtimeIt’s the browser sideHMRIs responsible for receiving the message from the previous stephashValue, and then notifies and waits for the next module namelyJsonpMainTemplate.runtimeSend the result of the request to the server.
  7. HotModuleReplacement.runtimenoticeJsonpMainTemplate.runtimeThe module makes a new code request and waits for the code block it returns.
  8. JsonpMainTemplate.runtimeThe request is first sent to the server and containshashworthjsonFile.
  9. Get all the modules to updatehashValue, sends a request to the server again, passesjsonpTo get the latest code block and send it toHotModulePlugin.
  10. HotModulePluginThe 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:

  1. webpack/hot/dev-derverListening to thewebpack-dev-server/clientSend thewebpackHotUpdateAnd callHotModuleReplacement.runtimeIn thecheckMethod to monitor for updates.
  2. It will be used in the monitoring processJsonpMainTemplate.runtimeTwo 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 throughjsonpRequest the latest code block and return the code toHotModuleReplacement.
  3. HotModuleReplacementDo 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

  1. Find expired modules and expired dependencies
  2. Delete expired modules and expired dependencies
  3. Add the new module tomodules, 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.