今天跟大家分享一个好用的技能 -- 监听文件变化,自动重启服务
很多时候我们都会用到 webpack
中带的热更新插件来实现这个功能,接下来我们就来看下如何使用 node
原生的 api
来实现。
此法不一定适用于所有项目 ,需要根据实际项目来自行选择。
首先我们来看下需要用到的 模块和api
1. 需要用到的模块
-
fs
- 毫无疑问,想要监听文件变化,此模块是必不可少的。
-
path
- 此模块可以说跟
fs
狼狈为奸,我们可以使用此模块来获得任何我们想要的路径。
- 此模块可以说跟
-
child_process
- 此模块可能大家不太了解,但是如果我们要做到自动重启这个功能,那么它就是必不可少的模块。因为只有这个模块可以帮助我们来运行
shell
命令。
- 此模块可能大家不太了解,但是如果我们要做到自动重启这个功能,那么它就是必不可少的模块。因为只有这个模块可以帮助我们来运行
好了,前置依赖讲完之后,我们就来看下如何实现 --- 监听文件变化,自动重启服务
2. 功能实现
2.1 文件变化触发函数执行
首先我们要做第一步,在文件发生改变的时候,触发某个函数执行。这一步我们需要用到 fs
和 path
两个模块来完成。
第一步,获得我们需要到路径
🌰栗子奉上
let dirPath = path.join(__dirname, './', '文件内容')
第二步,监听文件变化
fs.watch(dirPath, {}, () => {
console.log('文件有变化')
});
注意:
如果监听的是文件夹,可以将第二个参数修改为 { recursive: true }
,表示可以监听文件夹内的所有文件。
添加完这两步之后就可以监听到文件的变化。
2.2 结束当前进程
这一步,我们要在文件变化后退出当前进程。这个实现也是比较简单。只需要在 2.1
的基础上添加一行代码就可以
fs.watch(dirPath, {}, () => {
console.log('文件有变化,退出当前服务')
process.exit()
});
process.exit()
表示将当前进程退出
2.3 添加退出的钩子函数
到这里,有的人就会问了,如果当前进程退出了,那就所有的代码都不会执行了,如何能让服务重启呢?
不急,通过探查 node 的 api 文档,我们得知这样一个知识点:
Node 进程退出之前会触发当前进程的 exit 钩子函数。
既然有解决的方法,我们就来实现以下
// 首先给当前进程添加 exit 钩子函数
process.addListener('exit', () => {
console.log('进程马上退出了')
})
// 然后添加文件监听
fs.watch(dirPath, {}, () => {
console.log('文件有变化,退出当前服务')
process.exit()
});
这里大家可以看到,退出之前会打印一行文案,并且退出当前进程。
2.4 添加服务重启
既然可以在退出之前做点儿事情,那么我们就可以在退出之前做重新启动。这里的逻辑可能不太好理解。后面的文章中我们会专门讲解一下这里的内容
🌰栗子:
let spawn = require('child_process').spawnSync
process.addListener('exit', () => {
console.log('进程马上退出了')
// 添加服务重启
spawn(`node${dirPath}`, { stdio: 'inherit', shell: true });
})
步骤解析:
- 首先加载
child_process.spawnSync
这个方法,然后通过这个方法来执行服务重启的命令。 - 参数说明
-
stdio: inherit
这里配置的是,将子进程执行的命令内容显示到终端。 -
shell: true
指运行这个shell命令。
-
注意: 之后我们会具体讲解一下 child_process
这个模块。这里就不赘述了。
配置完成之后,我们就可以看到服务可以重新启动。
切莫欣喜,这里还有很多未知的问题,下面我们来一一探查一下。
3. 问题解析
运行的时间久了大家可能会发现这样一个问题,这里的命令只能执行一次,之后就不生效了。
下面我们就来解决一下这个问题。
第一步,重启改造
function start () {
startStatus = false;
let child = exports.child = spawn(execText, { stdio: 'inherit', shell: true });
child.addListener("exit", function () {
if (!startStatus) {
exports.child = null;
}
start();
});
}
说明:
- 首先,我们将重启的服务,封装为了一个方法,然后我们把退出的监听放到了
child
,也就是我们的子进程上面。 - 第二步,我们添加了一个
startStatus
状态,保证当前服务重启完成之前不会再次出发重启。 - 然后我们把子进程的进程 id 存储在了
exports.child
上面,因为随着服务的变更,进程 id 可能会发生变化。所以需要持久保存。
第二步,监听改造
let dirPath = path.join(__dirname, './index.js')
fs.watch(dirPath, {}, restart);
function restart () {
if (startStatus)
return;
startStatus = true;
let child = exports.child;
if (child) {
process.kill(child.pid, "SIGTERM");
} else {
startProgram();
}
}
说明:
- 先获取到子进程的进程 id
- 然后判断id是否存在。如果存在,则将进程杀掉,否则就重启进程
- 如果当前进程没有结束,不会再次执行
restart
方法。
好了,到这里我们的服务自动重启就完成了。试验一下吧。
完整代码展示。
let fs = require("fs");
let spawn = require("child_process").spawn;
let path = require("path");
let startStatus = false;
let dirPath = path.join(__dirname, './getParams.js')
let execText = `node ${dirPath}`
if (!exports.init) {
exports.init = true
start()
}
function start () {
startStatus = false;
let child = exports.child = spawn(execText, { stdio: 'inherit', shell: true });
child.addListener("exit", function () {
if (!startStatus) {
exports.child = null;
}
start();
});
}
function restart () {
if (startStatus)
return;
startStatus = true;
let child = exports.child;
if (child) {
process.kill(child.pid, "SIGTERM");
} else {
start();
}
}
let dirPath1 = path.join(__dirname, './test.html')
fs.watch(dirPath1, {}, restart);
自己运行有惊喜哟~~
期望本文对你有所帮助。那今天的分享就到这里了。
Bye~