What is a hot update
Hot update generally refers to the app has a small update, in the form of direct patch update app, in contrast, another is to download the entire app update package, hot update way found in mobile game, because the mobile game is bigger, now generally are hundreds of megabytes to several G, if there is a small update let users to download the complete package, I don’t think anyone will be using this app in a few days
Web hot update & hot load implementation premise
If you follow the above statement, there may be no hot update on the Web, mainly because the Web is B/S (browser + server), the browser mainly communicates with the server through THE HTTP protocol, the server responds to the HTTP request, the HTTP connection is finished, unlike the APP is C /S (client + server). The server cannot push patches to the browser, but why is it that the Web can now implement hot-loader or module replacement HMR mainly due to the two new communication methods proposed in HTML5
-
WebSocket: a communication mechanism that allows a long TCP link between a browser and a server
-
EventSource (server-sent-Events) : indicates a network event interface pushed by the Server. An EventSource instance opens a persistent connection to the HTTP service, sending events in text/event-stream format, and remains open until it is asked to close. (specific can look at this article www.ruanyifeng.com/blog/2017/0…).
The difference between:
-
Unlike WebSocket, the EventSource server push is unidirectional. Data information is distributed one-way from server to client. This makes EventSources a great choice when there is no need to send data from the client to the server as a message.
-
SSE supports disconnected reconnection, WebSocket needs to implement itself
-
SSE uses HTTP protocol, which is supported by existing server software. WebSocket is a stand-alone protocol.
-
SSE is generally only used to transmit text, binary data needs to be encoded after transmission, WebSocket default support to transmit binary data
-
SSE supports user-defined message types to be sent.
How WebPack hot updates work
Hot load: Simply refresh the browser without retaining the page state, like window.location.reload() hot update: The other is module hot replacement based on WDS (Webpack-dev-server), which only needs to partially refresh the changed modules on the page, while retaining the current page state (Webpack-dev-server cannot do this by itself, it needs to be used with React-hot-Loader). For example, the status of check boxes, input boxes, and so on
Webpack build
When you run NPM run start, the following information is displayed on the console
The hash: 8420886c15F084075F1D field represents the identifier of a compilation. When the changes are saved, WebPack recompiles the code and the console displays the following
Find hash has become 94992 a48d7e645922c7b because it represents a new compilation contrast, found that there will be a new file js: main. 8420886 c15f084075f1d. Hot – update. Js new json: 8420886c15f084075f1d.hot-update.json: 8420886c15f084075F1d is the prefix for this generation of JS and JSON. Let’s take a look at the browser side. After the code is modified and saved, the browser will issue two requests after the compilation of Webpack
You can see that the requested files are the JS and JSON files generated for this compilation, and then take a look at the contents of each file. Let’s first take a look at the json file
The c field represents the currently changed Module, and the H field represents the current compiled hash, which is used as the prefix for the next request file. Take a look at the contents of the JS file, XXX is the moduleId, and we will discuss webpackHotUpdate later
webpackHotUpdate("main", {'xxx': (function(module, __webpack_exports__, __webpack_require__) {}})))Copy the code
What does the server do during the hot update
What does the server do
Before we start, run the following steps to debug the React project you created with the creation-react-app
-
NPM run eject to generate the configuration
-
Modify webpack.config.js in the config directory
const shouldUseReactRefresh = env.raw.FAST_REFRESH || false;
/ / to
const shouldUseReactRefresh = false;
Copy the code
This is the official implementation of the Fast Refresh function, which is faster and more stable, similar to the React-hot-loader. For details, see blog.logrocket.com/whats-new-i…
- Modify the
config
In the directorywebpackDevServer.config.js
injectClient: false
/ / to
injectClient: true
Copy the code
The configuration for this is described below
-
Remove webpackDevClientEntry from entry in webpack.config.js
-
Add the following code to index.js in the SRC directory
if (module.hot) {
module.hot.accept(() = > {
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>.document.getElementById('root')); })}Copy the code
Running NPM run start calls start.js start.js in the scripts
const devServer = new WebpackDevServer(compiler, serverConfig);
Copy the code
Webpack-dev-server package server.js, server.js constructor
class Server {
constructor(compiler, options = {}, _log) {
/ / webpack compiler
this.compiler = compiler;
this.options = options;
updateCompiler(this.compiler, this.options);
this.setupHooks();
// Initialize the app
this.setupApp();
this.setupDevMiddleware();
this.setupFeatures();
/ / create a server
this.createServer(); }}Copy the code
First look at the updateCompiler () function that captures the code of the main webpack – dev – server/lib/utils/updateCompiler js
'use strict';
/* eslint-disable no-shadow, no-undefined */
const webpack = require('webpack');
const addEntries = require('./addEntries');
const getSocketClientPath = require('./getSocketClientPath');
function updateCompiler(compiler, options) {
if(options.inline ! = =false) {
// Find the HMR plugin
const findHMRPlugin = (config) = > {
if(! config.plugins) {return undefined;
}
return config.plugins.find(
(plugin) = > plugin.constructor === webpack.HotModuleReplacementPlugin
);
};
const compilers = [];
const compilersWithoutHMR = [];
let webpackConfig;
webpackConfig = compiler.options;
compilers.push(compiler);
// Not found
if(! findHMRPlugin(compiler.options)) { compilersWithoutHMR.push(compiler); }// Add something to the entry file
addEntries(webpackConfig, options);
// If the plugin is found, run the plugin (that is, call the apply method of the plugin)
if (options.hot || options.hotOnly) {
compilersWithoutHMR.forEach((compiler) = > {
const plugin = findHMRPlugin(compiler.options);
if(plugin) { plugin.apply(compiler); }}); }}}Copy the code
See addEntries () method webpack – dev – server/lib/utils/addEntries js
'use strict';
const webpack = require('webpack');
const createDomain = require('./createDomain');
/**
* A Entry, it can be of type string or string[] or Object<string | string[],string>
* @typedef {(string[] | string | Object<string | string[],string>)} Entry* /
/**
* Add entries Method
* @param {? Object} config - Webpack config
* @param {? Object} options - Dev-Server options
* @param {? Object} server
* @returns {void}* /
function addEntries(config, options, server) {
if(options.inline ! = =false) {
const app = server || {
address() {
return { port: options.port };
},
// clientEntry
const domain = createDomain(options, app);
const sockHost = options.sockHost ? `&sockHost=${options.sockHost}` : ' ';
const sockPath = options.sockPath ? `&sockPath=${options.sockPath}` : ' ';
const sockPort = options.sockPort ? `&sockPort=${options.sockPort}` : ' ';
const clientEntry = `The ${require.resolve(
'.. /.. /client/'
)}?${domain}${sockHost}${sockPath}${sockPort}`;
// hotEntry
if (options.hotOnly) {
hotEntry = require.resolve('webpack/hot/only-dev-server');
} else if (options.hot) {
hotEntry = require.resolve('webpack/hot/dev-server');
}
const checkInject = (option, _config, defaultValue) = > {
if (typeof option === 'boolean') return option;
if (typeof option === 'function') return option(_config);
return defaultValue;
};
const additionalEntries = checkInject(
options.injectClient,
config,
webTarget
)
? [clientEntry]
: [];
if (hotEntry && checkInject(options.injectHot, config, true)) {
additionalEntries.push(hotEntry);
}
//
config.entry.unshift(additionalEntries)
// Add the HMR plug-in if it is not available
config.plugins.push(newwebpack.HotModuleReplacementPlugin()); }); }}module.exports = addEntries;
Copy the code
The whole updateCompiler() function basically does
- Add the following two files to the entry
xxx/node_modules/webpack-dev-server/client/index.js
The file is mainly responsible for communicating with the serverxxx/node_modules/webpack/hot/dev-server.js
Mainly responsible for the logic of hot update
[
'xxx/node_modules/webpack-dev-server/client/index.js? [http://localhost:3000](http://localhost:3000/)'
'xxx/node_modules/webpack/hot/dev-server.js'
'.. /src/index.js'
]
Copy the code
- join
HotModuleReplacementPlugin
Plug-in, and call the execution plug-in (call the apply method)
Let’s take a look at what setupHooks does, and capture the main code, server.js
setupHooks () {
const addHooks = (compiler) = > {
const { done } = compiler.hooks;
done.tap('webpack-dev-server'.(stats) = > {
this._sendStats(this.sockets, this.getStats(stats));
this._stats = stats;
});
};
addHooks(this.compiler);
}
// send stats to a socket or multiple sockets
_sendStats(sockets, stats, force) {
this.sockWrite(sockets, 'hash', stats.hash);
this.sockWrite(sockets, 'ok');
}
Copy the code
SetupHooks mainly did
- Listen for WebPack compilation to complete and pass
websocket
Send a message to the front-end
Next look down at setupApp server.js
setupApp() {
// Init express server
// eslint-disable-next-line new-cap
this.app = new express();
}
Copy the code
Nothing to talk about, setupDevMiddleware
setupDevMiddleware() {
// middleware for serving webpack bundle
this.middleware = webpackDevMiddleware(
this.compiler,
Object.assign({}, this.options, { logLevel: this.log.options.level })
);
}
Copy the code
Look at webpackDevMiddleware, the file is in the index.js index.js file under the Webpack-dev-Middleware package
module.exports = function wdm(compiler, opts) {
const context = createContext(compiler, options);
// Listen for code changes and recompile
context.watching = compiler.watch(options.watchOptions, (err) = > {
if (err) {
context.log.error(err.stack || err);
if(err.details) { context.log.error(err.details); }}}); setFs(context, compiler);// For the moment, ignore the return
};
// Omit some useless code
setFs(context, compiler) {
let fileSystem;
// store our files in memory
const isConfiguredFs = context.options.fs;
constisMemoryFs = ! isConfiguredFs && ! compiler.compilers && compiler.outputFileSysteminstanceof MemoryFileSystem;
if (isConfiguredFs) {
/ / to omit
} else if (isMemoryFs) {
fileSystem = compiler.outputFileSystem;
} else {
fileSystem = new MemoryFileSystem();
compiler.outputFileSystem = fileSystem;
}
context.fs = fileSystem;
},
Copy the code
From the above code you can see that so far, mainly done
-
Webpack is started in Watch mode and will be recompiled and repackaged as soon as the monitored files change.
-
The storage mode of Webpack files is changed to memory, which improves the storage and reading efficiency of files.
Go ahead and look at the webpack-dev-middleware/index.js that comes back from webpack-dev-middleware
function wdm (compiler, opts) {
return Object.assign(middleware(context), {
/ / to omit
});
}
// middleware
module.exports = function wrapper(context) {
return function middleware(req, res, next) {
let filename = getFilenameFromUrl(
context.options.publicPath,
context.compiler,
req.url
);
return new Promise((resolve) = > {
// If the file name in memory satisfies the hash and is a file rather than a directory, processRequest is executed
handleRequest(context, filename, processRequest, req);
// eslint-disable-next-line consistent-return
function processRequest() {
try {
// Omit the code
// Read the contents of the file
let content = context.fs.readFileSync(filename);
// Omit the code
if (res.send) {
res.send(content);
} else{ res.end(content); } resolve(); }}); }; };Copy the code
The Middleware method is a middleware wrapper function that returns the middleware. The middleware is meant to intercept the update file requested by the browser, read the content from memory and return it. Go back to the setupDevMiddleware method 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
SetupDevMiddleware is the middleware that comes back
-
Webpack is started in Watch mode and will be recompiled and repackaged as soon as the monitored files change.
-
The storage mode of Webpack files is changed to memory, which improves the storage and reading efficiency of files.
-
Initialize this.middleware. The middleware is designed to intercept a browser request to update a file, read the contents of the file from memory and return it
Next look down at the setupFeature() method webpack-dev-server /lib/server.js
setupFeatures() {
const features = {
middleware: () = > {
// include our middleware to ensure
// it is able to handle '/index.html' request after redirect
this.setupMiddleware(); }}; features['middleware']()
}
// this.middleware is for the middleware above. Load the middleware
setupMiddleware() {
this.app.use(this.middleware);
}
Copy the code
The setUpFeature() mainly does
- Loading middleware
Moving on to the createServer() function
this.listeningApp = http.createServer(this.app);
Copy the code
Mainly for the
- Creating a server
Go back to scripts/start.js
devServer.listen(port, HOST, err= > {
// Omit the code
openBrowser(urls.localUrlForBrowser);
});
Copy the code
Call the listen() method webpack-dev-server /lib/server.js in server.js
listen(port, hostname, fn) {
this.hostname = hostname;
return this.listeningApp.listen(port, hostname, (err) = > {
//
this.createSocketServer();
});
}
Copy the code
The listen() method mainly does
- Create a websocket
Use a flow chart to represent the entire process
The complete process for hot updates is as follows
Images from (segmentfault.com/a/119000002…).
Reference documentation
Easily understand webpack hot update principle [www.ruanyifeng.com/blog/2017/0…