This article uses webpack4 as the explanation
What is webPack hot update?
I believe that the students who have done front-end should be familiar with hot update is one of the important functions of Webpack.
When I first wrote HTML, I had to refresh the page manually every time I finished writing the code. Since using this hot update feature of WebPack, it’s been a blast. Instead of manually refreshing, CTRL + S will automatically update the page, which is called hot update.
2. Compilation and construction process of Webpack
Before we look at how WebPack hot updates work, let’s take a look at the build process for WebPack.
Set devServer.stats to normal to see the logging process on the console.
If quiet or noInfo is configured, the configuration does not take effect. Quiet or noInfo needs to be disabled
When we start NPM run dev, we can see the log output on the console:
Here’s a Hash value that represents the identifier for this Webpack Compiling: b6DB0705DDCF5a433075.
When we modify the file and CTRL + S again, we can see the log that the control prints:
The Hash value here has changed: 480FF6313164fc01043e
Js and hot-update.json. Note the hash values of the prefixes of these two files: b6db0705DDcf5a433075.
:
The hash value of the previous compilation should be used to identify the new generated file. The hash value of this compilation will be used to indicate the next compilation of the new generated file.
At the same time, we can see in the browser console that two new files have been loaded:
First look at the JSON file: h represents the hash value generated by this compilation.
C indicates that the file to be hot updated corresponds to the index module. If index is used, then index: true is used.
And the JS file is after we modify this time, recompile and package.
There is also a case where we do not modify this time, but simply save the code and recompile it:
You can see that the console has only one JSON file: the C inside is empty, indicating that there is no code that needs to be updated at this time.
3. Hot update principle of Webpack
webpack-dev-server
We use webpack-dev-server to start the local service, look at the source here:
// node_modules/webpack-dev-server/bin/webpack-dev-server.js // Generate Webpack compiler main engine let compiler = webpack(webpackOptions); // Let server = new server (compiler, options); server.listen(options.port, options.host, (err) => { if (err) throw err; if (options.bonjour) broadcastZeroconf(options); const uri = createDomain(options, server.listeningApp) + suffix; reportReadiness(uri, options); });Copy the code
function Server(compiler, options) { if (! options) options = {}; // Init express server const app = this.app = new express(); // eslint-disable-line this.listeningApp = http.createServer(app); } Server.prototype.listen = function (port, hostname, fn) { const returnValue = this.listeningApp.listen(port, Hostname, (err) => {// Start webSocket service const sockServer = sockjs.createserver ({}); if (fn) { fn.call(this.listeningApp, err); }}); return returnValue; };Copy the code
In the above code, we start Webpack and generate the Compiler instance. Then start the local server so that the browser can request local static resources. Then start the WebSocket service. Through websocket can establish the local server and browser two-way communication function, when the local file changes can inform the browser to do hot update operation.
Entry configuration for webpack.config.js
Before start a local service, calls the server addDevServerEntrypoints
/ / lib/util/addDevServerEntrypoints. Js / / get the websocket client code const domain = the createDomain (options, app); const clientEntry = `${require.resolve( '.. /.. /client/' )}? ${domain}${sockHost}${sockPath}${sockPort}`; HotOnly {hotEntry = require.resolve('webpack/hot/only-dev-server'); } else if (options.hot) { hotEntry = require.resolve('webpack/hot/dev-server'); }Copy the code
After modification, webPack entry configuration is as follows:
// Add two files to the entry, which will be packaged together into the bundle and run online. { entry: { main: [// clientEntry 'XXX /node_modules/webpack-dev-server/client/index.js?http://localhost:8080', // hotEntry 'XXX /node_modules/webpack/hot/dev-server.js', // development configuration entry './ SRC /main.js']}}Copy the code
Webpack-dev-server /client/index.js: This file is used for websocket, which requires two-way communication. When we started the server earlier, we started webSocket locally, but the browser did not communicate with our local server. However, there is no code for webSocket in the browser, so we need to bundle the webSocket browser communication code into our code (bundle).
Webpack /hot/dev-server.js: This file is mainly used to check the updated logic.
Listen for webpack compilation to finish
After modifying the entry configuration, register listening events to listen each time webPack is compiled.
// node_modules/webpack-dev-server/lib/Server.js const { compile, invalid, done } = compiler.hooks; . done.tap('webpack-dev-server', (stats) => { this._sendStats(this.sockets, this.getStats(stats)); this._stats = stats; });Copy the code
// Send a message to the client via websoket _sendStats() {... 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
When listening on the completion of a Webpack compilation, the _sendStates() method is called to notify the browser via websocket: OK and hash events. That’s how you browse to get the latest hash value.
Webpack listens for file changes
Every time we change the code hold locally, a compilation is triggered, which proves that webpack also listens for file changes, mainly through the setupDevMiddleware method.
Webpack-dev-server: Responsible for starting the service and preparing for the pre-installation
Webpack-dev-middleware: Builds and outputs local files and listens.
// node_modules/webpack-dev-server/lib/server.js
setupDevMiddleware() {
// middleware for serving webpack bundle
this.middleware = webpackDevMiddleware(
this.compiler,
Object.assign({}, this.options, { logLevel: this.log.options.level })
);
}
Copy the code
This method basically implements the Webpack-dev-Middleware library. Take a look
// node_modules/webpack-dev-middleware/index.js compiler.watch(options.watchOptions, (err) => {if (err) {/* Error handling */}}); // Use the "memory-fs" library to write the packed file into the memory setFs(context, compiler);Copy the code
(1) Call the compiler.watch method to enable the monitoring of local files. When the files change, recompile and continue to listen after compilation.
(2) Execute the setFs method, whose purpose is to package the compiled file into memory. During development we discovered that there was no dist directory because the code was in memory. Because accessing code in memory is faster than accessing files in the file system, it also reduces the overhead of writing code to files.
The browser receives an update notification
When listening for changes to the file, it sends ok and hash events to the browser.
How does the browser accept webSocket messages?
Remember what we said above about modifying entry? This file will be packaged into bundle.js and run in the browser.
‘xxx/node_modules/webpack-dev-server/client/index.js? http://localhost:8080’
Take a look at this core code:
// webpack-dev-server/client/index.js var socket = require('./socket'); Var onSocketMessage = {hash: function hash(_hash) {// Update currentHash status.currentHash = _hash; },... . ok: function ok() { sendMessage('Ok'); ReloadApp (options, status); }, } socket(socketUrl, onSocketMessage); // webpack-dev-server/client/util/reloadApp.js function reloadApp() { if (hot) { log.info('[WDS] App hot update... '); // 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 a number of listening events. We’ll focus on OK and hash events.
- Hash event: Updates the hash value that was last packaged.
- Ok event: The reloadApp() method is called for hot update detection.
In the reloadApp() method, EventEmitter of Node.js emits the webpackHotUpdate message. Once this message is emitted, what does webPack do next?
In addition to adding ‘XXX /node_modules/webpack-dev-server/client/index.js? http://localhost:8080’, and a new file: ‘XXX /node_modules/webpack/hot/dev-server.js’
Let’s look at the contents of this file:
// node_modules/webpack/hot/dev-server.js var check = function check() { module.hot.check(true) .then(function(updatedModules) {// If (! updatedModules) { window.location.reload(); return; 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
As you can see, webPack listens for the webpackHotUpdate event, retrieves the latest hash value, and checks for updates.
But where does module.hot.check come from?
HotModuleReplacementPlugin
First, compare bundle.js with and without hot updates.
Not configured:
Configured:
A hot: hotCreateModule(moduleId) configuration is found in the file configured with hot update.
As we continue down the hotCreateModule() method, we’ll find the source module.hot.check.
In the browser environment, Webpack and Plugin are surreptitiously coded to check for updates and to facilitate debugging. The main advantage is tapable.
module.hot.check
From the above we can know the module. Hot. Check from the HotModuleReplacementPlugin. Specifically done:
- Using the hash value saved last time, call hotDownloadManifest to send an Ajax request for XXX /hash-update.json
- Result Obtain the hot update module and the Hash id of the next hot update, and enter the hot update preparation phase.
Call hotDownloadUpdateChunl to send XXX /hash.hot-update.js request (JSONP)
function hotDownloadUpdateChunk(chunkId) {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type = "text/javascript";
script.charset = "utf-8";
script.src = __webpack_require__.p + "" + chunkId + "." + hotCurrentHash + ".hot-update.js";
head.appendChild(script);
}
Copy the code
Why use JSONP to get the latest code?
The newly compiled code is inside the body of a webpackHotUpdate function, which immediately executes the webpackHotUpdate method.
// webpackHotUpdate window["webpackHotUpdate"] = function (chunkId, moreModules) { hotAddUpdateChunk(chunkId, moreModules); };Copy the code
The hotAddUpdateChunk method assigns the updated module moreModules to the global variable hotUpdate.
The hotUpdateDownloaded method calls hotApply to replace the code.
function hotAddUpdateChunk(chunkId, MoreModules) {// Updated moreModules assign a value 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
HotApply hot update module replacement
The heart of hot updates is in the hotApply function. Specifically done:
- Delete expired modules, that is, modules that need to be replaced (old modules can be found through hotUpdate)
var queue = outdatedModules.slice(); while (queue.length > 0) { moduleId = queue.pop(); // Remove expired modules from cache module = installedModules[moduleId]; // Delete outdatedDependencies[moduleId]; / / store the deleted module id, easy to update the code outdatedSelfAcceptedModules. Push ({module: moduleId}); }Copy the code
- Add a new module to modules
appliedUpdate[moduleId] = hotUpdate[moduleId]; for (moduleId in appliedUpdate) { if (Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) { modules[moduleId] = appliedUpdate[moduleId]; }}Copy the code
- Execute the code for the relevant module with __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