很多前端同学没有服务端或者客户端经验,对node操作望而生畏,这大可不必。
正如大神Winter所说:所谓编程,无非IO。一般意义上的Web前端,处理的是基于http与浏览器(或宿主)的本地缓存、各种API的输入输出。Node作为一个服务端平台,面向的并不是浏览器,而是服务器本身,所以node可以操作的,是整个服务端资源,其中包括前端并不常接触的网络IO、磁盘IO、数据库IO等等;要学习这些操作,看看node文档、写个demo玩一下,相信你很快就能掌握。
进一步讲,我们日常基于webpack/gulp等工具对前端项目进行打包等等这些操作,就是通过node和各种工具包,对自己电脑上的代码资源进行测试、重构、压缩,输出不同需求的代码包。如果把这个流程放到服务端操作,并整合发布审核流程,就是我们常说的CI/CD。
本文主要展示node自带的几个基础接口能力。
process
Node主进程。
process.env
% node
Welcome to Node.js v15.12.0.
Type ".help" for more information.
> process.env
复制代码
process
是node主进程,process.env
中带有运行时各种环境变量。其中常用的有
process.env.PWD // 启动脚本时的目录
process.env.HOME // 当前用户目录
process.env.USER // 当前用户名
process.env.SHELL // 当前终端名称
process.env.PATH // 系统全局变量
复制代码
process.argv
脚本执行的完整命令+参数。
path
// js
const path = require('path')
复制代码
path
用于处理各种本地路径问题。
path.join 路径拼接
> path.join('/baseDir', 'a', 'b')
'/baseDir/a/b'
> path.join('/baseDir', 'a', '../b')
'/baseDir/b'
复制代码
path.resolve 路径处理
> path.resolve('/baseDir', 'a', 'b')
'/baseDir/a/b'
> path.resolve('/baseDir', 'a', '../b')
'/baseDir/b'
复制代码
path.relative 相对路径
> path.relative('/baseDir', '/baseDir/a')
'a'
> path.relative('/baseDir', '/someElseDir/a')
'../someElseDir/a'
> path.relative('/baseDir/a/aa', '/baseDir/b/bb')
'../../b/bb'
复制代码
path.dirname 获取所在目录名
> path.dirname('/baseDir/a/b')
'/baseDir/a'
> path.dirname('/baseDir/a/b/')
'/baseDir/a'
复制代码
由于Windows
系统和Mac
Linux
的路径格式不一样,所以要处理路径问题时,一定要使用path
,如非必要不要尝试自己拼接。
fs
const fs = require('fs')
复制代码
fs
也就是file system
的缩写,故名思义就是操作文件的工具。为了方便,这里都只介绍异步方法。同步方法直接加Sync
,具体请查阅node文档。
fs.exists 文件或目录是否存在
> fs.exists('/baseDir/a/b.html', r => console.log('exists', r))
> exists false // 该文件确实不存在
> fs.exists('~/11111.zip', r => console.log('exists', r))
> exists false // 不支持使用 ~/
> fs.exists(path.join(process.env.HOME, '11111.zip'), r => console.log('exists', r))
> exists true // 使用环境变量拼接完整地址
> fs.exists(process.env.HOME, r => console.log('exists', r))
> exists true // 也可以检查目录是否存在
复制代码
fs.stat 路径详情
文件详情信息
> fs.stat(path.join(process.env.HOME, '11111.zip'), (err,stat) => console.log('\nstat', stat, '\nerror', err))
stat Stats {
dev: 16777221,
mode: 33188,
nlink: 1,
uid: 501,
gid: 20,
rdev: 0,
blksize: 4096,
ino: 12006418,
size: 332419104,
blocks: 649264,
atimeMs: 1615184984501.856,
mtimeMs: 1615184993657.0806,
ctimeMs: 1615185011988.9607,
birthtimeMs: 1615184984501.856,
atime: 2021-03-08T06:29:44.502Z,
mtime: 2021-03-08T06:29:53.657Z,
ctime: 2021-03-08T06:30:11.989Z,
birthtime: 2021-03-08T06:29:44.502Z
}
error null
复制代码
目录详情信息
> fs.stat(process.env.HOME, (err,stat) => console.log('\nstat', stat, '\nerror', err))
stat Stats {
dev: 16777221,
mode: 16877,
nlink: 78,
uid: 501,
gid: 20,
rdev: 0,
blksize: 4096,
ino: 363143,
size: 2496,
blocks: 0,
atimeMs: 1624841990808.2407,
mtimeMs: 1624841990597.5269,
ctimeMs: 1624841990597.5269,
birthtimeMs: 1603169031408.2742,
atime: 2021-06-28T00:59:50.808Z,
mtime: 2021-06-28T00:59:50.598Z,
ctime: 2021-06-28T00:59:50.598Z,
birthtime: 2020-10-20T04:43:51.408Z
}
error null
复制代码
路径不存在
> fs.stat('/baseDir/a/b', (err,stat) => console.log('\nstat', stat, '\nerror', err))
stat undefined
error [Error: ENOENT: no such file or directory, stat '/baseDir/a/b'] {
errno: -2,
code: 'ENOENT',
syscall: 'stat',
path: '/baseDir/a/b'
}
复制代码
判断路径类型
> fs.stat(process.env.HOME, (err,stat) => console.log('\nisDir', stat.isDirectory(), 'isFile', stat.isFile(), '\nerror', err))
isDir true isFile false
error null
复制代码
> fs.stat(path.join(process.env.HOME, '11111.zip'), (err,stat) => console.log('\nisDir', stat.isDirectory(), 'isFile', stat.isFile(), '\nerror', err))
isDir false isFile true
error null
复制代码
PS:根据我自己的印象,在不同的node
版本中,对于不存在的路径,fs.stat反馈存在差异。建议先使用fs.exists
判断存在性。
fs.readFile 读取文件内容
使用该方法前,应当先确定文件是否存在。
> fs.readFile(path.join(process.env.HOME, 'testnpm/build.js'), (err, content) => console.log('\ncontent',content,'\nerror', err))
content <Buffer 63 6f 6e 73 6f 6c 65 2e 6c 6f 67 28 27 62 75 69 6c 64 27 29 0a>
error null
复制代码
上面的代码没有指定编码格式,所以返回文件内容是Buffer
二进制格式。下面使用utf-8
格式按文本读取文件:
> fs.readFile(path.join(process.env.HOME, 'testnpm/build.js'), 'utf-8', (err, content) => console.log('\ncontent',content,'\nerror', err))
content console.log('build')
error null
复制代码
fs.writeFile 写入文件内容
使用该方法前,应当先确定文件是否存在。
> fs.writeFile(path.join(process.env.HOME, 'a.txt'), 'hello fs', err => console.log('\nerr', err))
err null
复制代码
# 查看文件
% cat a.txt
hello fs
复制代码
fs.unlink 删除文件
> fs.unlink(path.join(process.env.HOME, 'a.txt'), err => console.log(err))
> null
复制代码
如果目录不存在,则报错:
> fs.unlink('/xxxxxx/xxxxxx.js', err => console.log(err))
> [Error: ENOENT: no such file or directory, unlink '/xxxxxx/xxxxxx.js'] {
errno: -2,
code: 'ENOENT',
syscall: 'unlink',
path: '/xxxxxx/xxxxxx.js'
}
复制代码
fs.mkdir 创建目录
> fs.mkdir(path.join(process.env.HOME, 'fs'), err => console.log('err', err))
> err null
复制代码
如果目录已存在,则报错
> fs.mkdir('/usr', err => console.log('err', err))
> err [Error: EEXIST: file already exists, mkdir '/usr'] {
errno: -17,
code: 'EEXIST',
syscall: 'mkdir',
path: '/usr'
}
复制代码
fs.rmdir 删除目录
> fs.rmdir(path.join(process.env.HOME, 'fs'), err => console.log('err', err))
> err null
复制代码
报错情况
该操作比较危险,报错情况相对复杂。
目录不存在
> fs.rmdir('/xxxxxx', err => console.log('err', err))
> err [Error: ENOENT: no such file or directory, rmdir '/xxxxxx'] {
errno: -2,
code: 'ENOENT',
syscall: 'rmdir',
path: '/xxxxxx'
}
复制代码
权限不足
> fs.rmdir('/usr', err => console.log('err', err))
> err [Error: EPERM: operation not permitted, rmdir '/usr'] {
errno: -1,
code: 'EPERM',
syscall: 'rmdir',
path: '/usr'
}
复制代码
非空目录
> fs.rmdir(path.join(process.env.HOME, 'testnpm'), err => console.log('err', err))
> err [Error: ENOTEMPTY: directory not empty, rmdir '/Users/js/testnpm'] {
errno: -66,
code: '{ recursive: true }',
syscall: 'rmdir',
path: '/Users/js/testnpm'
}
复制代码
在较高版本的node
中,fs.rmdir
可以使用{ recursive: true }
参数项,递归删除整个目录,避免ENOTEMPTY
错误。但有同学反馈node12不支持,我还没有对此问题作详细研究。考虑到兼容,还是手动递归、删除为好。
fs.readdir 读取目录中的文件列表
> fs.readdir('/usr', (err, res) => console.log(res))
> [
'X11', 'X11R6',
'bin', 'lib',
'libexec', 'local',
'sbin', 'share',
'standalone'
]
复制代码
fs + path 递归读取全部文件
const path = require('path')
const fs = require('fs')
const read = p => {
p = path.resolve(process.env.PWD, p)
const exists = fs.existsSync(p)
if(!exists) {
return []
}
const stat = fs.statSync(p)
if(stat.isFile()) {
return [p]
} else {
return fs.readdirSync(p)
.filter(f => !/^\./.test(f))
.map(pp => path.join(p, pp))
.map(p => read(p))
.reduce((r, v) => [...r, ...v], [])
}
}
console.log(read('./').map(f => f.replace(process.env.HOME, '')))
复制代码
技巧点:
- 使用
path.resolve
+process.env.PWD
实现从脚本运行点出发,补全完成目录。 - 使用
fs.exists
fs.stat
判断路径存在性和类型,返回不同的数据。 - 在使用
path.readdir
获取到文件名称列表后,通过正则过滤掉.
..
两类操作路径符。本例也会过滤掉以.
起头命名的路径,如.gitignore
文件。 - 使用递归减少代码量
- 使用
reduce
打平数组。