Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to understand Webpack HMR

2025-01-20 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly explains "how to understand Webpack HMR". Interested friends may wish to have a look. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn how to understand Webpack HMR.

1. Introduction of HMR

Hot Module Replacement (hereinafter referred to as: HMR module hot replacement) is a very useful feature provided by Webpack that allows various modules to be updated while JavaScript is running without a complete refresh.

Hot Module Replacement (or HMR) is one of the most useful features offered by webpack. It allows all kinds of modules to be updated at runtime without the need for a full refresh. -- "Hot Module Replacement"

When we modify the code and save it, Webpack repackages the code, and HMR replaces, adds, or removes modules while the application is running, without reloading the entire page.

HMR significantly speeds up development in the following ways:

Retain the application state that was lost when the page was completely reloaded

Update only the changes to save valuable development time

Adjusting the style is faster-almost equivalent to changing the style in the browser debugger.

It is important to note that HMR is not suitable for production environments, which means that it should only be used in development environments.

II. Mode of use of HMR

It is relatively simple to enable the HMR feature in Webpack:

1. Method 1: use devServer

1.1 set devServer options

Simply add the devServer option to webpack.config.js, set the hot value to true, and use HotModuleReplacementPlugin and NamedModulesPlugin (optional) two Plugins:

/ / webpack.config.js

Const path = require ('path')

Const webpack = require ('webpack')

Module.exports = {

Entry:'. / index.js'

Output: {

Filename: 'bundle.js'

Path: path.join (_ _ dirname,'/')

}

+ devServer: {

+ hot: true, / / hot update HMR of startup module

+ open: true, / / Open the browser page automatically

+}

Plugins: [

+ new webpack.NamedModulesPlugin ()

+ new webpack.HotModuleReplacementPlugin ()

]

}

1.2 add scripts

Then give the scripts command in package.json:

/ / package.json {/ /... "scripts": {+ "start": "webpack-dev-server"}, / /...}

two。 Mode 2. Use command line parameters

The other is achieved by adding the-- hot parameter. After adding the-- hot parameter, devServer tells Webpack to introduce HotModuleReplacementPlugin automatically, rather than manually.

In addition, it is often accompanied by-- open automatically opens the browser to the page.

Here, remove the two Plugins you added earlier:

/ / webpack.config.js const path = require ('path') const webpack = require (' webpack') module.exports = {/ /...-plugins: [- new webpack.NamedModulesPlugin (),-new webpack.HotModuleReplacementPlugin () -]}

Then modify the scripts configuration in the package.json file:

/ / package.json {/ /... "scripts": {- "start": "webpack-dev-server" + "start": "webpack-dev-server-- hot-- open"}, / /.}

3. Simple exampl

Based on the above configuration, we simply implement a scenario: import the hello.js module into the index.js file, and when the hello.js module changes, index.js will update the module.

The module code is implemented as follows:

/ / hello.js export default () = >'hi leovars'; / / index.js import hello from'. / hello.js' const div = document.createElement ('div'); div [XSS _ clean] = hello (); document.body.appendChild (div)

Then import the packaged JS file in index.html and execute npm start to run the project:

Great Webpack HMR Learning Guide

4. Implement snooping updates

When we enable HMR through the HotModuleReplacementPlugin plug-in, its interface is exposed under the global module.hot property. Typically, you can check that the interface is accessible before you start using it.

For example, you can accept an updated module like this:

If (module.hot) {module.hot.accept ('. / library.js', function () {/ / perform some operations using the updated library module.})}

For more API about module.hot, you can check the official document "Hot Module Replacement API".

Going back to the example above, we test the functionality of the update module.

At this point we modify the index.js code to listen for updates in the hello.js module:

Import hello from'. / hello.js'; const div = document.createElement ('div'); div [XSS _ clean] = hello (); document.body.appendChild (div); + if (module.hot) {+ module.hot.accept ('. / hello.js', function () {+ console.log ('now updating hello module ~'); + div [XSS _ clean] = hello (); +}) +}

