Implementation of Hot Update Source Code for webpack

Keywords: socket Webpack hot update JSON

I. Thermal Renewal Principle
Server side:
1. Start the webpack-dev-server server server
2. Create webpack instances
3. Create Server Server Server
4. The compilation of the done event callback adding webpack completes sending messages to the client (hash and description files oldhash.js and oldhash.json)
5. Create express application app
6. Setting File System to Memory File System
7. Adding webpack-dev-middleware is responsible for returning the generated files
8. Create http service startup
9. Use socket to communicate between browser and server (send a hash first, save the socket in step 4, the socket in step 4 is empty after compiling for the first time, and will not trigger hash download)

Client:
1. File listen for hash under webpack-dev-server/client-src and save the hash value
2. Client receives ok message to perform reload update
3. Make a judgment in reload. If hot update is supported to execute webpackHotUpdate, the page is refreshed directly if not supported.
4. Monitor webpackHotUpdate in webpack/hot/dev-server.js and then perform check() method for detection
5. Call module.hot.check in the check method
6. By calling the hotDownload mainfest method of JsonpMainTemplate.runtime, an ajax request is sent to the server side, which returns a Mainfest file containing the hash value and chunk name of all modules to be updated.

7. Call the hotDownload Update Chunk method of JsonpMainTemplate.runtime to get the latest module code through the jsonp request
8. When patch js is retrieved, it calls the webpackHotUpdate method of JsonpMainTemplate.runtime, which calls the hotAddUpdate Chunk method and replaces the old module with the hard module.
9. Call the hotAddUpdateChunk method of HotMoudleReplacement.runtime.js to update module code dynamically
10. Call hotApply method for hot update

Client Code Assisted Process Understanding:

  • Client here first loads socket.on("hash") and socket.on("ok") to get the hash value generated by server for the first time.
  • Then the reloadApp function is executed where the hotEmitter.emit('webpackHotUpdate') event is dispatched.
  • Then the hotEmitter.on('webpackHotUpdate') function is executed.
  • Because it's the first compilation, hotCurrentHash is undefined and then assigns the currentHash obtained for the first time to hotCurrentHash.
  • The logic of the initial load is executed here.
  • -------------------next--------------------
  • If the user modifies the code of a module, socket.on("hash") and socket.on("ok") will be executed again to get the latest code-compiled hash.
  • If the above step enters the event judgment in hotEmitter.on('webpackHotUpdate'), hotCurrentHash (!HotCurrentHash | | hotCurrentHash == currentHash) hotCurrentHash is the last hash value currentHash received and judges whether two times are consistent or not, the consistency does not need to be updated, and the inconsistency executes the hot update logic.
  • hotCheck pulls the latest hot-update.json description file from the ajax request server to indicate which modules and which chunk s (large collections) have changed.
  • Then according to the description file hotDownload Update Chunk to create jsonp to pull up the latest updated code, the return form is: webpackHotUpdate(id, {...})
  • To pull the code directly, the client needs to define a window.webpackHotUpdate function to handle it.
  • Here, the cached old code is updated to the latest code, and then the render function in the parent module is executed.
  • Finally, the hotCurrentHash = currentHash is replaced by the old hash to facilitate the next comparison.

2. The client code is implemented as follows:

//publish-subscribe
class Emitter{
	constructor(){
		this.listeners = {}
	}
	on(type, listener){
		this.listeners[type] = listener
	}
	emit(){
		this.listeners[type] && this.listeners[type]()
	}
}
let socket = io('/');
let hotEmitter = new Emitter();
const onConnected = () => {
	console.log('Successful client connection')
}
//Store the hash passed by the server, this hash and the last hash
let currentHash, hotCurrentHash;
socket.on("hash",(hash)=>{
	currentHash = hash
});

//After receiving the ok event
socket.on('ok',()=>{
	//true stands for hot update
	reloadApp(true);
})
hotEmitter.on('webpackHotUpdate',()=>{
	if(!hotCurrentHash || hotCurrentHash == currentHash){
		return hotCurrentHash = currentHash
	} 
	hotCheck()
})

function hotCheck(){
	hotDownloadMainfest().then((update)=>{
		let chunkIds = Object.keys(update.c)
		chunkIds.forEach(chunkId=>{
			hotDownloadUpdateChunk(chunkId);
		})
	})
}
function hotDownloadUpdateChunk(chunkId){
	let script = document.createElement('script');
	script.charset = 'utd-8'
	script.src = '/'+chunkId+'.'+hotCurrentHash+'.hot-update.js'
	document.head.appendChild(script);
}
//This method is used to ask the server what chunk s and modules this compilation has changed relative to the previous compilation.
function hotDownloadMainfest(){
	return new Promise(function(resolve){
		let request = new XMLHttpRequest()
		let requestPath = '/'+hotCurrentHash+".hot-update.json"
		request.open('GET', requestPath, true)
		request.onreadystatechange = function(){
			if(request.readyState === 4){
				let update = JSON.parse(request.responseText)
				resolve(update)
			}
		}
		request.send()
	})
}

