preface
Nodemon is my favorite source monitoring tool for Node.
background
Previously explored Node-Watch, Chokidar, read their source code, roughly understand the implementation of the train of thought.
Now there is another problem. Every time you change a file, you need to restart it for the service to take effect. This makes our development less efficient. The above two plug-ins do not solve this problem. With the appearance of Nodemon, we can monitor the changes of files at any time and automatically restart the service. We only need to pay attention to the code during development, and there is no need to manually restart the service.
Let’s explore how Nodemon can automatically restart the server.
The project structure
|____index.js // Develop file entry
|____README.md
|____yarn-lock.json
|____package.json
Copy the code
Install nodemon
yarn add nodemon -D
Copy the code
Start the project
First you need to configure the packge.json to start the project using Nodemon instead of Node
{
"scripts": {
"start": "nodemon index.js".// Start with nodemon instead}}Copy the code
After the configuration, run the command on the terminal
yarn start
Copy the code
experience
You can change the contents of the index.js file, write something, and save it. You’ll see that the terminal displays the project restart.
Search for the source code
According to the command nodemon index.js, it can be seen that the bin command under Nodemon must be executed. According to this direction, find the bin object under Nodemon /pacage.json
"bin": {
"nodemon": "./bin/nodemon.js"
},
Copy the code
/bin/nodemon.js according to the preceding address
#! /usr/bin/env node
const nodemon = require('.. /lib/');
nodemon(options); // Start the project
Copy the code
Nodemon is a method wrapped in lib/index.js.
The first step
The first step resets all configuration information, resets the listening file queue, and kills the child process.
function nodemon(settings) {
/ / reset
nodemon.reset();
/ /...
}
Copy the code
The reset function looks like this:
bus.on('reset'.function (done) {
debug('reset');
nodemon.removeAllListeners(); // Clear all listeners
monitor.run.kill(true.function () {
utils.reset(); // Reset the utility function
config.reset(); // Reset the configuration information
config.run = false; // Turn off the running state, the next restart through this state whether to start
if(done) { done(); }}); });Copy the code
The utils utility function has the following configuration information:
const utils = {
semver: semver,
satisfies: test= > semver.satisfies(process.versions.node, test),
version: {/* Version control */},
clone: require('./clone'), / / clone
merge: require('./merge'), / / merge
bus: require('./bus'), / / subscribe
isWindows: process.platform === 'win32'.isMac: process.platform === 'darwin'.isLinux: process.platform === 'linux'.isRequired: (function () {/* Check whether */ can be executed normally}) (),home: process.env.HOME || process.env.HOMEPATH,
quiet: function () {/* Reset the log function */},
reset: function () {/* Reset the log function */},
regexpToText: function (t) {/* Matches the special character */ },
stringify: function (exec, args) {/* to string */}};Copy the code
As a global object, config is configured as follows. It interacts with multiple files and records the monitored and filtered files and directories.
const config = {
run: false.system: {
cwd: process.cwd(),
},
required: false.dirs: [].timeout: 1000.options: {},}function reset() {
config.dirs = []; // Listen to the directory
config.options = { ignore: [].watch: [].monitor: []};// Listen options, including filter files, listen files, files already in observer
config.lastStarted = 0;
config.loaded = [];
}
Copy the code
Then the nodemon command will be converted into node command. Node implements the operation of the project. The Nodemon index.js executed at the beginning will be converted into Node index.js
// allow the cli string as the argument to nodemon, and allow for
// `node nodemon -V app.js` or just `-V app.js`
if (typeof settings === 'string') {
settings = settings.trim();
if (settings.indexOf('node')! = =0) {
if (settings.indexOf('nodemon')! = =0) {
settings = 'nodemon ' + settings;
}
settings = 'node ' + settings; // Execute the command, such as node index.js
}
settings = cli.parse(settings);
}
Copy the code
The second step
Read all the listening files in the root directory, fill in the information of config configuration items, and listen to the user’s keystrokes, such as CTRL + D, CTRL + L, and so on
Listening for keyboard events
config.load(settings, function (config) {
if (config.options.stdin && config.options.restartable) {
// If you press CTRL + L, clear the information printed on the console
if (str === config.options.restartable) {
bus.emit('restart');
} else if (data.charCodeAt(0) = = =12) { // ctrl+l
console.clear(); }}else if (config.options.stdin) {
if (chr === 3) {
if (ctrlC) {
process.exit(0);
}
ctrlC = true;
return;
} else if (buffer === '.exit' || chr === 4) { // ctrl+d
process.exit();
} else if (chr === 13 || chr === 10) { // enter / carriage return
buffer = ' ';
} else if (chr === 12) { // ctrl+l
console.clear();
buffer = ' '; }}}Copy the code
Start the
config.load(settings, function (config) {
config.run = true;
monitor.run(config.options); // Pass the configuration information to monitor to start the listener
}
Copy the code
After config is filled, all the information is as follows:
{
run: false.system: { cwd: '/Users/zhoujianpiao/Desktop/node/rollup' },
required: false.dirs: [].timeout: 1000.options: { ignore: [].watch: [].monitor: []},load: [Function (anonymous)],
reset: [Function: reset],
lastStarted: 0.loaded: []}Copy the code
The third step
This step is responsible for listening for files.
As soon as you run run, restart is immediately started.
restart = run.bind(this, options);
run.restart = restart;
Copy the code
The start signal is then notified in the form of publish subscriptions
bus.emit('start');
Copy the code
The actual file listening is done by watch.js. All monitoring files are saved in the Watchers queue. If no monitoring file is found, watch is not executed.
function watch() {
// Check whether there are listening files
if (watchers.length) {
debug('early exit on watch, still watching (%s)', watchers.length);
return; }}Copy the code
Chokidar is a high-performance, stable file listening tool that uses different listening systems according to different operating environments. Nodemon’s core listening source code is here.
const promise = new Promise(function (resolve) {
// Configuration information
var watchOptions = {
ignorePermissionErrors: true.ignored: ignored, // File ignored
persistent: true.// Keep the process running after it is ready
usePolling: config.options.legacyWatch || false.interval: config.options.pollingInterval,
};
// Create a listener
var watcher = chokidar.watch(
dirs,
Object.assign({}, watchOptions, config.options.watchOptions || {})
);
watcher.ready = false;
var total = 0;
watcher.on('change', filterAndRestart); // The callback function is notified of file changes
watcher.on('add'.function (file) {
if (watcher.ready) {
return filterAndRestart(file);
}
watchedFiles.push(file);
bus.emit('watching', file);
});
watcher.on('ready'.function () {
watchedFiles = Array.from(new Set(watchedFiles)); // ensure no dupes
total = watchedFiles.length;
watcher.ready = true; // Ready
resolve(total);
debugRoot('watch is complete');
});
watchers.push(watcher);
});
Copy the code
FilterAndRestart is responsible for filtering files, looking for matching files, and getting those files that are actually listening.
function filterAndRestart(files) {
// Matches a file that can be listened on
if (matched.result.length) {
if (config.options.delay > 0) {
utils.log.detail('delaying restart for ' + config.options.delay + 'ms');
if (debouncedBus === undefined) {
debouncedBus = debounce(restartBus, config.options.delay);
}
debouncedBus(matched); // If delay is set, the anti-shake function is called
} else {
return restartBus(matched); // Otherwise, restart the server}}}}Copy the code
RestartBus is responsible for restarting the server
// Restart the server
function restartBus(matched) {
utils.log.status('restarting due to changes... '); // This print prompt can be seen on the console
matched.result.map(file= > {
utils.log.detail(path.relative(process.cwd(), file));
});
bus.emit('restart', matched.result); // Issue a restart notification
}
Copy the code
The fourth step
To update the file, the run.kill() function executes, sends a SIGINT flag to the child process, kills the child process, and then restarts the child process using the exit event handler.
bus.on('restart'.function () {
// run.kill will send a SIGINT to the child process, which will cause it
// to terminate, which in turn uses the 'exit' event handler to restart
run.kill();
});
Copy the code
summary
The above four episodes are the core part of nodemon’s entire process. What I find most interesting is the use of a publish-subscribe model.
conclusion
- When Nodemon runs, it will first reset all configuration information, such as clearing the queue of the previously monitored subprocess, clearing the subscribed events, and resetting all tool methods
nodemon index.js
Terminal commands are converted tonode index.js
- Once you’ve done the basics, scan the entire root directory for all executable files and process user parameter information, such as
ignoring
Which files to filter, all fill toconfig.options
In the. - Next, start the server, create the child process, use the Chokidar tool, start the file to listen for events.
- When the file changes,
watcher
The instance will executefilterAndRestart
Callback function. If the restart of the service needs to be delayed, the system uses the anti-shake function to restart the service according to the time specified by the user. Otherwise, the system restarts the service immediatelybus.emit('restart', matched.result)
Release a restart notification. - If a restart message is received, the system will execute the command
run.kill()
Function to send one to the child processSIGINT
Flag, kill the child process, and then the child process to useexit
The event handler is restarted.