在开发Node.js应用时,经常需要在源代码更改后重启服务器。如果每次都需要手动重启服务器,那将非常烦人。幸运的是,有一些工具可以帮助在检测到目录中的文件更改时自动重启服务器。最著名的工具之一是nodemon。nodemon是一个帮助开发基于Node.js的应用程序的工具,它在检测到目录中的文件更改时自动重启Node应用程序。本文的目的是展示如何创建自己的工具,以监视Node.js应用程序中的任何更改,并在几行代码中自动重启服务器。
首先,需要初始化Node.js项目:
npm init
然后,需要更新package.json
以添加ES6的支持,通过设置module
为type
:
{
"name": "watcher",
"type": "module",
"version": "1.0.0",
"author": "Akram El Assas"
}
接着,将安装开发依赖:
npm i -D @types/node
@types/node:在Visual Studio Code中提供自动补全功能。
将创建一个简单的web服务器server.js
:
import { createServer } from 'http';
const PORT = 8888;
createServer((_, res) => {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.write('Hello World!');
res.end();
}).listen(PORT);
console.log('HTTP server is running on Port', PORT);
然后,将创建一个监视器,以便在检测到服务器父文件夹及其子文件夹中的更改时,每次更改都会重启服务器:
node watcher.js server.js
监视器将在检测到更改时重启服务器。首先,需要获取命令行参数。在Node.js中,可以通过process.argv
访问命令行参数。process.argv
属性返回一个数组,包含启动Node.js进程时传递的命令行参数。第一个元素将是execPath
。第二个元素将是正在执行的JavaScript文件的路径。其余元素将是任何额外的命令行参数。
如果运行以下命令:
node watcher.js server.js
那么process.argv
将如下所示:
[
'C:\\Program Files\\nodejs\\node.exe',
'C:\\dev\\watcher\\src\\watcher.js',
'server.js'
]
第一个元素是Node.js可执行文件的路径。第二个元素是watcher.js
的路径。最后一个元素是server.js
。因此,可以开始代码,声明第一个和第三个元素如下:
const [node, _, file] = process.argv
然后需要创建一个函数,启动一个子进程来启动Node.js,并指定文件作为参数,在例子中是server.js
。为此,将使用child_process
模块的spawn
方法。child_process.spawn()
方法使用给定的命令启动一个新进程,命令行参数在args中。使用spawn
方法的优点是可以重定向子进程的stdout
和stderr
到父进程,使用pipe
方法。
pipe
方法用于将可写流附加到可读流,使其随后切换到流动模式,然后将所有数据推送到附加的可写流。函数源代码如下:
import { spawn } from 'child_process';
const [node, _, file] = process.argv;
const spawnNode = () => {
const childProcess = spawn(node, [file]);
childProcess.stdout.pipe(process.stdout);
childProcess.stderr.pipe(process.stderr);
childProcess.on('close', (code) => {
if (code !== null) {
process.exit(code);
}
});
return childProcess;
};
首先,使用给定的文件参数启动一个子Node.js进程。然后,使用pipe
方法将子进程的stdout
和stderr
重定向到父进程。然后,当子进程关闭时,使用相同的退出代码退出父进程。process.exit()
方法指示Node.js同步终止进程,退出状态为code。如果省略code,exit
使用成功代码0或如果已设置,则使用process.exitCode
的值。Node.js不会终止,直到所有exit
事件侦听器都被调用。最后,返回子进程。
现在,需要检测文件父文件夹及其子文件夹中的更改。每次检测到与JavaScript文件相关的更改时,将杀死子进程并再次生成子进程。为此,将使用fs/promises
模块的watch
方法。fs/promises.watch()
方法返回一个异步迭代器,用于监视文件名的更改,其中文件名可以是文件或目录。将在文件的父文件夹上创建一个监视器。然后,将迭代监视器。将忽略node_modules
文件夹,每次检测到JavaScript文件的更改时,将杀死子进程并再次生成它,如下所示:
let childProcess = spawnNode();
const watcher = watch(dirname(file), { recursive: true });
for await (const event of watcher) {
if (!event.filename.includes('node_modules') && event.filename.endsWith('.js')) {
childProcess.kill('SIGKILL');
childProcess = spawnNode();
}
}
subprocess.kill()
方法向子进程发送一个信号。如果没有给出参数,进程将发送SIGTERM
信号。SIGKILL
信号不能被捕获、阻塞或忽略,并强制子进程停止。有关可用信号的列表,请参见signal(7)
。
刚刚用几行代码设置了自己的nodemon。现在,watcher.js
的整个源代码如下:
import { spawn } from 'child_process';
import { watch } from 'fs/promises';
import { dirname } from 'path';
const [node, _, file] = process.argv;
const spawnNode = () => {
const childProcess = spawn(node, [file]);
childProcess.stdout.pipe(process.stdout);
childProcess.stderr.pipe(process.stderr);
childProcess.on('close', (code) => {
if (code !== null) {
process.exit(code);
}
});
return childProcess;
};
let childProcess = spawnNode();
const watcher = watch(dirname(file), { recursive: true });
for await (const event of watcher) {
if (!event.filename.includes('node_modules') && event.filename.endsWith('.js')) {
childProcess.kill('SIGKILL');
childProcess = spawnNode();
}
}
最后,需要在package.json
中添加start
和dev
脚本,如下所示:
{
"name": "watcher",
"type": "module",
"version": "1.0.0",
"scripts": {
"start": "node server.js",
"dev": "node watcher.js server.js"
},
"author": "Akram El Assas",
"devDependencies": {
"@types/node": "^18.11.17"
}
}
要启动应用程序,只需输入以下命令:
npm run dev
现在,如果运行应用程序并更改server.js
,服务器将自动重启。不再需要手动停止和启动服务器。
这是一个简单的例子,但可以想象其他情况,例如监视视频文件的更改,每次检测到更改时,都会启动一个转换子进程(例如ffmpeg)。
还可以实施其他选项,例如: