通过 respawn 加强需要即时生效的开发环境构建系统
便利的开发环境是一个开发团队非常重要的基础建设之一,好的构建环境能大幅的提高业务开发人员的效率,顺畅的开发过程也能维持一个好心情。
其中构建环境的持续响应程度是良好构建系统一个要点,我们不仅需要构建系统的功能完善,也需要它能及时的将结果呈现。
一般的面向浏览器的静态资源文件构建不会存在这个方面的问题,构建生成新的结果文件只是坐等被引用加载而已。
但是如果是面向 server 的 nodejs 项目构建,你就得考虑在构建完成时,新文件能应用在已经启动的服务中。 这就是本文要优化的问题。
child_process 的 exec/spawn
nodejs 原生提供的 api 中,有 exec/spawn
两种语法,都是启动子进程相关。不同的是,spawn
可以支持挂起的服务性应用,比如一个服务器,我们可以通过此 api,在构建结束后,将服务重新启动,这也是笔者之前用的方法。
不过存在几个不方便的问题:
- 停止进程,原生没有提供进程停止相关的方法,只能是通过系统杀死进程的命令,把 pid 给 kill 掉;
- 多次启动,如要我们的场景需要在服务启动失败后再次尝试多次,用 child_process 就比较麻烦了,在 onerror 和启动之前要求一段比较高质量的代码
除以上两点外,其它的一些小不便就不再说了,虽然都可以用代码解决,但在之前笔者完成后发现这一部分写得脚本真有不少了,实际我只需要一个友好的启动及监控方法而已。
respawn
respawn 就是一个 spawn 的封装方法工具,除原生 spawn 的功能之外,自身还提供了几个非常好用的常规方法。
-
monitor.start() Starts the monitor
-
monitor.stop(cb) Stops the monitor (kills the process if its running with SIGTERM)
-
monitor.status Get the current monitor status. Available values are running, stopping, stopped, crashed and sleeping
通过这几个“简单”的方法,我们再来实现上述提到的需求就非常便捷了。
代码示例
下面分享一个 gulp 构建环境的 watch 重启代码,用 respawn
来进行服务的及时重启。
/**
* 主构建方法
*/
function transpile () {...}
/**
* 启动服务
*/
function startServe(done) {
console.log(gutil.colors.green('Start server ...'));
// 服务启动命令
const command = [ 'node', '--harmony' ];
command.push('index.js');
// 启动进程
const monitor = respawn(command, {
env,
cwd: PATH_DIST,
maxRestarts: 10,
sleep: 300,
stdio: 'inherit',
});
monitor
.on('stdout', (data) => console.log(data.toString()))
.on('stderr', (err) => console.error(err.toString()));
// 重启
function restartMonitor () {
monitor.stop(() => monitor.start());
}
monitor.start();
/**
* watch 子方法
* 文件改动后
*/
function watchHandle (evt) {
if (evt.endsWith('.html')) {
moveTpl()
.resume()
.on('end', () => {
restartMonitor();
});
}
else {
let isLintError = false;
lint()
.resume()
.on('error', () => {
isLintError = true;
})
.on('end', () => {
if (isLintError) {
// 未通过 lint,不进行启动
return;
}
transpile().on('end', restartMonitor);
});
}
}
/**
* 开始监听任务
*/
const watchIns = gulp.watch([ PATH_JS, PATH_TPL, '../dist' ], (watchDone) => {
gutil.log(`Watch project is doing ...`);
watchDone();
});
watchIns.on('change', (evt) => {
gutil.log(`File change : ${evt}`);
// 如果是 server 端的变化,重启 server
if (evt.indexOf('src/') === 0) {
watchHandle(evt);
}
});
watchIns.on('add', (evt) => {
gutil.log(`File add : ${evt}`);
watchHandle(evt);
});
watchIns.on('unlink', (evt) => {
gutil.log(`File delete : ${evt}`);
watchHandle(evt);
});
done();
}
/**
* 默认任务
* @module gulp#4.0
*/
gulp.task('default', gulp.series(
// 第一步:clean + lint
gulp.parallel(clean, lint),
// 第二步:编译 + 移动模板
gulp.parallel(transpile, moveTpl),
// 第三步:启动服务
startServe
));