Introduction – WebPack Hot Update
Hot Module Replacement, or HMR for short, updates the Module without refreshing the entire page. The benefits of HMR are evident in everyday development work: saving valuable development time and improving the development experience.
Refresh we generally divide into two types:
- One is a page refresh, not to retain the page state, is simple, straightforward
window.location.reload()
. - The other is based on
WDS (Webpack-dev-server)
Module hot replacement, only need to partially refresh the changed modules on the page, at the same time can retain the current page status, such as the check box selected status, input box input, etc.
HMR as a Webpack built-in function, can pass HotModuleReplacementPlugin or opened – hot. So, how exactly does HMR implement hot updates? Let’s have a look at it!
2. Webpack compilation and build process
Once the project starts, the build package is done, the console outputs the build process, and we can observe that a Hash value is generated: a93FD735d02d98633356.
The Compiling...
- A new Hash value:
a61bdd6e82294ed06fa3
- New JSON file:
a93fd735d02d98633356.hot-update.json
- New JS file:
index.a93fd735d02d98633356.hot-update.js
First, we know that the Hash value represents the identity of each compilation. Second, according to the new generated file name, the Hash value output last time will be used as the identifier of the newly generated file during this compilation. In turn, the output Hash value is used as the identifier of the next hot update.
And let’s see, what’s the new file that’s generated? Each time the code changes, recompilation is triggered, and the browser makes two requests. The request is the two newly generated files. As follows:
json
h
Hash
c
index
Then look at the generated JS file, that is the modified code, recompiled after the package.
- A new Hash value:
d2e4208eca62aa1c5389
- New JSON file:
a61bdd6e82294ed06fa3.hot-update.json
However, we found that no new JS file was generated, because no code was changed. At the same time, in the request made by the browser, we can see that the C value is empty, which means that there is no code to be updated this time.
Quietly, in previous versions of Webapck, the hash value did not change, and may have been changed later for some reason. Details need not care, understand the principle is the true meaning!!
Finally consider 🤔. How does the browser know that the native code has been recompiled and quickly requests the newly generated file? Who told the browser? How does the browser get these files and hot update them successfully? So let’s take a look at the hot update process in question, from a source code perspective.
Three, the realization principle of hot update
I’m sure everyone will configure webpack-dev-server hot updates, so I won’t show you an example. It is ok to look up on his net. Let’s take a look at how webpack-dev-server implements hot updates. (The source code is simplified, the first line will indicate the code path, read the best combined with the source food once).
1. Webpack-dev-server Starts the local service
We can find the entry file bin/webpack-dev-server.js according to the bin command in the package.json of webpack-dev-server.
// node_modules/webpack-dev-server/bin/webpack-dev-server.js
// Generate webPack compiler for the main engine
let compiler = webpack(config);
// Start the local service
let server = new Server(compiler, options, log);
server.listen(options.port, options.host, (err) => {
if (err) {throw err};
});
Copy the code
Local service code:
// node_modules/webpack-dev-server/lib/Server.js
class Server {
constructor() {
this.setupApp();
this.createServer();
}
setupApp() {
// Rely on express
this.app = new express();
}
createServer() {
this.listeningApp = http.createServer(this.app);
}
listen(port, hostname, fn) {
return this.listeningApp.listen(port, hostname, (err) => {
// Start the WebSocket service after starting the Express service
this.createSocketServer(); }}}Copy the code
This section of code does three main things:
- Start the
webpack
To generate thecompiler
Instance.compiler
There are many ways to do this, for example, you can startwebpack
allcompileWork, andListening to theChanges to local files. - use
express
Framework start localserver
To allow the browser to request localStatic resource. - local
server
After startup, start againwebsocket
Service, if not understoodwebsocket
, suggest a brief understandingWebsocket crash. throughwebsocket
To establish two-way communication between the local service and the browser. This enables you to immediately tell the browser that you can hot update the code when a local file changes.
The above code does three main things, but the source code does a lot more before starting the service. What else does webpack-dev-server /lib/server.js do?
2. Modify the entry configuration of webpack.config.js
The updateCompiler(this.compiler) method is called before starting the local service. There are two key pieces of code in this method. One is to get the WebSocket client code path, and the other is to get the WebPack hot update code path based on the configuration.
// Get the WebSocket client code
const clientEntry = `The ${require.resolve(
'.. /.. /client/'
)}?${domain}${sockHost}${sockPath}${sockPort}`;
// Get hot update code based on configuration
let hotEntry;
if (options.hotOnly) {
hotEntry = require.resolve('webpack/hot/only-dev-server');
} else if (options.hot) {
hotEntry = require.resolve('webpack/hot/dev-server');
}
Copy the code
The modified webPack entry configuration is as follows:
// New entry
{ entry:
{ index:
[
// The clientEntry obtained above
'xxx/node_modules/webpack-dev-server/client/index.js? http://localhost:8080'.// The hotEntry obtained above
'xxx/node_modules/webpack/hot/dev-server.js'.// Develop the configuration entry
'./src/index.js',}}Copy the code
Why did you add two files? Silently add two files to the entry, which means they will be packaged together into the bundle file, which is the online runtime.
(1) webpack – dev – server/client/index. Js
First of all, this file is used for Websocket, because websocket is two-way communication, if you do not know websocket, it is recommended to briefly understand websocket quick. We started the websocket on the local server during the webpack-dev-server initialization in step 1. What about the client, which is our browser, and the browser doesn’t have the code to talk to the server? You can’t leave it to developers to write HHHHHH. So we need to sneak webSocket client communication code into our code. The specifics of the client side will be discussed later when appropriate.
(2) webpack/hot/dev – server. Js
The main purpose of this file is to check the update logic, so keep it in mind that the code will be covered later when appropriate (step 5).
3. Listen for the end of WebPack compilation
With the entry configuration modified, the setupHooks method is called again. This method is used to register listening events for each webPack compilation.
// node_modules/webpack-dev-server/lib/Server.js
// Bind listener events
setupHooks() {
const {done} = compiler.hooks;
// Listen to the done hook of webPack, which is provided by tapable
done.tap('webpack-dev-server', (stats) => {
this._sendStats(this.sockets, this.getStats(stats));
this._stats = stats;
});
};
Copy the code
When listening to the end of a Webpack compilation, the _sendStats method is called to send a webSocket notification, OK, and hash events to the browser so that the browser can get the latest hash value and check the update logic.
// Send a message to the client through Websocket_sendStats() {
this.sockWrite(sockets, 'hash', stats.hash);
this.sockWrite(sockets, 'ok');
}
Copy the code
4. Webpack listens for file changes
Every time the code is changed, the compilation is triggered. The setupDevMiddleware method is used to monitor native code changes.
This method mainly implements the Webpack-dev-Middleware library. Many people can’t tell the difference between Webpack-dev-middleware and webpack-dev-server. Since webpack-dev-server is only responsible for starting the service and preparing the middleware, all file-related operations are removed to the Webpack-dev-Middleware library. The main tasks are compiling and exporting local files and listening to them.
Let’s take a look at what the Webpack-dev-Middleware source code does:
// node_modules/webpack-dev-middleware/index.js
compiler.watch(options.watchOptions, (err) => {
if (err) { /* Error handling */}});// Write the packaged file to memory through the "memory-fs" library
setFs(context, compiler);
Copy the code
(1) The compiler. Watch method is called. As mentioned in step 1, compiler is powerful. This method basically does two things:
- First, compile and package the local file code, i.e
webpack
A series of compilation processes. - Second, after the compilation, open the local file listening, when the file changes, recompile, after the compilation is completed, continue listening.
Why are code changes saved automatically compiled and repackaged? The compiler. Watch method is responsible for this series of recompilations. The main way to listen for local file changes is to see if the generation time of the file has changed. I won’t go into details here.
(2) Execute the setFs method, the main purpose of which is to package the compiled file into memory. This is why, during development, you’ll find that the dist directory has no packaged code, because it’s all in memory. The reason is that accessing code in memory is faster than accessing files in the file system, and it also reduces the overhead of writing code to a file, all thanks to memory-fs.
5. The browser receives a hot update notification
We can already listen to changes in the file, and when the file changes, we trigger a recompile. It also listens for events at the end of each compilation. When listening to the end of a Webpack compilation, the _sendStats method sends a notification to the browser via websocket to check if a hot update is needed. The next thing to focus on is what the OK and hash events in the _sendStats method do.
So how does the browser receive webSocket messages? Recall the entry file added in step 2, which is the WebSocket client code.
'xxx/node_modules/webpack-dev-server/client/index.js? http://localhost:8080'
Copy the code
The code for this file is packaged into bundle.js, which runs in the browser. Take a look at the core code of this file.
// webpack-dev-server/client/index.js
var socket = require('./socket');
var onSocketMessage = {
hash: function hash(_hash) {
// Update currentHash value
status.currentHash = _hash;
},
ok: function ok() {
sendMessage('Ok');
// Check for updatesreloadApp(options, status); }};// socketUrl,? http://localhost:8080, local service address
socket(socketUrl, onSocketMessage);
function reloadApp() {
if (hot) {
log.info('[WDS] App hot update... ');
// hotEmitter is actually an instance of EventEmitter
var hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash); }}Copy the code
The socket method establishes a connection between the Websocket and the server and registers two listener events.
hash
Event, updated after the latest packaginghash
Value.ok
Event for a hot update check.
The hot update check event is a call to the reloadApp method. Oddly enough, this method uses Node.js EventEmitter to emit webpackHotUpdate messages. Why is that? Why not just check for updates?
Personal understanding is for better code maintenance and a clearer division of responsibilities. Websocket is only used by the client (browser) to communicate with the server. The real work is left to Webpack.
So what does Webpack do? Let’s remember step 2. There is another file not mentioned in the entry file, namely:
'xxx/node_modules/webpack/hot/dev-server.js'
Copy the code
The code for this file is also packaged into bundle.js, which runs in the browser. It’s easy to see what this file does! Take a look at the code:
// node_modules/webpack/hot/dev-server.js
var check = function check() {
module.hot.check(true)
.then(function(updatedModules) {
// Refresh the page directly
if(! updatedModules) {window.location.reload();
return;
}
// The hot update is finished and the information is printed
if (upToDate()) {
log("info"."[HMR] App is up to date.");
}
})
.catch(function(err) {
window.location.reload();
});
};
var hotEmitter = require("./emitter");
hotEmitter.on("webpackHotUpdate".function(currentHash) {
lastHash = currentHash;
check();
});
Copy the code
Here WebPack listens to the webpackHotUpdate event, gets the latest hash value, and finally checks for updates. Check for updates by calling the module.hot.check method. Where does module.hot. Check come from? The answer is HotModuleReplacementPlugin ghost. Here’s a question. Keep reading.
6. HotModuleReplacementPlugin
Seems to have been in front of the webpack dev – server to do the HotModuleReplacementPlugin during hot update and do great things?
First, you can compare the difference between bundle.js when you configure hot updates and when you don’t. Can’t see it in memory? To see the bundle.js file generated, run the webpack command directly. Just don’t start with webpack-dev-server.
(1) Not configured.
HotModuleReplacementPlugin
--hot
moudle
hot
hotCreateModule
module.hot.check
Compare the moudle in __webpack_require__ and the number of lines of code in the packaged file. We can find HotModuleReplacementPlugin originally is also quietly filled a lot of code to bundle. The js ah. This is very similar to step 2. Why? Because checking for updates is done in the browser. This code must be in the runtime environment.
You can also directly look at the browser Sources under the code, will find webpack and plugin secretly added code oh. Debugging is also very convenient here.
HotModuleReplacementPlugin
tapable
plugin
Webpack plug-in mechanism Tapable- source code parsing
7. Moudle.hot. check starts the hot update
In step 6, we can see how the moudle.hot.check method came into being. What did you do? After the source is HotModuleReplacementPlugin into to bundle. The js in the oh, I don’t write down the path to the file.
- Use the last saved
hash
Value, callhotDownloadManifest
sendxxx/hash.hot-update.json
theajax
Requests; - The request results get the hot update module, as well as the next hot update
Hash
Identify and enter the hot update preparation phase.
hotAvailableFilesMap = update.c; // Files to be updated
hotUpdateNewHash = update.h; // Update the hash value for the next hot update
hotSetStatus("prepare"); // Enter the hot update ready state
Copy the code
- call
hotDownloadUpdateChunk
sendxxx/hash.hot-update.js
Request, passJSONP
Way.
function hotDownloadUpdateChunk(chunkId) {
var script = document.createElement("script");
script.charset = "utf-8";
script.src = __webpack_require__.p + "" + chunkId + "." + hotCurrentHash + ".hot-update.js";
if (null) script.crossOrigin = null;
document.head.appendChild(script);
}
Copy the code
The body of this function is separate because it explains why JSONP is used to get the latest code. This is mainly because JSONP gets code that can be executed directly. Why do we just do it? Let’s remember what the code format for /hash.hot-update.js looks like.
You can see that the newly compiled code is inside the body of a webpackHotUpdate function. This means that the webpackHotUpdate method is executed immediately.
Take a look at the webpackHotUpdate method.
window["webpackHotUpdate"] = function (chunkId, moreModules) { hotAddUpdateChunk(chunkId, moreModules); };Copy the code
hotAddUpdateChunk
Method will put the updated modulemoreModules
Assign a value to the global fullhotUpdate
.hotUpdateDownloaded
Method will callhotApply
Make code substitutions.
function hotAddUpdateChunk(chunkId, moreModules) {
// The updated module moreModules is assigned to the global full hotUpdate
for (var moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { hotUpdate[moduleId] = moreModules[moduleId]; }}// Call hotApply to replace the module
hotUpdateDownloaded();
}
Copy the code
8. Replace hotApply module
The core logic for hot updates is in the hotApply method. The hotApply code is nearly 400 lines long, but I’ve got to get to the point. ðŸ˜
(1) Deleting an expired module is the module that needs to be replaced
You can find old modules by using hotUpdate
var queue = outdatedModules.slice();
while (queue.length > 0) {
moduleId = queue.pop();
// Delete expired modules from the cache
module = installedModules[moduleId];
// Delete expired dependencies
delete outdatedDependencies[moduleId];
// The deleted module ID is stored so that the code can be updated easily
outdatedSelfAcceptedModules.push({
module: moduleId
});
}
Copy the code
â‘¡ Add the new module to the modules
appliedUpdate[moduleId] = hotUpdate[moduleId];
for (moduleId in appliedUpdate) {
if (Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) { modules[moduleId] = appliedUpdate[moduleId]; }}Copy the code
(3) Execute the code of the relevant module through __webpack_require__
for (i = 0; i < outdatedSelfAcceptedModules.length; i++) {
var item = outdatedSelfAcceptedModules[i];
moduleId = item.module;
try {
// Execute the latest code
__webpack_require__(moduleId);
} catch (err) {
/ /... Fault-tolerant processing}}Copy the code
HotApply is a bit more complicated, so it’s good to know the general process, but this section requires you to have some understanding of how webPack files are executed, so you can take a look at it.
Four,
Or to read the source code in the form of a drawing, â‘ -â‘£ small mark, is a process of file change.
Write in the last
This is to read the source code of the way to explain the principle, because I feel hot update this piece of knowledge involved more. So the knowledge of the key code out, because each piece of detail can be said to write an article, you can understand the source code again.
It is recommended to know the following knowledge in advance to better understand hot updates:
- Websocket: Basic knowledge of Websocket
- After packaging
bundle
How the file works. webpack
Start the process,webpack
Life cycle.- Tapable: Webpack plug-in mechanism tapable – source code parsing
Refer to the link
- Principle analysis of Webpack Hot Module Replacement
- After reading this, you’ll never be asked Webpack hot updates again
You can also look at the reference article, but because the source version is different, so do not be too entangled with the details.
Original is not easy, through the point of a like it ~😊