问题
上一篇文章我们实现了基本构建功能,做到了在业务代码开发完成之后构建为脚本文件,但是在开发过程中还是不方便,因为每次修改都要重新手动运行构建。因此,我们需要实现持续构建,即每当源文件发生修改时,就自动重新构建所在的脚本目录。
初步实现
在构建程序中,创建全局变量watched,存放已监听的源文件路径列表,用于防止重复监听。
每当构建一个脚本目录时,除了构建脚本文件,还对源文件做监听。对于每个涉及的源文件(index.js、index.css(如果有);不监听template.js,因为不变),如果不在watched中,则:
- 调用
fs.watchFile(),注册回调函数,监听源文件的改变。当源文件内容改变时(前后的mtimeMs不一致),则重新构建源文件所在的脚本目录(不递归)。 - 将源文件加入
watched。
启动构建程序时:
- 删除
dist/。 - 对
src/执行构建(递归)。
在对src/递归构建一遍后,此时所有源文件都已被监听,每当源文件修改都会自动重新构建。
构建模式
构建有两种场景:
- 开发时,需要持续构建。
- 部署时,需要一次性构建。
因此,我们让构建程序接收一个可选的命令行参数--watch,决定是否对源文件监听,即是否开启持续构建。
将package.json改为:
"scripts": {
"build": "node index.js",
"watch": "node index.js --watch"
}
那么,可通过如下命令执行不同的构建模式:
# 一次性构建模式
npm run build
# 持续构建模式
npm run watch
优化监听性能
实测发现,通过fs.watchFile()实现监听,在发生修改到执行回调之间,有很大的延迟,这导致修改代码后总是不能立即重新构建,刷新页面时加载的还是旧代码,要等一会儿刷新,新代码才生效。
查阅Node.js API文档发现,fs.watchFile()是通过轮询文件的状态信息(stats)来检测文件变化的,效率很低。文档推荐使用fs.watch(),因为它是直接通过操作系统机制(通知、事件等)实现的,效率要高得多。
因此,将fs.watchFile()改为fs.watch(),将判断文件内容变化的条件改为eventType === "change"。
实测,及时性有了显著提高,可以做到立即重新构建。
支持源文件增删
目前只实现了对已有源文件修改的监听,但实际开发过程中还有增加、删除源文件的场景,例如:
- 新建一个脚本目录,新建
index.js,开始开发新功能。 - 编写代码时发现需要写样式,因此新建
index.css。 - 编写代码时发现不需要样式了,因此删除
index.css。
目前只能重新启动构建程序,否则增、删源文件不会触发重新构建。
查阅fs.watch()的文档发现,它支持递归监听整个目录,目录下任何层级的文件变化都会触发回调,通过回调的filename参数可以知道是哪个文件/目录发生了变化。这就类似于在window上集中监听DOM树中所有节点的事件。
那么,现在不需要watched变量了,只需要在src/上注册一次递归监听。当src/中的任何文件/目录发生变化时,不必区分变化类型(增、删、改),直接将变化文件所属的目录作为脚本目录,对其调用构建函数(非递归)。在构建函数中,如果脚本目录存在,且index.js存在,则构建,否则不构建,即可。