Hot Update (HMR) stands for Hot Module Reload and is often used in build tools.

The page automatically updates as soon as we change the code during development. How does this work?

Now let’s build the simplest hot update, line by line.

So the question is how do we get the file to change to the page and automatically respond to updates?

First, we divided the whole process into three steps

1. Listen for file changes. 2

Write pseudocodes 1, 2, 3

// server

watch(file); / / 1
fileContent = readFile(file) / / 2
send(fileContent); / / 3
Copy the code

Next, choose a plan.

Step 1: Listen for file changes

We know that nodeJS has an Fs.watch API. It can listen for file changes.

(fs.FSWatcher) fs.watch(filename[, options][, listener])
Copy the code

But take a closer look at some documentation, StackOverflow

There are a few issues with node’s native Watch API. At this time go back to find some libraries to implement

Use Chokidar to implement file listening

const chokidar = require('chokidar');
// One-liner for current directory
chokidar.watch('demo/chokidar').on('all'.(event, path) = > {
    console.log(path);
});
Copy the code

Successfully monitored for file changes, but found that after starting the service, the path was printed directly. Now we just want the event to be triggered when the file changes. I’m going to change the code like this and replace all with change and get rid of the event argument

const chokidar = require('chokidar');
// One-liner for current directory
chokidar.watch('demo/chokidar').on('change'.(path) = > {
    console.log(path);
});
Copy the code

Restarting the Server

It is now ok to listen for file changes.

Step 2: Read the contents of the file

Fs.readfilesync (nodeJS, fs.readfilesync, fs.readfilesync, fs.readfilesync

const chokidar = require('chokidar');
const fs = require('fs');
const path = require('path');

chokidar.watch('demo/ws-chokidar').on('change'.(relativePath) = > {
    const filePath = path.resolve(__dirname, '.. /.. / ', relativePath);
    const data = fs.readFileSync(filePath, 'utf-8');
    console.log(data, relativePath);
    
    ws.send(data);
});
Copy the code

Since path is a relative path, readFileSync needs to read the file path for library splicing. Now let’s see what happens

As we modify the change.html content at will, the console prints out our file contents and path

Step 3: Notify the browser to update the page

We know that usually through AJAX HTTP protocol only the client can send a request to the server, receive the response. How to let the server actively notify the client which files are updated.

Obviously websocket is needed. Although Websockets have API support in both browsers and Nodes, decrypting socket data can be cumbersome. Here we need the WebSocket library.

One is socket. IO and one is WS. Ws is used only as an example here.

Let’s start by writing a front page. Simply construct a socket send and listener.

<! DOCTYPEhtml>
<html>
    <head>
        <meta charset="utf-8" />
        <title>socket</title>
    </head>
    <body>
        <button class="button">send</button>
        <h2>data:</h2>
        <div class="response-data"></div>

        <script>
            var oButtons = document.getElementsByClassName('button');
            var oResponseDatas = document.getElementsByClassName('response-data');
            var oResponseData = oResponseDatas.length && oResponseDatas[0];


            if (oButtons.length) {
                oButtons[0].addEventListener('click', onClick, false);
            }

            function onClick() {
                var socket = new WebSocket('ws://localhost:3000');

                socket.open = function() {
                    socket.send('123');
                }
                socket.onmessage = function(event) {
                    console.log(event.data);
                    oResponseData.innerHTML = event.data;
                }

                socket.onclose = function() {
                    console.log('close'); }}</script>
    </body>
</html>
Copy the code

We then use WS to build send and sink to the server side

const chokidar = require('chokidar');
const WebSocket = require('ws');
const fs = require('fs');
const path = require('path');

const wss = new WebSocket.Server({ port: 3000 });

wss.on('connection'.function connection(ws) {
    ws.on('message'.function incoming(message) {
        console.log('received: %s', message);
    });

    chokidar.watch('demo/ws-chokidar').on('change'.(relativePath) = > {
        const filePath = path.resolve(__dirname, '.. /.. / ', relativePath);
        const data = fs.readFileSync(filePath, 'utf-8');
        console.log(data, relativePath);

        ws.send(data);
    });

});
Copy the code

Let’s test the effect

Start the Node server first. Then click the page send button to build the socket connection.

Successful connection

Change the contents of change.html. Let’s try adding a button

<button>This is a button</button>
Copy the code

As soon as the file is saved, the browser receives the message and updates the pageSoon, pow!! Messge inside received the data, the page is also more than a button.

Add a slightly more complicated HTML to change.html and see if it works

<div style="height: 300px; width: 300px; border-radius: 50%; background: crimson; text-align: center; color:white; line-height: 300px;">This is a ball</div>
Copy the code

I didn’t blink this time. Out came a big red ball. There are several more records in the right message.

This is basically done. Of course, formal projects are far from that simple. The rest is up to you to study directly.

Finally, look at the Vite code

Hot updates are more complex for projects that are modular built. There is a dependency on update page replacement.

Let’s see what Vite does. So let’s take some of the code here.

  const mods = moduleGraph.getModulesByFile(file)

  // check if any plugin wants to perform custom HMR handling
  const timestamp = Date.now()
  const hmrContext: HmrContext = {
    file,
    timestamp,
    modules: mods ? [...mods] : [],
    read: () = > readModifiedFile(file),
    server
  }

  for (const plugin of config.plugins) {
    if (plugin.handleHotUpdate) {
      const filteredModules = await plugin.handleHotUpdate(hmrContext)
      if (filteredModules) {
        hmrContext.modules = filteredModules
      }
    }
  }

  if(! hmrContext.modules.length) {// html file cannot be hot updated
    if (file.endsWith('.html')) {
      config.logger.info(chalk.green(`page reload `) + chalk.dim(shortFile), {
        clear: true.timestamp: true
      })
      ws.send({
        type: 'full-reload'.path: config.server.middlewareMode
          ? The '*'
          : '/' + normalizePath(path.relative(config.root, file))
      })
    } else {
      // loaded but not in the module graph, probably not js
      debugHmr(`[no modules matched] ${chalk.dim(shortFile)}`)}return
  }

  updateModules(shortFile, hmrContext.modules, timestamp, server)
}
Copy the code

Talk to you next time

More articles on the simple wheel will follow. Want to keep an eye on the front wheels