前言
我的 github/blog,给个小星星咯~
最近因为工作,需要写一个脚本来自动读取文件夹下的某个文件,把其中的内容写到另一个新生成的文件中。因为这种场景还是挺常见的,网络上也搜不到好的(手把手教学的)解决方案,这对于还没学过 node.js
的前端小白来说,很不友好啊~
于是这篇文章就手把手教你写一个这样的脚本,都会尽量解释清楚,保证你看了就会!
场景举例
假如有这么一个项目,其文件目录如下:
|-- app1
|-- config.json
|-- index.js
|-- app2
|-- config.json
|-- index.js
|-- app3
|-- config.json
|-- index.js
|-- app4
|-- config.json
|-- index.js
|-- config.all.js
|-- package.json
index.js
中的内容是啥与本文无关,只是做个样子,但是在每个 app
文件夹中都有一个 config.json
文件,这就是我们需要读的配置文件,现在我们要做的就是写一个 node
脚本去监听当前这个目录的文件变动,并且实时地去读各个 app
下的配置文件,并写入 config.all.js
文件中。
现在假设配置文件 config.json
内容大概如下:
{
"name": "vortesnail",
"github": "github.com/vortesnail",
"age": "24",
"address": "earth",
"hobby": ["sing", "dance", "rap", "code"]
}
各个 app
文件下的 config.json
内容可不一致,比较符合我们的实际项目场景。
脚本编写
安装 chokidar
因为用原生的 fs.watch
会有很多问题,并且有很大的局限,我们采用第三方模块 chokidar 来进行文件的监听。
npm install chokidar
创建脚本文件
现在在根目录下创建我们的脚本文件: auto-config.js
,当然,名字随你。
先引用我们的第三方模块 chokidar
,以及 node 核心模块 fs
、 path
以及 process
。
const chokidar = require('chokidar')
const fs = require('fs')
const path = require('path')
const process = require('process')
const PROJECT_PATH = process.cwd()
PROJECT_PATH
表示当前目录路径。
使用 chokidar.watch
这里需要注意我们是 chokidar.watch('.', {}).on()
, .
代表当前跟路径,使用 PROJECT_PATH
会有问题,有知道的大佬可以评论交流一下!
const chokidar = require('chokidar')
const fs = require('fs')
const path = require('path')
const process = require('process')
const PROJECT_PATH = process.cwd()
chokidar.watch('.', {
persistent: true,
ignored: /(^|[\/\\])\..|auto-config.js|config.all.js|node_modules/,
depth: 1
}).on('all', (event, pathname) => {
console.log(event, pathname)
// do something later...
})
- persistent: 与原生
fs.watch
一样,表示是否保护进程不退出持久监听,默认值为true。 - ignored: 所要忽略监听的文件或文件夹。
- depth: 只监听当前目录以及下一级子目录。
使用 fs.readdirSync
使用 fs.readdirSync(PROJECT_PATH)
可读取当前目录的文件列表,是数组形式,数组内容为每一个文件或文件夹的名字。更新我们的代码:
chokidar.watch('.', {
persistent: true,
ignored: /(^|[\/\\])\..|auto-config.js|config.all.js|node_modules/,
depth: 0
}).on('all', (event, pathname) => {
console.log(event, pathname)
- // do something later...
+ const rootFilenames = fs.readdirSync(PROJECT_PATH)
+ console.log(rootFilenames)
})
现在已经可以在当前目录执行 node auto-config.js
来查看当前控制台的打印了,会发现循环打印了当前目录下的文件名字数组:
[
'app1',
'app2',
'app3',
'app4',
'auto-config.js',
'config.all.js',
'node_modules',
'package-lock.json',
'package.json'
]
循环的原因是 chokidar
第一次会监听当前目录所有文件的 add 事件,都有哪些事件详情可看这: event。
循环遍历每个文件夹并获取子目录文件列表
获得了当前目录的文件,我们需要先筛选出文件夹,再对该文件夹(比如我们的 app1
、 app2
文件夹)使用上面使用过的 fs.readdirSync([路径])
来获取配置文件所在目录的文件列表, [路径]
可通过字符串拼接得到。
chokidar.watch('.', {
// ...
}).on('all', (event, pathname) => {
console.log(event, pathname)
const rootFilenames = fs.readdirSync(PROJECT_PATH)
- console.log(rootFilenames)
+ rootFilenames.forEach(function(file) {
+ const newPath = path.join(PROJECT_PATH, `/${file}/`)
+ const subFilenanme = fs.readdirSync(newPath)
+ console.log(subFilenanme)
+ })
})
但是现在会报错,因为对于 fs.readdirSync
来说,若读取的当前路径为一个文件而不是一个文件夹,就会发生错误并终止程序的运行。故我们需要对其做一个判断。
读取文件状态 fs.stat
使用 fs.stat(path,callback)
,而不是 fs.statSync
,我们可以处理错误发生后的一些操作。
callback
有两个参数: (err,stats),stats 是一个 fs.Stats 对象。stats.isDirectory()
可判断是否是文件夹。
更新代码如下:
chokidar.watch('.', {
// ...
}).on('all', (event, pathname) => {
console.log(event, pathname)
const rootFilenames = fs.readdirSync(PROJECT_PATH)
rootFilenames.forEach(function(file) {
const newPath = path.join(PROJECT_PATH, `/${file}/`)
fs.stat(newPath, function(err, stats) {
if(err){
console.log(file + 'is not a directory...')
} else {
const isDir = stats.isDirectory() //是文件夹
if (isDir) {
const subFilenanmes = fs.readdirSync(newPath)
console.log(subFilenanmes)
}
}
})
})
})
现在已经可以获取到子目录的文件列表了,接下来可以判断是否找到我们需要读取的文件,并且读文件了。
使用 fs.readFileSync 与 fs.writeFileSync
我们需要一个变量来存储读取到的值,这里我们使用
let content = ''
这里我只是简单的读取 .json
文件,并将其内容后添加一个 ,
并全部写入到新生成的 config.all.js
文件中。
添加代码如下:
chokidar.watch('.', {
persistent: true,
ignored: /(^|[\/\\])\..|auto-config.js|config.all.js|node_modules/,
depth: 0
}).on('all', (event, pathname) => {
console.log(event, pathname)
+ let content = ''
const rootFilenames = fs.readdirSync(PROJECT_PATH)
rootFilenames.forEach(function(file) {
const newPath = path.join(PROJECT_PATH, `/${file}/`)
fs.stat(newPath, function(err, stats) {
if(err){
console.log(file + 'is not a directory...')
} else {
const isDir = stats.isDirectory() //是文件夹
if (isDir) {
const subFilenanmes = fs.readdirSync(newPath)
- console.log(subFilenanmes)
+ subFilenanmes.forEach(function(file) {
+ if (file === 'config.json') {
+ const data = fs.readFileSync(path.join(newPath, file), 'utf-8') //读取文件内容
+ content += data + ',' + '\n'
+ }
+ fs.writeFileSync(path.join(PROJECT_PATH, 'config.all.js'), `module.exports={data: [${content}]}`)
+ })
}
}
})
})
+ console.log(`配置表 config.all.js 已自动生成...`)
})
到目前为止,这个读写脚本就算完成了,你不信你执行 node auto-config.js
,再打开根目录下 config.all.js
文件看看,是不是把所有 app
目录下的 config.json
中的文件写入到里面了,而且你任意修改一下当前目录以及子目录的任一文件内容,都会重新生成配置表。
处理瑕疵
最后的的打印因为第一次监听会生成很多很多。。。这看起来太丑了,可以加一个防抖,只让它输出一次。
另外,还可以在适合的地方加一些提示,现放出完整代码:
const chokidar = require('chokidar')
const fs = require('fs')
const path = require('path')
const process = require('process')
const PROJECT_PATH = process.cwd()
chokidar.watch('.', {
persistent: true,
ignored: /(^|[\/\\])\..|auto-config.js|config.all.js|node_modules/,
depth: 0
}).on('all', (event, pathname) => {
console.log(event, pathname)
let content = ''
const rootFilenames = fs.readdirSync(PROJECT_PATH)
rootFilenames.forEach(function(file) {
const newPath = path.join(PROJECT_PATH, `/${file}/`)
fs.stat(newPath, function(err, stats) {
if(err){
console.log(file + 'is not a directory...')
} else {
const isDir = stats.isDirectory() //是文件夹
if (isDir) {
const subFilenanmes = fs.readdirSync(newPath)
subFilenanmes.forEach(function(file) {
if (file === 'config.json') {
const data = fs.readFileSync(path.join(newPath, file), 'utf-8') //读取文件内容
content += data + ',' + '\n'
}
fs.writeFileSync(path.join(PROJECT_PATH, 'config.all.js'), `module.exports={data: [${content}]}`)
})
}
}
})
})
success()
})
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
const success = debounce(() => {
console.log('配置表 config.all.js 已自动生成...')
}, 500)
现在你再试试 node auto-config.js
,看看效果吧~
webpack打包配置
有的时候,我们不仅仅是只在项目中使用而已,我们需要打包出一个脚本文件,丢到 nginx
环境中去,在那个根目录打开我们的脚本,自动时时刻刻监听文件的变动,生成配置表,彻底解放双手!
打包配置很简单,不要慌!
安装必要插件
无非就是一些 webpack 打包需要的依赖而已,比较重要的是 node-loader
这个包。
npm install -D webpack webpack-cli node-loader
webpack配置
根目录创建 webpack.auto.js
const path = require('path');
module.exports = {
target: "node",
entry: {
script: path.resolve(__dirname, "auto-config.js"),
},
output: {
publicPath: '',
filename: '[name].js',
path: path.resolve(__dirname, "build"),
},
module: {
rules: [
{
test: /\.node$/,
use: 'node-loader'
}
],
},
node: {
fs: 'empty',
child_process: 'empty',
tls: 'empty',
net: 'empty'
},
};
比较重要的地方就是 target: node
,以及入口文件要写对,因为 fsevents 中有 .node 文件,我们需要对其处理,需要一个 node-loader
做识别转译。
修改 package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
+ "build:auto": "webpack -p --progress --config webpack.auto.js"
},
打包
现在,你在控制台执行 npm run build:auto
,一个可以监听并读写的小脚本就这样完成了!尽情去使用吧。把打出来的包丢到任意目录,执行:
node auto-config.js
// 没权限的话需要要加 sudo
sudo node auto-config.js
完美!
结语
自我认为该写法还有很大改进地方,奈何自己水平有限,如果有大佬有更好的意见,非常希望您能在评论区说出来,让更多像我这样“求知若渴”的同学得到成长,感谢!🙏
推荐阅读:
这一次,彻底理解 https 原理