Then modify the contents of the hello.js file to test the effect:

-export default () = >'hi leavers; + export default () = >'hi leo! hello world'

When we save the code, the console outputs "now updating the hello module ~" and the page says "hi leo!" It is also updated to "hi leo! hello world", which proves that we are listening for file updates.

This is the end of the simple use of Webpack HMR. For more information, please read the official document "Hot Module Replacement".

5. Common configurations and skills of devServer

5.1 Common configuration

Depending on the directory structure, the contentBase and openPage parameters should be configured with appropriate values, otherwise the runtime should not immediately access your home page. At the same time, pay attention to your publicPath. The path generated by the packaging of static resources is a point to think about, depending on your directory structure.

DevServer: {contentBase: path.join (_ _ dirname, 'static'), / / tell the server where to provide content (default current working directory) openPage:' views/index.html', / / specify the page that opens when the browser starts by default index: 'views/index.html', / / specify the home page location watchContentBase: true / / change the file under contentBase to the reload page (default false) host: 'localhost', / / default localhost. If you want external accessibility, use' 0.0.0.0' port: 8080, / / default 8080 inline: true, / / you can monitor js changes hot: true, / / Hot start open: true, / / automatically open the browser at startup (specify to open chrome Open: 'Google Chrome') compress: true, / / all services enable gzip compression disableHostCheck: true, / / true: no host check quiet: false, https: false, clientLogLevel:' none', stats: {/ / prompt for setting console chunks: false, children: false, modules: false, entrypoints: false / / whether to output entry information warnings: false, performance: false, / / whether to export webpack suggestions (such as file size)}, historyApiFallback: {disableDotRule: true,}, watchOptions: {ignored: / node_modules/, / / skip the node_modules directory}, proxy: {/ / interface proxy (this configuration is recommended: write to package.json Then introduced here) "/ api-dev": {"target": "http://api.test.xxx.com"," secure ": false," changeOrigin ": true," pathRewrite ": {/ / rewrite a paragraph on the url (for example, replace api-dev here with a blank)" ^ / api-dev ":"} Before (app) {},}

Tip 1: output dev-server code as a file

The code output by dev-server is usually in memory, but it can also be written to the hard disk to produce physical files:

DevServer: {writeToDisk: true,}

Usually can be used for proxy mapping file debugging, compilation will produce a lot of js files with hash, files without hash are also compiled in real time.

Tip 2: start the service using local IP by default

Sometimes, when you start the service, you want to open it using the local ip address by default:

DevServer: {disableHostCheck: true, / / true: no host check / / useLocalIp: true, / / it is not recommended to configure / / host: '0.0.0.0, / / it is not recommended to configure here}

At the same time, you also need to configure host to 0.0.0.0. It is recommended to append it to the scripts command instead of writing it dead in the configuration, otherwise you don't want to change this way back in the future. To be clever, add a new command:

"dev-ip": "yarn run dev--host 0.0.0.0-- useLocalIp"

5.4 Tip 3: specify the debug domain name to start

Sometimes you want to start with the specified debug domain name, for example: local.test.baidu.com:

DevServer: {open: true, public: 'local.test.baidu.com:8080', / / Port port: 8080,}

At the same time, 127.0.0.1 needs to be modified to the specified host, which can be modified with the help of iHost and other tools, which are more or less the same, with the following format:

127.0.0.1 local.test.baidu.com

Local.test.baidu.com:8080 access will be automatically opened after the service is started.

Tip 4: start gzip compression

DevServer: {compress: true,}

III. Introduction to the basic principles of HMR

From the previous introduction, we know that the main function of HMR is to replace, add, or remove modules while the application is running, without reloading the entire page.

So, how do the file changes caused by the Webpack compilation source code relate to the replacement module implementation at run time at compile time?

With these two questions in mind, let's take a brief look at the core workflow of HMR (simplified version):

HMR Workflow Chart. PNG

Next, start the HMR workflow analysis:

When Webpack (Watchman) listens for a change in the file / module code in the project, it notifies the build tool (Packager) in Webpack of the change, that is, HMR Plugin

After HMR Plugin processing, the result is sent to the runtime framework (HMR Runtime) of the application (Application).

Finally, HMR Runtime updates (adds / deletes or replaces) these changed files / modules to the module system.

Among them, HMR Runtime is injected by the build tool at compile time, corresponds the compile-time files to the runtime modules through a unified Module ID, and provides a series of API provisioning layer frameworks (such as React) calls.

Note: it is recommended that you first understand the general process of the above picture and read it later. Don't worry, I'll wait for everyone.

IV. The complete principle and source code analysis of HMR

From the previous section, we probably know the simple workflow of HMR, so you may still have a lot of doubts: what is the notification of file updates to HMR Plugin? How does HMR Plugin send updates to HMR Runtime? Wait a minute.

Then we begin to analyze the whole hot update process of HMR module in detail with the source code. First of all, we should take a look at the flow chart, without knowing the name of the method in the figure (red font, yellow background color):

Webpack HMR.png

The figure above shows a complete HMR workflow from the modification of the code to the hot update of the module, which has been identified with red Arabic numerals.

To understand how the above works, let's first understand the name concepts in the figure:

Webpack-dev-server: a server plug-in, equivalent to an express server, starts a Web service and is only applicable to the development environment

Webpack-dev-middleware: a Webpack-dev-server middleware, the role is simply summarized as follows: through watch mode, listen for changes to resources, and then automatically package.

Webpack-hot-middleware: middleware used in conjunction with Webpack-dev-middleware, which enables no refresh update of browsers, namely HMR

Let's learn the whole working principle of HMR:

1. Monitor code changes, recompile and package

First, according to the devServer configuration, using npm start will start Webpack-dev-server to start the local server and enter the watch mode of Webpack, then initialize the Webpack-dev-middleware, and watch the file system in Webpack-dev-middleware by calling the startWatch () method:

/ / webpack-dev-server\ bin\ webpack-dev-server.js / / 1. Start local server Line 386server = new Server (compiler, options); / / webpack-dev-server\ lib\ Server.js / / 2. Initialize Webpack-dev-middleware Line 109this.middleware = webpackDevMiddleware (compiler, Object.assign ({}, options, wdmOptions)); / / webpack-dev-middleware\ lib\ Shared.js / / 3. Start the watch file system Line 171startWatch: function () {/ /... / / start watching if (! options.lazy) {var watching = compiler.watch (options.watchOptions, share.handleCompilerCallback); context.watching = watching;} / /.} share.startWatch (); / /.

When the startWatch () method is executed, it enters watch mode, and if the code in the file is found to be modified, the module is recompiled and packaged according to the configuration file.

two。 Save the compilation result

Webpack interacts with Webpack-dev-middleware, and Webpack-dev-middleware calls Webpack's API to monitor the code changes and tells Webpack to keep the recompiled code in memory through the JavaScript object.

We will find that there is no file for the compilation results in the dist directory specified by output.path. Why?

In fact, Webpack keeps the compilation results in memory because accessing code in memory is faster than accessing files in the file system, which reduces the overhead of code writing to files.

Webpack can save the code to memory, thanks to Webpack-dev-middleware 's memory-fs dependent library, which replaces the original outputFileSystem with an instance of MemoryFileSystem, and then outputs the code to memory. Some of the source codes are as follows:

/ / webpack-dev-middleware\ lib\ Shared.js Line 108 / / store our files in memory var fs; var isMemoryFs =! compiler.compilers & & compiler.outputFileSystem instanceof MemoryFileSystem; if (isMemoryFs) {fs = compiler.outputFileSystem;} else {fs = compiler.outputFileSystem = new MemoryFileSystem ();} context.fs = fs

The above code first determines whether fileSystem is an instance of MemoryFileSystem, and if not, replaces the outputFileSystem before compiler with an instance of MemoryFileSystem. In this way, the bundle.js file code is saved in memory as a simple JavaScript object. When the browser requests a bundle.js file, devServer goes directly to memory to find the saved JavaScript object and return it to the browser.

3. Monitor file changes and refresh the browser

Webpack-dev-server starts monitoring file changes, and unlike step 1, it's not monitoring code changes, recompiling and packaging.

When we configure devServer.watchContentBase as true in the configuration file, Webpack-dev-server will listen for changes in the static files in the configuration folder and notify the browser to refresh the application when it changes, which is different from HMR.

/ / webpack-dev-server\ lib\ Server.js / / 1. Read the parameter Line 385if (options.watchContentBase) {defaultFeatures.push ('watchContentBase');} / / 2. Define _ watch method Line 697 Server.prototype._watch = function (watchPath) {/ /... Const watcher = chokidar.watch (watchPath, options) .on ('change', () = > {this.sockWrite (this.sockets,' content-changed');}); this.contentBaseWatchers.push (watcher);}; / / 3. Execute _ watch () to listen for file changes Line 339 watchContentBase: () = > {if (/ ^ (https?:)?\ /\ / .test (contentBase) | | typeof contentBase = = 'number') {throw new Error (' Watching remote files is not supported.');} else if (Array.isArray (contentBase)) {contentBase.forEach ((item) = > {this._watch (item);}) } else {this._watch (contentBase);}}

4. Establish WS and synchronize the compilation phase status

This step is handled in Webpack-dev-server, mainly through sockjs (Webpack-dev-server dependency) to establish a WebSocket persistent connection between the browser side (Client) and the server side (Webpack-dev-middleware) of Webpack-dev-server.

Then synchronize the status information of each stage of Webpack compilation and packaging to the browser side. There are two important steps:

Sending status

Webpack-dev-server listens for the done event of compile through Webpack API, and when the compile is completed, Webpack-dev-server sends the hash value of the compiled new module to the browser using socket through the _ sendStats method.

Save statu

The browser saves the hash sent by _ sendStats, and it will use the hot update of the module.

/ / webpack-dev-server\ lib\ Server.js / / 1. Define _ sendStats method Line / / send stats to a socket or multiple sockets Server.prototype._sendStats = function (sockets, stats, force) {/ /. This.sockWrite (sockets, 'hash', stats.hash);}; / / 2 Listen to the done event Line 86 compiler.plugin ('done', (stats) = > {/ / pass the hash value (stats.hash) of the latest packaged file as a parameter to _ sendStats () this._sendStats (this.sockets, stats.toJson (clientStats); this._stats = stats;}); / / webpack-dev-server\ client\ index.js / / 3. Save the hash value Line 74 var onSocketMsg = {/ /... Hash: function hash (_ hash) {currentHash = _ hash;}, / /...} socket (socketUrl, onSocketMsg)

5. The browser side publishes messages

When the hash message is sent, socket also sends an ok message informing Webpack-dev-server that since the client (Client) does not request a hot update code or perform a hot update module operation, the work is transferred back to Webpack through an emit "webpackHotUpdate" message.

/ / webpack-dev-server\ client\ index.js / / 1. Processing ok messages Line 135 var onSocketMsg = {/ /... Ok: function ok () {sendMsg ('Ok'); if (useWarningOverlay | | useErrorOverlay) overlay.clear (); if (initial) return initial = false; / / eslint-disable-line no-return-assign reloadApp ();}, / /...} / / 2. Processing refresh APP Line 218function reloadApp () {/ /... If (_ hot) {/ / dynamic load emitter var hotEmitter = require ('webpack/hot/emitter'); hotEmitter.emit (' webpackHotUpdate', currentHash); if (typeof self! = = 'undefined' & & self.window) {/ / broadcast update to window self.postMessage (' webpackHotUpdate' + currentHash,'*');}} /.}

6. Pass hash to HMR

Webpack/hot/dev-server listens to the browser-side webpackHotUpdate message, transfers the hash value of the new module to the HotModuleReplacement.runtime of the client HMR core, and calls the check method to detect the update to determine whether the browser is refreshed or the module is hot updated. If it is a browser refresh, there are no next steps.

/ / webpack\ hot\ dev-server.js / / 1. Monitor webpackHotUpdate Line 42 var hotEmitter = require (". / emitter"); hotEmitter.on ("webpackHotUpdate", function (currentHash) {lastHash = currentHash; if (! upToDate ()) & & module.hot.status () = = "idle") {log ("info", "[HMR] Checking for updates on the server..."); check ();}}) Var check = function check () {module.hot.check (true) .then (function (updatedModules) {if (! updatedModules) {/ /... _ window.location.reload (); / / browser refresh return;} if (! upToDate ()) {check () ) .catch (function (err) {/ *... * /});}; / / webpack\ lib\ HotModuleReplacement.runtime.js / / 3. Call the check method Line 167function hotCheck (apply) {if (hotStatus! = = "idle") throw new Error ("check () is only allowed in idle status"); hotApplyOnUpdate = apply; hotSetStatus ("check"); return hotDownloadManifest (hotRequestTimeout) .then (function (update) {/ /...});}

7. Detect if there is an update

When HotModuleReplacement.runtime calls the check method, two methods in JsonpMainTemplate.runtime, hotDownloadUpdateChunk (to get the latest module code) and hotDownloadManifest (to get whether there is an update file) are called. The source code of these two methods is expanded in the next step.

/ / webpack\ lib\ HotModuleReplacement.runtime.js / / 1. Call HotModuleReplacement.runtime to define the hotDownloadUpdateChunk method Line 171function hotCheck (apply) {if (hotStatus! = = "idle") throw new Error ("check () is only allowed in idle status"); hotApplyOnUpdate = apply; hotSetStatus ("check"); return hotDownloadManifest (hotRequestTimeout) .then (function (update) {/... {/ hotEnsureUpdateChunk method calls hotDownloadUpdateChunk hotEnsureUpdateChunk (chunkId)) });}

HotDownloadUpdateChunk is called in the hotEnsureUpdateChunk method:

/ / webpack\ lib\ HotModuleReplacement.runtime.js Line 215 function hotEnsureUpdateChunk (chunkId) {if (! hotAvailableFilesMap [chunkId]) {hotWaitingFilesMap [chunkId] = true;} else {hotRequestedFilesMap [chunkId] = true; hotWaitingFiles++; hotDownloadUpdateChunk (chunkId);}}

8. Request to update the latest file list

When calling the check method, the hotDownloadManifest method in JsonpMainTemplate.runtime is called first to obtain whether there is an update file by issuing an AJAX request to the server, and if so, the mainfest is returned to the browser.

If some native XMLHttpRequest is involved here, not all of them will be posted.

/ / webpack\ lib\ JsonpMainTemplate.runtime.js / / hotDownloadManifest defines Line 22 function hotDownloadManifest (requestTimeout) {return new Promise (function (resolve, reject) {try {var request = new XMLHttpRequest (); var requestPath = $require$.p + $hotMainFilename$; request.open ("GET", requestPath, true); request.timeout = requestTimeout; request.send (null) } catch (err) {return reject (err);} request.onreadystatechange = function () {/ /...};});}

9. Request to update the latest module code

In the hotDownloadManifest method, the hotDownloadUpdateChunk method is also executed, requesting the latest module code through JSONP, and returning the code to HMR runtime.

HMR runtime then processes the new code further to determine whether it is a browser refresh or a module hot update.

/ / webpack\ lib\ JsonpMainTemplate.runtime.js / / hotDownloadManifest define Line 12 function hotDownloadUpdateChunk (chunkId) {/ / create a script tag and initiate a JSONP request var head = document.getElementsByTagName ("head") [0]; var script = document.createElement ("script"); script.type = "text/javascript"; script.charset = "utf-8"; script.src = $require$.p + $hotChunkFilename$; $crossOriginLoading$; head.appendChild (script);}

10. Update modules and dependency references

This step is the core step of the whole module hot update (HMR), through the hotApply method of HMR runtime, remove expired modules and code, and add new modules and code to achieve hot update.

As can be seen from the hotApply method, module hot replacement is mainly divided into three stages:

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

Find out the expiration module outdatedModules and the expiration dependency outdatedDependencies

/ / webpack\ lib\ HotModuleReplacement.runtime.js / / find outdatedModules and outdatedDependencies Line 342function hotApply () {/ /. Var outdatedDependencies = {}; var outdatedModules = []; function getAffectedStuff (updateModuleId) {var outdatedModules = [updateModuleId]; var outdatedDependencies = {}; / /. Return {type: "accepted", moduleId: updateModuleId, outdatedModules: outdatedModules, outdatedDependencies: outdatedDependencies};}; function addAllToSet (a, b) {for (var I = 0; I

< b.length; i++) { var item = b[i]; if (a.indexOf(item) < 0) a.push(item); } } for(var id in hotUpdate) { if(Object.prototype.hasOwnProperty.call(hotUpdate, id)) { // ... 省略多余代码 if(hotUpdate[id]) { result = getAffectedStuff(moduleId); } if(doApply) { for(moduleId in result.outdatedDependencies) { // 添加到 outdatedDependencies addAllToSet(outdatedDependencies[moduleId], result.outdatedDependencies[moduleId]); } } if(doDispose) { // 添加到 outdatedModules addAllToSet(outdatedModules, [result.moduleId]); appliedUpdate[moduleId] = warnUnexpectedRequire; } } } } 2. 从缓存中删除过期模块、依赖和所有子元素的引用; // webpack\lib\HotModuleReplacement.runtime.js // 从缓存中删除过期模块、依赖和所有子元素的引用 Line 442 function hotApply() { // ... var idx; var queue = outdatedModules.slice(); while(queue.length >

0) {moduleId = queue.pop (); module = installedModules [moduleId]; / / remove the module delete installedModules [moduleId] from the cache; / / remove the processing method delete outdatedDependencies [moduleId] that is not needed in the expiration dependency; / / remove the reference for of all child elements (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);} / / remove obsolete dependencies from module subcomponents var dependency; var moduleOutdatedDependencies; for (moduleId in outdatedDependencies) {if (Object.prototype.hasOwnProperty.call (outdatedDependencies, moduleId)) {module = installedModules [moduleId]; if (module) {moduleOutdatedDependencies = outdatedDependencies [moduleId] For (j = 0; j)

< moduleOutdatedDependencies.length; j++) { dependency = moduleOutdatedDependencies[j]; idx = module.children.indexOf(dependency); if(idx >

= 0) module.children.splice (idx, 1);}}

3. Add the new module code to modules, and the next time you call the _ _ webpack_require__ (webpack overridden require method) method, you get the new module code.

/ / webpack\ lib\ HotModuleReplacement.runtime.js / / add the new module code to modules Line 501 function hotApply () {/ /. For (moduleId in appliedUpdate) {if (Object.prototype.hasOwnProperty.call (appliedUpdate, moduleId)) {modules [moduleId] = appliedUpdate [moduleId];}

After the execution of the hotApply method, the new code has replaced the old code, but our business code is not aware of these changes, so we need to notify the application layer to use the new module for a "local refresh" through the accept event, as we use in the business:

If (module.hot) {module.hot.accept ('. / library.js', function () {/ / perform some operations using the updated library module.})}

11. Hot update error handling

During the hot update process, abort or fail errors may occur during the hotApply process, then the hot update is returned to the refresh browser (Browser Reload), and the whole module hot update is completed.

/ / webpack\ hot\ dev-server.js Line 13 module.hot.check (true) .then (function (updatedModules) {if (! updatedModules) {return _ window.location.reload ();} / /.}) .catch (function (err) {var status = module.hot.status () If (["abort", "fail"] .indexOf (status) > = 0) {_ window.location.reload ();}}). At this point, I believe you have a better understanding of "how to understand Webpack HMR". You might as well do it in practice! Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report