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