65.9K
CodeProject 正在变化。 阅读更多。
Home

用几行代码构建您自己的 nodemon

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (4投票s)

2022年12月26日

CPOL

5分钟阅读

viewsIcon

22462

downloadIcon

61

监控你的 Node.js 应用程序中的任何变化,并自动重启服务器

目录

  1. 引言
  2. 项目设置
  3. 服务器
  4. 监视器 (Watcher)
  5. 整合各部分
  6. 参考文献
  7. 历史

引言

在处理 Node.js 应用程序时,你经常需要在进行一些更改后重启服务器。如果每次修改源代码时都要手动重启服务器,那将非常令人烦恼。幸运的是,有一些可用的工具可以让你在检测到更改时自动重启服务器。其中最著名的是 nodemon。nodemon 是一个帮助开发基于 Node.js 的应用程序的工具,当检测到目录中的文件更改时,它会自动重启 Node 应用程序。本文的目的是不重新发明轮子,而是展示如何用几行代码创建一个自己的实用工具,该工具可以监控你的 Node.js 应用程序中的任何变化并自动重启服务器。

项目设置

第一步将是初始化我们的 Node.js 项目

npm init

然后,我们将更新 package.json,通过将 module 设置为 type 来添加对 ES6 的支持

{
  "name": "watcher",
  "type": "module",
  "version": "1.0.0",
  "author": "Akram El Assas"
}

然后,我们将安装开发依赖项

npm i -D @types/node
  • @types/node: 在 Visual Studio Code 中获得自动补全功能

服务器 (Server)

我们将创建一个简单的 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

监视器 (Watcher)

监视器将在每次检测到更改时重启服务器。

首先,我们需要检索命令行参数。在 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 方法的优点在于,我们可以使用 pipe 方法将子进程的 stdoutstderr 重定向到父进程。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 方法将子进程的 stdoutstderr 重定向到父进程。然后,当子进程关闭时,我们以相同的退出代码退出父进程。process.exit() 方法指示 Node.js 以退出状态码 code 同步终止进程。如果省略 code,exit 将使用成功代码 0 或 process.exitCode 的值(如果已设置)。在所有 exit 事件监听器都调用完毕之前,Node.js 不会终止。最后,我们返回子进程。

现在,我们需要检测文件父文件夹及其子文件夹中的更改。每次检测到与 JavaScript 文件相关的更改时,我们将终止子进程并重新生成子进程。为此,我们将使用 fs/promises 模块中的 watch 方法。fs/promises.watch() 方法返回一个异步迭代器,该迭代器监视 filename 的更改,其中 filename 是文件或目录。我们将在文件的父文件夹上创建一个监视器。然后,我们将迭代监视器。我们将忽略 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。

最后但同样重要的是,我们需要将 startdev 脚本添加到我们的 package.json 中,如下所示

{
  "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 进行更改,服务器将自动重启。我们不再需要手动停止和启动服务器。

整合各部分

我们仅用几行代码就设置了自己的 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()
    }
}

这只是一个简单的例子,但你可以想象其他情况,例如监控视频文件的更改,并在每次检测到更改时启动一个转换子进程 (ffmpeg)。

你还可以实现其他选项,例如

  • 忽略特定文件或目录
  • 监视特定目录
  • 监控多个目录
  • 指定扩展名监视列表
  • 延迟重启
  • 运行 Node.js 以外的可执行文件,如 Python、Ruby、make 等。
  • 等等...

参考文献 (References)

历史 (History)

  • 2022年12月26日 - 初始发布
  • 2023年2月11日 - 内容更新
© . All rights reserved.