HMR hot update

First, hot update function

Real-time hot update can be implemented for specific modules during development

1.1 Basic Configuration

  • Webpack. Config. Js configuration
const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const { HotModuleReplacementPlugin  } = require('webpack') module.exports = { devtool: false, mode: 'development', entry: './src/index.js', output: { filename: '[name].js', path: path.resolve('dist') }, devServer: { port: 3000, hot: true, writeToDisk: true, contentBase: path.resolve(__dirname, 'static') }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html' }), new HotModuleReplacementPlugin() ] }Copy the code
  • HTML Interface content
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta  name="viewport" content="width=device-width, </title> </head> <body> <input type="text" value=" Headache ">< div id="root"></div> </body> </ HTML >Copy the code

1.2 Nature of hot update

  • Hot updates are similar to partial refreshes, so they are essentially a matter of finding a way to take the code that has changed and repackage it into the interface
  • The HotModuleReplacement plug-in is implemented within WebPack, which generates two files, one JSON and one JS
  • For the first time, there is no hot update, and the packaging logic is normal, and the subsequent file changes in the file system are monitored in watch mode
  • Once a file has changed, it repackages to produce new content and notifies the browser of the change
  • After the browser receives the change information, it will try to retrieve the actual changed file content and perform a callback to display it on the interface

Conclusion:

  1. How to notify browser of new file output (based on WebSocket communication)
  2. Communication occurs at least at two ends: the server (write yourself) and the client (browser).
  3. The server sends a message to the client: ok hash
  4. The client listens for the trigger of the corresponding event, requests the JSON file, and identifies the changed file
  5. The client requests the JS file, gets the changed content, and then performs a callback to update it (HTTP request)

1.3 Import Debugging

  1. Perform debugging as NPM runwebpack serve
  2. When the package is installed, the value of the bin key in the package.json file in the current package is automatically added to the.bin directory under node_modules
  3. When you run NPM run XXX, node_modules/. Bin is automatically added to system environment variables
  4. So you find webpack.cmd, and you end up executing the webpack/bin/webpack.js file
  5. When executing webpack.js, we finally found the webpack-cli/bin/cli.js file
  6. The cli.js file is executed with the new webpackCli instance, and then the run method is executed to complete the parse and Compiler operations internally
  7. A breakpoint is forced to be added in the getCompiler process. The call stack can be found at @webpack-cli/serve/index.js
  8. Startdevserver.js is imported into the above index.js module, whose core function is to return a server for processing messages

Two, start the service

2.1 startDevServer. Js

  1. StartDevServer method, after the call can open HTTP and socket server
  2. The current file is only responsible for implementation of the call, the specific implementation of the webpack-dev-server lib/ server.js implementation
const webpack = require('webpack') const config = require('./webpack.config') const Server = require('./webpack-dev-server/lib/Server') //! 01 Create compiler const compiler = webpack(config) //! 02 restart the server function startDevServer (compiler, the config) {const devServerArgs = config. DevServer | | {} const {port = 3000, host = 'localhost' } = devServerArgs const server = new Server(compiler, devServerArgs) server.listen(port, host, (err) => { console.log(`project is running at http://${host}:${port}`) }) } startDevServer(compiler, config) module.exports = startDevServerCopy the code

2.2 Starting the Http Service

const express = require('express') const http = require('http') class Server { constructor(compiler, DevServerArgs) {this.devServerargs = devServerArgs this.setuphooks () This.setupapp () // Initialize the express app to execute middleware this.routes() // handle the route this.createserver () // create HTTP service} setupHooks() { / / listen to compile successfully, complete a compiled to trigger the done hook callback this.com piler. Hooks. Done. Tap (' webpack - dev - server '(stats) = > {the console. The log (' compilation is complete, The hash value of ', This._stats = stats})} setupApp() {this.app = express()} routes() {this.app = express()} routes() { / / if set contentBase is classified as static directory if (this. DevServerArgs. DevServer. ContentBase) { this.app.use(express.static(this.devServerArgs.devServer.contentBase)) } } createServer() { this.server = http.createServer(this.app) } listen(port, host, callback) { this.server.listen(port, host, callback) } } module.exports = Server //! Use Express to start the HTTP server and create a Websocket service for future communicationCopy the code

2.3 the websocket service

HTTP services are used to provide static resource access, allowing users to browse packaged resources

The Websoket service is used for communication, allowing users to know about and obtain new packaged resources

const express = require('express') const http = require('http') const WebSocketIo = require('socket.io') class Server { constructor(compiler, DevServerArgs) {this.sockets = [] //* + save this.piler = devServerArgs = devServerArgs This.setuphooks () // Listen to package, This.setupapp () // Initializes the Express app for executing middleware this.routes() // handles the route this.createserver () // creates the HTTP service This.createsocketserver () //* + start socket service} setupHooks() { Complete a compiled to trigger the done hook callback this.com piler. Hooks. Done. Tap (' webpack -- dev server, (stats) = > {the console. The log (' compilation is complete, Hash = ', stats.hash ') //? // this.sockets. ForEach (socket => {// socket.emit('hash', Stats. Hash) // socket.emit(' OK ') //}) // Save a list of compiled output this._stats = stats})} setupApp() {// Here app is just a routing middleware This app = express ()} routes () {/ / if set contentBase is classified as static directory if (this. DevServerArgs. DevServer. ContentBase) { this.app.use(express.static(this.devServerArgs.devServer.contentBase)) } } createServer() { this.server = http.createServer(this.app) } listen(port, host, callback) { this.server.listen(port, host, Callback)} createSocketServer() {// A handshake is required before socket communication, Const websocketServer = WebSocketIo(this.server) // Listen for a client connection. (socket) => {console.log(' new websocket client connected ') // Store the newly connected client this.sockets. Push (socket) // Monitor client disconnection events socket.on('disconnect', () => { let index = this.sockets.indexOf(socket) this.sockets.splice(index, 1)}) // If there is already a compilation result, If (this._stats) {socket.emit('hash', this._stats.hash) socket.emit('ok') } }) } } module.exports = Server //! Use Express to start the HTTP server and create a Websocket service for future communicationCopy the code

Modify the configuration before compiling

The entry file will be modified after hot update is enabled

Need to load webpack dev – server \ client \ index js | webpack/hot/dev – server. Js | HotModuleReplacement. The runtime

This action is defined separately as a functional function implementation and placed in the updateCompiler module

Function updateCompiler(compiler) {const options = compiler.options // Add the socket client in the browser, Receiving server message options. Entry. The main. Import. Unshift (require) resolve ('.. /.. / client/index. Js')) / / add files for handling the browser client receives the message options. The entry. The main, the import, unshift (require) resolve ('.. /.. /.. / webpack/hot/dev - server. Js')) / / * test the console. The log (options. Entry, '~ ~ ~ ~') / / * 3 trigger hooks, Inform webpack compiler is compiled according to the new entrance. The hooks. EntryOption. Call (options. The context, options.entry) } module.exports = updateCompiler //! Modify the entry to package the files needed for hot updatesCopy the code

4. Middleware Watch file

Process:

  • Start the service (HTTP service + WebSocket service) and create the Compiler to perform the packaged build
  • When creating the Server service, you need to change the Entry property of config to inject two new entry files into it
  • Compiler performs the build, at which point the server needs to participate in the build, adding a middleware

4.1 setupDevMiddleware

const webpackDevMiddleware = require('.. /.. /webpack-dev-middleware') this.setupDevmiddleware () //* +3.1 Init middleware () {this.middleware = webpackDevMiddleware(this.compiler) this.app.use(this.middleware) }Copy the code

4.2 webpack – dev – middle

/ /! Start webpack compilation in listening mode //! Return express middleware, Const MemoryFileSystem = require('memory-fs') const MemoryFileSystem = new MemoryFileSystem() const Middleware = require('./middleware') const fs = require('fs') function webpackDevMiddleware(compiler) {// Let webpack Watch ({}, () => {console.log(' listen for file changes, webpack starts compiling again ')}) In the hard disk / / let the fs = compiler. OutputFileSystem = memoryFileSystem return middleware ({fs, outputPath: compiler.options.output.path }) } module.exports = webpackDevMiddlewareCopy the code

4.3 implementation middleware

const path = require('path') const mime = require('mime') function wrapper({ fs, outputPath }) { return (req, res, next) => { let url = req.url if (url == '/') url = './index.html' let filename = path.join(outputPath, url) try { let stat = fs.statSync(filename) if (stat.isFile()) { let content = fs.readFileSync(filename) res.setHeader('Content-Type', mime.getType(filename)) } else { res.sendStatus(404) } } catch (err) { res.sendStatus(404) } } } module.exports = wrapper //! Previewing output files with Express middleware //! Pull the HTTP request to see if the requested file is a webpack file //! If yes, read it from the hard disk and return it to the clientCopy the code

5. Connect the server

After the preceding operations are complete, the server is ready

Then the browser as the client needs to connect with the server and process the subsequent logic based on the information changes

The client file is placed in webpack-dev-server\client\index.js

The real business processing file is in webpack\hot\dev-server.js

5.1 Environment Modification

  1. Enable the static resource service directory to copy the HTML and packaged main.js to the directory
  2. Comments in the configuration file about the use of the HotModuleReplacement plug-in to simplify main.js output
  3. Modify HTML file, add socket.io. Js file, convenient browser client can directly use IO operations
<script src="/socket.io/socket.io.js"></script>
<script defer src="hmr.js"></script>
Copy the code
"./src/index.js":
((module, exports, __webpack_require__) => {
    let render = () => {
        let title = __webpack_require__("./src/title.js")
        root.innerText = title
    }
    render()
    if (false) { }
})
​
__webpack_require__('./src/index.js')
Copy the code

5.2 Connecting the Server

The webpack-dev-server\client\index.js file acts as the client in the browser

After repackaging, messages are sent to the client through the Socket server

In this case, the client needs to connect to the server to receive specific messages to implement the corresponding service logic

Const socket = IO () const socket = IO () const socket = IO () const socket = IO () const socket = IO () let currentHash (hash) => {console.log(' client received hash message ') currentHash = hash}) socket.on('ok', // reloadApp()}) function reloadApp() {// reloadApp}Copy the code

6. Initial hot update

6.1 the trigger webpackHotUpdate

Hot updates run packaging in Watch mode, monitoring changes to the file system

After the change, the package build operation is re-executed, at which point the WebSocket server sends a message to notify the client

The browser client completes the resource reload after receiving the server message (triggering the webpackHotUpdate)

The trigger operation mentioned above is implemented by the publish-subscribe mode, and the specific logical code is placed in webpack\hot\dev-server.js

socket.on('ok', () => {console.log(' client received OK ') // reloadApp()}) function reloadApp() {reloadApp() { Emit hotemitter. emit('webpackHotUpdate', currentHash)}Copy the code

6.2 Simple edition publishing subscription

class EventEmitter { constructor() { this.events = {} } on(eventName, fn) { this.events[eventName] = fn } emit(eventName, ... args) { this.events[eventName](... args) } } module.exports = new EventEmitter()Copy the code

6.3 Initialization of heavy loads

var hotEmitter = require('./emitter') hotEmitter.on('webpackHotUpdate', (currentHash) => {console.log(' client received the latest hash value ', currentHash) // Hot update})Copy the code

7. Hot update

7.1 modify the module

Hot updates add additional attributes to the Module

function __webpack_require__(moduleId) { var cachedModule = __webpack_module_cache__[moduleId]; if (cachedModule ! == undefined) { return cachedModule.exports; } var module = __webpack_module_cache__[moduleId] = { exports: {}, hot: hotCreateModule(), parents: new Set(), children: new Set() }; __webpack_modules__[moduleId](module, module.exports, hotCreateRequire(moduleId)); return module.exports; } function hotCreateModule() {let hot = { Accept (deps, callback) {for (let I = 0; i < deps.length; i++) { hot._acceptedDependencies[deps[i]] = callback } }, check(moduleId) { let callback = hot._acceptedDependencies[moduleId] callback && callback() } } return hot }Copy the code