The webpack-dev-server property configuration was described in the previous article
This article will briefly take a look at the internal implementation of Webpack-dev-server.
Because it involves source code parsing, it will involve some of the more difficult knowledge to chew, I will try to simplify the description.
However, if you still have difficulty or are not interested in the internal implementation of Webpack-dev-server, you can skip this article altogether.
Debugging webpack – dev – server
Configuring the Debugging Mode
In daily development, if you are not familiar with the code logic, the easiest way is to debug, step by step to observe the flow.
The easiest way to learn webpack-dev-server source code is to debug it, but debugging webpack-dev-server source code is not as simple as debugging itself, it must do some configuration,
The following is a brief introduction to two source code webpack-dev-server
Browser debugging
It is now known that WebPack is executed in node.js, so debugging WebPack means debugging Node.js programs.
The great Chrome browser provides a solution for debugging Node.js applications.
Start by adding a debug directive to the package.json file scripts property, which is used to launch the debug Node.js program
The inspect-brk property sets the debug Node.js parameters:
5858 indicates the port number for starting the Node.js program
./node_modules/webpack-dev-server/bin/webpack-dev-server.js represents the specified file for debugging,
The next step is to set up the browser
Typing Chrome ://inspect into the Chrome browser address bar takes you to a Settings page
:whale2: Because I’m using a new version of Edge, it shows Edge ://inspect.
Click Open Dedicated DevTools for Node to access the debug panel
Set the node.js port number to be debugged in the debug panel
If yarn Debug is enabled, Chrome captures yarn debug
IDE debug code
Common IDE tools can debug Node.js programs.
You can configure and debug webpack-dev-server using IDE tools.
I use WebStorm. Zero configuration debugging is possible
To Debug the current command, click Debug start
As for VS Code, I haven’t used it and don’t know much about configuration information.
Webpack-dev-server starts the process
The following is a brief introduction to the webpack-dev-server startup process.
Only the key process code is described here. The specific details of each step interested friends can debug view
The first file executed by webpack-dev-server is the /bin/webpack.js file module of the Webpack library, which is the entry file
The reason has been described in the previous article: [email protected] start, the webpack-dev-server command is changed to webpack serve. The webpack command was used
The /bin/cli.js file module of webpack-cli library is called in the /bin/webpack.js file module of Webpack library.
:whale2: check whether webpack-CLI is installed when executing webpack-cli is executed in the webpack library /bin/webpack.js file module, interested friends can check by themselves.
After that, the lib/bootstrap.js startup file module was called in the /bin/cli.js file module of webpack-CLI
Then create the /lib/webpack-cli.js module instance (WebpackCLI) in the lib/bootstrap.js startup file module and call run()
Then webpackcli.run () calls the corresponding file in the @webpack-cli library according to the command.
For example, webpack serve, call @webpack-CLI library /serve/ module according to the serve command parameter.
:whale2::whale2: @webpack- CLI is [email protected] version dependent
Call the /serve/ startDevserver.js file module from the /serve/index.js file module in @webpack-cli
/serve/ startDevserver. js create webpack-dev-server /lib/ server. js module instance object.
And a call to server.listen () listens to start the Server.
This is the webpack-dev-server startup process.
The Webpack-dev-server server starts up calling code across several libraries,
Webpack –> webpack-cli –> @webpack-cli –> webpack-dev-server. But once the execution logic is clear, it shouldn’t be too difficult to understand
Source code module parsing
Express server
As mentioned in the previous article, Webpack-dev-Server uses the Express framework internally as a server.
Let’s start with Express,
The server for webpack-dev-server is the /lib/server.js file module.
Express is in this module.
The /lib/server.js module constructor initializes many things, including Express
Express is initialized in the /lib/server.js constructor
You can see the Express Server instance built in server.setupapp ()
The value is then assigned to server.listeningApp in server.createserver ().
Finally, a boot is performed in server.listen ().
Server.listen() is called by the /serve/ startDevserver.js file module in @webpack-cli.
:whale2: server.setupapp () and server.createserver () added configured middleware for Express.
Listen for code file updates
In Webpack-dev-server, the main function is to listen for code changes to trigger recompilation and notify the browser to update data after compilation.
Listening for code file changes to trigger recompilation is a feature provided by WebPack itself.
The WebPack configuration item has a Watch property that indicates whether you want to be in a listening state, listening for code file changes
Let’s start with a simple test.
Create a new webpack.watch.js profile and set the Watch properties in this profile
const { merge } = require('webpack-merge');
const common = require('./webpack.common');
// Use node. Export the js configuration to export
module.exports = merge([
common(true),
{
mode:'development'.// Listen for file changes
watch: true.// Listen on options
watchOptions: {
// Ignore directories that can be regular or array
ignored: /node_modules/.// Start polling, default false Set true or milliseconds to start polling
poll: false}}])Copy the code
Add the watch directive to package.json to execute the webapck command.
After the yarn watch command is executed, the wating state is kept waiting for the code file to change
Updating the code triggers repackaging,
The Watch property simply provides the watching state to wait for the code file to change and recompile.
The :whale2: watch property will only listen for referenced code module files. It will not listen for unreferenced code module files.
The package file is memorized
When you use webpack-dev-server, you find that no local packaging files are generated.
As mentioned in the previous article, webpack-dev-server packs files in memory streams by default to increase access speed
By default, webpack-dev-server uses the webpack-dev-middleware middleware to ram packaged files
The webpack-dev-middleware middleware is also added to the /lib/server.js constructor
Webpack dev – middleware/dist/index, js module calls/dist/utils/setupOutputFileSystem js module, this module is to set the output stream function
As you can see, webpack-dev-Middleware uses MEMFS, a memory stream library, as an output stream by default
:whale2: is a library for node.js memory streams
:whale2: webpack-dev-Middleware The main project file is /dist/ css. js, which calls /dist/index.js from /dist/ css. js.
WebSocket
The core function of Webpack-dev-server notifies the browser to update data after compilation is completed using WebSocket.
Instantiate WebSocket Server after the Server is started. Long links are made to the browser (WebSocket Client) during browser access.
When the code is compiled, the WebSocket Server is used to push messages to the browser (WebSocket Client)
The browser (WebSocket Client) completes the operation after receiving the message.
WebSocket Server
The Server to create
WebSocket Server is instantiated in server.listen ().
After successfully starting the server, determine whether hot or liveReload is set. If set call Server. CreateSocketServer () instantiate the WebSocket Server
Server. CreateSocketServer (), you can choose according to configuration WebSocket Server type.
Then instantiate the WebSocket Server.
[email protected] uses the webSocketServer.js type, which is ws, by default
[email protected] uses the sockjsServer. js type by default, that is, sockjs
:whale2::whale2:
Currently there are problems with using WS in [email protected] version and problems with using SockJS in [email protected] version.
Therefore, WebSocket Server simply uses the default
Being pushed
The server.js module has a sockWrite() function that calls WebSocket Server to push messages.
Webpack-dev-server uses WebSocket Server to push messages on three occasions
Client connection
Connecting on the client side (browser access)
WebSocket Server pushes initialization information. For example, whether to enable hot update, overlay configuration information, whether to output package build progress, enter log level, etc.
Code compiled
After each code is packaged and compiled, WebSocket Server pushes the compilation information to the client (browser).
Inside WebPack, a series of hook events are exposed after the compile is complete (compile succeeded, compile failed).
Webpack-dev-server uses this hook function.
Listen for the WebPack hook function in the server.js constructor
After compiling, use WebSocket Server to push messages
The static file has changed
Webpack-dev-server provides the WebSocket Server push message notifying the client (browser) to automatically refresh the browser when static files change.
WebSocket Client
The WebSocket client is the WebSocket client (browser). When receiving a message, the WebSocket client performs operations based on the message type, such as refreshing the browser, obtaining the latest code data, and outputting logs.
The WebSocket client code is stored in the /client directory of webpack-dev-server. The WebSocket client has two types: SockJSClient and WebsocketClient, corresponding to the WebSocket Server
Message processing
The WebSocket Client message processing logic is written in the /client/default/index.js module file.
The /client/default/index.js module file has an onSocketMessage object, which is the message handling object.
Copy the onSocketMessage object here. Add some comments to make it easier to understand, but I won’t repeat the details.
The code is actually pretty easy to understand.
Interested friends can look at the source code.
var onSocketMessage = {
hot: function hot() {
// If you push hot, use HMR. Push after the client connects
options.hot = true;
log.info('Hot Module Replacement enabled.');
},
liveReload: function liveReload() {
// If you push liveReload, you are using liveReload. Push after the client connects
options.liveReload = true;
log.info('Live Reloading enabled.');
},
invalid: function invalid() {
// Listen for an invalid Webpack compilation push. Invalid, like done, is a Webpack hook function
log.info('App updated. Recompiling... '); // fixes #1042. overlay doesn't clear if errors are fixed but warnings remain.
// If an error page is set, the page is cleared
if (options.useWarningOverlay || options.useErrorOverlay) {
overlay.clear();
}
sendMessage('Invalid');
},
hash: function hash(_hash) {
// The hash value of the current code module.
status.currentHash = _hash;
},
'still-ok': function stillOk() {
log.info('Nothing changed.');
if (options.useWarningOverlay || options.useErrorOverlay) {
overlay.clear();
}
sendMessage('StillOk');
},
logging: function logging(level) {
// The client displays the log level and pushes it after the client connects
// this is needed because the HMR logger operate separately from
// dev server logger
var hotCtx = require.context('webpack/hot'.false./^\.\/log$/);
if (hotCtx.keys().indexOf('./log')! = = -1) {
hotCtx('./log').setLogLevel(level);
}
setLogLevel(level);
},
overlay: function overlay(value) {
// Sets whether to display error pages when compiling errors. Push after the client connects
if (typeof document! = ='undefined') {
if (typeof value === 'boolean') {
options.useWarningOverlay = false;
options.useErrorOverlay = value;
} else if(value) { options.useWarningOverlay = value.warnings; options.useErrorOverlay = value.errors; }}},progress: function progress(_progress) {
// Whether to display the current packaging progress and push it after the client connects
if (typeof document! = ='undefined') { options.useProgress = _progress; }},'progress-update': function progressUpdate(data) {
// Current packaging progress
if (options.useProgress) {
log.info("".concat(data.percent, "% -").concat(data.msg, "."));
}
sendMessage('Progress', data);
},
ok: function ok() {
// Compile successfully push current type,
sendMessage('Ok');
// If an error page is set, the page is cleared
if (options.useWarningOverlay || options.useErrorOverlay) {
overlay.clear();
}
if (options.initial) {
return options.initial = false;
}
// Reload data with this method, which handles HMR
reloadApp(options, status);
},
'content-changed': function contentChanged() {
// Static file changes when push, refresh the page
log.info('Content base changed. Reloading... ');
self.location.reload();
},
warnings: function warnings(_warnings) {
// Push warning after compiling
log.warn('Warnings while compiling.');
var strippedWarnings = _warnings.map(function (warning) {
return stripAnsi(warning);
});
sendMessage('Warnings', strippedWarnings);
for (var i = 0; i < strippedWarnings.length; i++) {
log.warn(strippedWarnings[i]);
}
// If the error page is displayed, the warning is displayed
if (options.useWarningOverlay) {
overlay.showMessage(_warnings);
}
if (options.initial) {
return options.initial = false;
}
// Reload data with this method, which handles HMR
reloadApp(options, status);
},
errors: function errors(_errors) {
// Push after compile error
log.error('Errors while compiling. Reload prevented.');
var strippedErrors = _errors.map(function (error) {
return stripAnsi(error);
});
sendMessage('Errors', strippedErrors);
for (var i = 0; i < strippedErrors.length; i++) {
log.error(strippedErrors[i]);
}
// If the display error page is set to error, then display
if (options.useErrorOverlay) {
overlay.showMessage(_errors);
}
options.initial = false;
},
error: function error(_error) {
log.error(_error);
},
close: function close() {
// Push before WebSocket Server is shut down
log.error('Disconnected! ');
sendMessage('Close'); }};Copy the code
reloadApp
ReloadApp () code in the/client/default/utils/reloadApp js file.
This function is personally felt to be a key function, so it is briefly introduced here.
ReloadApp () is the processing logic when the code file changes.
ReloadApp () does different processing based on the hot and liveReload property types.
When hot, load the Webpack/Hot/Emitter module, which is used to trigger hot updates.
The Webpack/Hot/Emitter module is an event defined by hot updates in Webpack.
When liveReload, the page is refreshed to retrieve the data
The two hot and liveReload properties control whether the code can be updated in real time
But while Hot updates the page hot, liveReload refreshes the page directly
Hot has a higher priority than liveReload
HMR
When it comes to Webpack-dev-Server, hot update (HMR) is always a topic. Hot update (HMR) is an important feature provided by Webpack-Dev-Server
When you’re developing, you often update a small piece of code and need to see the effect in the browser.
If you just update a small piece of code, let the browser refresh to reload all the data. That would be a huge waste of time.
The best result would be to package a module file that records current updates at compile time and let the browser update only that module file.
This technique is called Hot Module Replacement
Webpack – dev – server, hot update (HMR) is provided by a webpack built-in plugin: HotModuleReplacementPlugin
When using webpack-dev-server, webpack-dev-server automatically adds this plugin if the hot attribute is set
: whale2: this code in webpack – dev – server/lib/utils/DevServerPlugin js
HotModuleReplacementPlugin after each compilation generates a hash, corresponding to the compiled files. The WebSocket push type has a hash type, which is the hash of the push
The WebSocket client stores hashes when processing messages.
The webpackHotUpdate event is then executed in reloadApp() to pull the latest code based on the current hash.
hash: function hash(_hash) {
status.currentHash = _hash;
},
Copy the code
if (hot) {
log.info('App hot update... ');
var hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash);
if (typeofself ! = ='undefined' && self.window) {
// broadcast update to window
self.postMessage("webpackHotUpdate".concat(currentHash), The '*'); }}// allow refreshing the page only if liveReload isn't disabled
else if (liveReload) {
Copy the code
The webpackHotUpdate event is an event defined in webpack, code in /hot/dev-server.js. Its function is to get the code compiled this time.
if (module.hot) {
var lastHash;
var upToDate = function upToDate() {
return lastHash.indexOf(__webpack_hash__) >= 0;
};
var log = require("./log");
var check = function check() {
module.hot
.check(true)
.then(function (updatedModules) {
// The HMR check is successful
if(! updatedModules) {// If there is no updated modules, refresh the page,
window.location.reload();
return;
}
if(! upToDate()) { check(); }// Call the HMR handler to load only the updated code
require("./log-apply-result")(updatedModules, updatedModules);
if (upToDate()) {
log("info"."[HMR] App is up to date.");
}
})
.catch(function (err) {
// The HMR check fails and the page is refreshed
var status = module.hot.status();
if (["abort"."fail"].indexOf(status) >= 0) {
window.location.reload();
} else {
log("warning"."[HMR] Update failed: "+ log.formatError(err)); }}); };var hotEmitter = require("./emitter");
// Event definition
hotEmitter.on("webpackHotUpdate".function (currentHash) {
lastHash = currentHash;
if(! upToDate() &&module.hot.status() === "idle") {
log("info"."[HMR] Checking for updates on the server..."); check(); }}); log("info"."[HMR] Waiting for update signal from WDS...");
} else {
throw new Error("[HMR] Hot Module Replacement is disabled.");
}
Copy the code
conclusion
:whale2::whale2::whale2:
- There are two ways to debug webpack-dev-server code: the browser and the IDE
- Webpack-dev-server uses the Express framework internally as the server
- Webpack-dev-server uses memFS to store packaged files by default
- Webpack-dev-server uses the hook function provided by Webpack to listen for packaging compilation
- The Wating state is a feature provided by WebPack and has the property: Watch
- Hot update (HMR) is the use of the webpack built-in HotModuleReplacementPlugin, webpack dev – server will each time after completion of the code to compilehashValue is pushed to the browser
If this article is helpful to you, ask for a star here. Project address: OrcasTeam/my-cli
In this paper, the reference
-
webpack-dev-server
-
Webpack-dev-server, HMR resolution
-
Webpack HMR principle analysis
package.json
{
"name": "my-cli"."version": "1.0.0"."main": "index.js"."author": "mowenjinzhao<[email protected]>"."license": "MIT"."devDependencies": {
"@babel/core": "7.13.1"."@babel/plugin-transform-runtime": "7.13.7"."@babel/preset-env": "7.13.5"."@babel/preset-react": "7.12.13"."@babel/runtime-corejs3": "7.13.7"."babel-loader": "8.2.2"."clean-webpack-plugin": "3.0.0"."css-loader": "5.0.2"."file-loader": "6.2.0"."html-webpack-plugin": "5.2.0"."mini-css-extract-plugin": "1.3.8"."style-loader": "2.0.0"."webpack": "5.24.0"."webpack-cli": "4.5.0"."webpack-dev-server": "4.0.0 - beta. 0"."webpack-merge": "5.7.3"
},
"dependencies": {
"react": "17.0.1"."react-dom": "17.0.1"
},
"scripts": {
"start:dev": "webpack-dev-server --config build/webpack.dev.js"."start": "webpack serve --config build/webpack.dev.js"."build": "webpack --config build/webpack.pro.js"."debug": "node --inspect-brk=5858 ./node_modules/webpack-dev-server/bin/webpack-dev-server.js"."watch": "webpack --config build/webpack.watch.js"
},
"browserslist": {
"development": [
"chrome > 75"]."production": [
"ie 9"]}}Copy the code
webpack.watch.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common');
// Use node. Export the js configuration to export
module.exports = merge([
common(true),
{
mode:'development'.// Listen for file changes
watch: true.// Listen on options
watchOptions: {
// Ignore directories that can be regular or array
ignored: /node_modules/.// Start polling, default false Set true or milliseconds to start polling
poll: false}}])Copy the code