One, foreword
Hot Module Replacement (HMR for short) updates the Module without completely refreshing the entire page. The benefit is to optimize the development experience.
If you have configured Webpack presumably you also not strange, HotModuleReplacementPlugin and devServer hot: open HMR function come true.
Learn about the daily build of WebPack
When we turn onHMR
throughdevServer
When building our project, we can open the consolenetwork
Observe that it generates a hash value41e99be0fd82c43bc67d
And introducedmain.41e99be0fd82c43bc67d.hot-update.js
This file.
At the same time the 41 e99be0fd82c43bc67d hot update logo files. Update. Json
Then we modify our code to recompile, which we can observe in the console:
It will generate a new hash value (the hash value webPack automatically generated for us), and it will generate a new JS file and a hot-update.json hotupdate identifier file.
We can see that the hash value printed last time will write the prefix of the newly generated file name. Similarly, if we modify the file again to trigger the update, a new file will be generated, with the file name prefixed with the generated hash.
Look at the contents of the document.
h
: Generates a new hash valuec
The current hot update is the module (here is the entry file)main
)
So finally, how does the browser know that our native code has changed and introduced new files? Let us understand it in detail!
Hot update implementation
Hot update is implemented in conjunction with dev-server, so we will implement it step by step from webpack-dev-server. (^▽^)
1. Webpack-dev-server starts the local service
const webpack = require('webpack'); // webpack const config = require('.. /webpack.config'); // webpack.config.js const Server = require('./ Server /Server'); const compiler = webpack(config); // Compiler object const server = new server (compiler); Server. listen(9090,'localhost',()=>{console.log(" server on port 9090 "); })Copy the code
Server.js service code
class Server { constructor(compiler) { this.compiler = compiler; this.setupApp(); this.createServer(); } setupApp() {this.app = express(); setupApp() {this.app = express(); CreateServer () {this.server = http.createserver (this.app); } listen(port, host, callback) {this.server.listen(port, host, callback); }}Copy the code
Here’s what the code does:
- The introduction of
webpack
And throughwebpack.config.js
generatecompiler
Example (here do not understand, we can go to understand firstwebpack
A rough build process). - Rely on
express
Framework to start local services.
2.websocket
How do we let the browser know that our native code has changed? Source code is based on websocket to achieve.
const socketIO = require('socket.io'); constructor(compiler) { this.currentHash; // Current hash this.clientSocketList = []; This.createsocketserver (); // WebSocket connection list this.createsocketServer (); } createSocketServer() {const IO = socketIO(this.server); On ("connection", (socket) => {// Console. log(" new client socket"); this.clientSocketList.push(socket); socket.emit("hash", this.currentHash); // The server actively sends hash socket.emit(" OK "); / / send ok field socket. On (" disconnect ", () = > {/ / listen to disconnect the let index = this. ClientSocketList. IndexOf (socket); this.clientSocketList.splice(index, 1); // Disconnect needs to be removed from the list})})}Copy the code
Here’s what the code does:
- Start the
websocket
Service that establishes two-way communication between the server and the browser. Actively send new to the browser when listening for local code changeshash
As well asok
Field. And to theclientSocketList
Add current to link collectionsocket
While listening for a link break event.
3. Listen for webpack compilation to complete
Listening for the completion of webpack compilation, mainly by mounting the webPack lifecycle hook function done, sends hash and OK for each socket.
constructor(compiler) { this.setupHooks(); SetupHooks () {let {compiler} = this; Tap ('webpack-dev-server', (state) => {//state is a description object, Console. log('hash', state.hash); this.currentHash = state.hash; this.clientSocketList.forEach(socket => { socket.emit("hash", this.currentHash); socket.emit("ok"); // Send an OK to the client})})}Copy the code
We can look at the state returned by the done callback:
Static resource access middleware
When we run our project through Webpack-dev-server, WebPack starts a service locally and we have access to the HTML pages we packaged for output, mostly through a middleware (middleware is a concept in Express, not explained here).
const MemoryFs = require("memory-fs"); const mime = require('mime'); constructor(compiler) { this.setupDevMiddleware(); / / create a middleware} setupDevMiddleware () {this. Middleware = this. WebpackDevMiddleware (); } webpackDevMiddleware() {// Returns an Express middleware let {compiler} = this; // let fs = new MemoryFs(); / / memory file system instance enclosing the fs = compiler. OutputFileSystem = fs; Return (staticDir) => {req, res, next) => {let {url} = req; if (url === '/favicon.ico') { return res.sendStatus(404); } url === '/' ? url = '/index.html' : null; let fs = new MemoryFs(); // Let filepath = path.join(staticDir, url); // Get access to static path try {let stateObj = this.fs.statsync (filepath); If (stateobj.isfile ()) {let content = this.fs.readfilesync (filepath); res.setHeader('Content-Type', mime.getType(filepath)); // res.send(content); } else { return res.sendStatus(404); } } catch (err) { return res.sendStatus(404); }}}}Copy the code
The main thing to do is to access static files from our local service, in order to have local access to the HTML files in our packaged dist directory.
5. Configure routes
constructor(compiler) { this.routes(); } routes() {let {compiler} = this; let config = compiler.options; this.app.use(this.middleware(config.output.path)); // Use the static file middleware we generated in the previous step}Copy the code
Here we mainly use the static file middleware we generated in the previous step, and we can see that its directory is actually the output directory configured in our webpack.config.js file, namely dist.
Now that we’re done with webpack-dev-server, we can enable Node to run and see if we can access the page.
Four, packaged file browser end
1. Websocket on the browser
When listening for webPack recompilation, the server sends the latest HAHS value, and the client should also have the corresponding WebSocket in response. What does the client code do during the response?
The following code is packaged by WebPack, so it appears in the import file of the HTML file.
var currentHash; // Current hash var lastHash; const socket = window.io('/'); class EventEmitter { constructor() { this.events = {}; } on(eventName, fn) { this.events[eventName] = fn; } emit(eventName, ... args) { this.events[eventName](... args); } } var hotEmitter = new EventEmitter(); Socket. on('hash', (hash) => {// Listen to server recompile send hash console.log(hash) currentHash = hash; }) socket. On (" ok ", () = > {/ / listen to the server to send the ok to the console. The log (" ok "); reloadApp(); }) function reloadApp() { hotEmitter.emit('webpackHotUpdate'); // Send the webpackHotUpdate message}Copy the code
The client websocket registry registers two events as the server does:
hash
:webpack
Re-edit the new packagehash
Value.ok
:reloadApp
Hot update check.
Hot update check is to call reloadApp method, during which a layer of publishing-subscription mode EventEmitter is also called, and EventEmitter sends a webpackHotUpdate event to check.
2.webpackHotUpdate
First of all, you can compare bundle.js with hot updates configured and bundle.js not configured. Can’t see it in memory? Since webpack-dev-server uses the memory file system memory-fs, we can’t see our packed files in the dist directory, so I’ve changed it to fs-extra so that we can see them.
- Configure hot update
- Hot update not configured
The hot update module has a lot of hot updates in it. I also wrote a hotCreateModule here
Function hotCreateModule() {// let hot = {_acceptDependencies: {}, accept(deps, callBack) { deps.forEach(dep => { hot._acceptDependencies[dep] = callBack; }) }, check: hotCheck } return hot; }Copy the code
Now we know where the module.hot.check method comes from. These are actually HotModuleReplacementPlugin plug-in functionality, it gives us the opportunity to pack out a lot of code. You can also read the code directly in browser Sources for easy debugging.
hotEmitter.on("webpackHotUpdate", () => { if (! LastHash) {// If not, the first render lastHash = currentHash; return; } console.log(lastHash, currentHash); module.hot.check(); //module.hot.check})Copy the code
3.hotCheck
Function hotCheck() {hotDownloadManifest(). Then (update => {let chunkIds = object.keys (update.c); ForEach (chunkId => {// iterate over the module hotDownloadUpdateChunk(chunkId) to send changes; }) lastHash = currentHash; }).catch(() => { window.location.reload(); } function hotDownloadManifest() {return new Promise((resolve, reject) => {let XHR = new XMLHttpRequest(); let url = `${lastHash}.hot-update.json`; Json xhr.open('get', url); xhr.responseType = "json"; xhr.onload = function () { resolve(xhr.response); } xhr.send(); })}Copy the code
- I’m going to use what I saved last time
hash
Value, callhotDownloadManifest
sendxxx/hash.hot-update.json
theajax
The request. - Save the next hot update
hash
Logo.
Function hotDownloadUpdateChunk(chunkId) {let script = document.createElement('script'); script.src = `${chunkId}.${lastHash}.hot-update.js`; document.head.appendChild(script); }Copy the code
- Here by
jsonp
Is sent by way ofxxx/hash.hot-update.js
Request to get the latest code
Here’s what the code looks like:
WebpackHotUpdate (); webpackHotUpdate (); webpackHotUpdate ();
WebpackHotUpdate = function (chunkId, moreModules) {// Chunk name, Update code for module hotAddUpdateChunk(chunkId, moreModules)} let hotUpdate = {}; function hotAddUpdateChunk(chunkId, moreModules) { for (let moduleId in moreModules) { modules[moduleId] = hotUpdate[moduleId] = moreModules[moduleId]; } hotApply(); }Copy the code
Webpack packs the code into modules when packaged
The above code simply replaces the EVAL (line 6) code in the diagram with our latest executable code.
4.hotApply
The core logic of hot updates is in the hotApply method.
function hotApply() { for (let moduleId in hotUpdate) { let oldModule = installedModules[moduleId]; Let {parents, children} = oldModule; Let Module = installedModules[moduleId] = {// Module cache replace I: moduleId, l: false, exports: {}, parents: parents, children: children, hot: hotCreateModule() } modules[moduleId].call(module.exports, module, module.exports, hostCreateRequire(moduleId)); // Execute module module.l = true; / / this module have been executed oldModule. Parents. ForEach (parentModule = > {/ / to iterate through all the father module performs the callback let cb = parentModule.hot._acceptDependencies[moduleId]; cb && cb(); }}})Copy the code
Refer to the link
- Easy to understand the webPack hot update principle
The code in this article is written manually, compared with the source code to simplify a lot, but the concentration is the essence, can also help you better read the source 😊😊😊😊 port.