function reloadApp(hot){
	if(hot){
		//Release
		hotEmitter.emit('webpackHotUpdate')
	}else{
		//Direct refresh does not support hot updates
		window.location.reload()
	}
}

window.hotCreateModule = function(){
	let hot = {
		_acceptedDependencies:{},
		accept: function(deps, callback){
			//Callback corresponds to render callback
			for(let i = 0; i < deps.length; i++){
				hot._acceptedDependencies[deps[i]] = callback
			}
			
		}
	}
	return hot
}

//The latest code obtained through jsonp has the function webpackHotUpdate in jsonp
window.webpackHotUpdate = function(chunkId, moreModules){
	for(let moduleId in moreModules){
		//Retrieving old module definitions from module caches
		let oldModule - __webpack_requrie__.c[moduleId]
		let {parents, children} = oldModule
		//Which modules are referenced by parents and which modules are used by the module children
		//Update the cache to the latest code
		let module = __webpack_requrie__.c[moduleId] = {
			i: moduleId,
			l: false,
			exports: {},
			parents,
			children,
			hot: window.hotCreateModule(moduleId)
		}
		moreModules[moduleId].call(module.exports, module, module.exports, __webpack_requrie__)
		module.l = true
		//Index.js - - the parent module of import A.js import b.js A.js and b.js (index.js)   
		parents.forEach(par=>{
			//Objects of old modules in the parent
			let parModule = __webpack_requrie__.c[par]
			parModule && parModule.hot && parModule.hot._acceptedDependencies[moduleId] && parModule.hot._acceptedDependencies[moduleId]()
		})
		//After the hot update, this hash becomes the last hash operation.
		hotCurrentHash = currentHash
	}
}


socket.on("connect", onConnected);


Server-side code implementation:

const path = require('path');
const express = require('express');
const mime = require('mime');
const webpack = require('webpack');
const MemoryFileSystem = require('memory-fs');
const config = require('./webpack.config');
//Compoiler represents the entire webpack compilation task, and there is only one global compiler
const compiler = webpack(config);
class Server{
  constructor(compiler){
      this.compiler = compiler;
      let sockets = [];
      let lasthash;//After each compilation, a stats object is generated, with a hash value representing a 32-string result of this compilation.
      compiler.hooks.done.tap('webpack-dev-server',(stats)=>{
          lasthash = stats.hash;
          //Every time a new compilation is completed, a message is sent to the client.
          sockets.forEach(socket=>{
              //Send the latest hash value to the client first
              //Each compilation produces a hash value, and in case of hot updates, two patch files are produced.
              //It describes what chunk s and modules have changed from the last result to this one. 
              socket.emit('hash',stats.hash);
              //Send another ok to the client
              socket.emit('ok');
          });
      });
      let app = new express();
      //Start a webpack compilation with the monitored module, and execute callbacks when the compilation is successful
      compiler.watch({},err=>{
          console.log('Once again the compilation task was successfully completed')
      });
      let fs = new MemoryFileSystem();
      //If you change the compiler's output file system to MemoryFileSystem, then all the output files will be packaged in memory later on.
      compiler.outputFileSystem = fs;
      function middleware(req, res, next) {
          // /index.html   dist/index.html
          let filename = path.join(config.output.path,req.url.slice(1));
          let stat = fs.statSync(filename);
          if(stat.isFile()){//Determine if the file exists, and if so, read it out and send it directly to the browser
            let content = fs.readFileSync(filename);
            let contentType = mime.getType(filename);
            res.setHeader('Content-Type',contentType);
            res.statusCode = res.statusCode || 200;
            res.send(content);
          }else{
             // next();
            return  res.senStatus(404);
          }
      }
      //Expressapp is actually a request listener function
      app.use(middleware);
      this.server = require('http').createServer(app);
      let io = require('socket.io')(this.server);
      //Start a websocket server, then wait for the connection to come, and socket after the connection comes
      io.on('connection',(socket)=>{
        sockets.push(socket);
        socket.emit('hash',lasthash);
        //Send another ok to the client
        socket.emit('ok');
      });
  }
  listen(port){
    this.server.listen(port,()=>{
        console.log(`The server is already there ${port}Start on port`)
    });
  }
}
let server = new Server(compiler);
server.listen(8000);

Posted by DoctorWho on Mon, 12 Aug 2019 01:36:36 -0700