这两天刚刚开始对ts的学习,感受到了ts的魅力,但是对于ts这个指令编译我觉得过于麻烦了,即使有tsc xxx.ts -w这样的监控指令我也觉得有点麻烦,毕竟不能监控整个文件夹下的所有ts文件,由此我就想到了自己做一个ts自动编译器。
好的,以上就是背景了,那么现在我们要开始实现了,其实这个自动编译很简单,就两步,第一步监听文件内容变化,第二步,对ts文件进行编译并生成js文件,知道了具体内容,那么接下来就是实现了,首先我们先来第一步,这里需要使用node。
const fs = require('fs');
因为需要对文件进行监控,所以要引用fs模块,接着就是使用里面的watch方法,对文件进行监控。watch方法接受三个参数:
用法:
fs.watch( filename[, options][, listener] )参数:此方法接受上述和以下所述的三个参数:
- filename:它是一个字符串,Buffer或URL,表示要监视的文件或目录的名称。
- options:它是可用于修改方法行为的字符串或对象。它是一个可选参数。它具有以下参数:
- persistent:它是一个布尔值,用于指定只要正在监视文件,该过程是否应继续。默认值是true。
- recursive:它是一个布尔值,用于指定是否应监视给定目录的所有子目录。默认值为false。
- encoding:它是一个字符串,它指定用于传递给侦听器的文件名的字符编码。
- listener:它是在访问或修改文件时调用的函数。它是一个可选参数。
- eventType:它是一个字符串,它指定文件进行的修改的类型。
- filename:它是一个字符串或Buffer,它指定触发事件的文件名。
然后由于我们需要监听的是整个文件夹,所以filename可以是当前文件夹的路径。
recursive需要设置为true。
然后回调里需要输出本次修改的类型以及当前修改的文件名字,方便之后做处理,所以我们的代码如下:
import fs from 'fs';
fs.watch('../TS', {
recursive: true
}, ((event, filename) => {
console.log(`检测到文件变化.....变化类型为${event}文件名字是${filename}`);
}))
让我们看看运行后的结果:
现在看起来是完成了,但是还是有问题,因为我们要做的是ts的编译器,所以我们应该只对ts文件进行编译,所以我们这里应该加一个分支;
import fs from 'fs';
let flag = true; //作为开关控制是否开启编译
fs.watch('../TS', {
recursive: true
}, ((event, filename) => {
flag = true; //手动初始化
if (filename.endsWith('ts')) {
flag = false;
}
if (flag) return //如果当前文件不是ts文件则不进行编译
console.log(`检测到文件变化.....变化类型为${event}文件名字是${filename}`);
}))
现在我们已经完成了第一步,成功的监听到了文件的变化,接下来做第二步,对ts文件进行编译并生成js文件。
当然这里我不会自己去写个编译器(别问,问就是不会),那不写编译器怎么办呢?我们日常是怎么编译的?是不是用指令tsc xxx.ts,那如果我们可以使用node自动运行这段指令,是不是问题就迎刃而解了。那么我们现在的问题就由编译ts文件,变成了运行ts指令,那么我们现在先来模拟一下;
import fs from 'fs';
let flag = true; //作为开关控制是否开启编译
let t = fs.watch('../TS', {
recursive: true
}, ((event, filename) => {
flag = true;
if (filename.endsWith('ts')) {
flag = false;
}
if (flag) return //如果当前文件不是ts文件则不进行编译
console.log(`检测到文件变化.....变化类型为${event}文件名字是${filename}`);
make(filename)
}))
function make(filename) { //编译方法
console.log(filename+'文件已被编译');
}
现在来运行一下,看看输出结果;
触发是触发了,但是有个问题就是为什么会触发两次?这里我并不知道真正的原因,在我查询了许久之后找到了这样的一篇回答:
翻译过来就是:
在某些情况下,您可能会注意到,单个创建事件会生成多个由组件处理的 Created 事件。例如,如果使用 FileSystemWatcher 组件监视目录中新文件的创建,然后使用记事本创建文件对其进行测试,则即使只创建了单个文件,也可能会看到生成的两个 Created 事件。这是因为记事本在写入过程中执行多个文件系统操作。记事本分批写入磁盘,创建文件内容,然后创建文件属性。其他应用程序可能以相同的方式执行。由于 FileSystemWatcher 监视操作系统活动,因此将选取这些应用程序触发的所有事件。
那么由此可知,第二次回调的触发是不可避免的,那我们现在要做的就是上锁,忽视掉多余的哪次调用;
import fs from 'fs';
let flag = true; //作为开关控制是否开启编译
let timeEr = null;
let t = fs.watch('../TS', {
recursive: true
}, ((event, filename) => {
flag = true;
if (timeEr) {
return
}
if (filename.endsWith('ts')) {
flag = false;
}
if (flag) return //如果当前文件不是ts文件则不进行编译
console.log(`检测到文件变化.....变化类型为${event}文件名字是${filename}`);
timeEr = setTimeout(()=>{
make(filename);
timeEr = null
},50)
}))
function make(filename) { //编译方法
console.log(filename+'文件已被编译');
}
运行结果为:
那么基本的设置就完成了,现在要做的就是真的运行指令,这里我们用到的是node里的exec模块;
function make(filename) { //编译方法
console.log(filename+'文件已被编译');
let order = `tsc ./${filename}`;
//第一个参数时要运行的指令 第二个时运行之后触发的回调
exec(order, (err, stdout, stderr) => {
if (err) {
console.log('err',stdout);
console.log('stderr',stderr);
return;
}
console.log('编译完成');
})
}
但是这里我们需要对运行的指令进行一个修改,因为这这样简单的tsc会让编译后的js文件和ts文件处于同一个文件夹下,不免显得有点难看,这里我们稍微修改一下;
import fs from 'fs';
import { exec } from'child_process';
import path from 'path';
let flag = true; //作为开关控制是否开启编译
let timeEr = null;
let t = fs.watch('../TS', {
recursive: true
}, ((event, filename) => {
flag = true;
if (timeEr) {
return
}
if (filename.endsWith('ts')) {
flag = false;
}
if (flag) return //如果当前文件不是ts文件则不进行编译
console.log(`检测到文件变化.....变化类型为${event}文件名字是${filename}`);
const file = path.parse(filename).name;
timeEr = setTimeout(()=>{
make(filename,file);
timeEr = null
},50)
}))
function make(filename,file) { //编译方法
console.log(filename+'文件已被编译');
let order = `tsc --outFile ./dist/${file}.js ./${filename}`;
exec(order, (err, stdout, stderr) => {
if (err) {
console.log('err',stdout);
console.log('stderr',stderr);
return;
}
console.log('编译完成');
console.log('-----------------------------');
})
}
那么到这里,一个简单的ts自动编译器就完成了。