fs-extra 是作为替代 Node.js 内置 fs 模块创建的,当你的项目中引入 fs-extra 后,就可以像下面这样做。
将所有使用 require('fs')
的地方:
const fs = require('fs')
// this is no longer necessaryconst fs = require('fs') // this is no longer necessary
改成 require('fs-extra')
:
const fs = require('fs-extra')
在 fs-extra 内部,除了重新导出(re-exporting)内置 fs 模块的所有方法外,还增加了一些常用而且安全的方法,比如 copy()
、remove()
和 mkdirs()
。
为什么?
按作者的原话说:“我已经厌倦了在我大多数项目里都引入一遍 mkdirp
、rimraf
和 ncp
”。
mkdirp
、rimraf
和 ncp
是 3 个 npm 包,用来做不同类型的文件操作:
-
mkdirp
:用来创建目录,支持嵌套嵌套目录的创建。实现类似mkdir -p
指令的功能 -
rimraf
:用来删除文件/目录,支持嵌套文件/目录的删除。实现 Linux 系统上rm -rf
指令的功能 -
ncp
:cp 就是 copy 简写,这个包用来复制文件/目录的,支持嵌套文件/目录的复制
之所以有这 3 个包出现,是因为 Node.js 内置 fs 模块实现上面 3 个功能有些麻烦,特别是嵌套文件/目录的复制和删除。
fs-extra 的做法
fs-extra 的作者将上面 3 个常用(创建目录、删除和复制)的功能统一在一个包中:
-
ensureDir
(别名mkdirs()
、mkdirp()
) 替代mkdirp
-
remove
替代rimraf
-
copy
替代ncp
这些方法默认都是异步的,同步版本需要加上 Sycn
后缀,也就是:ensureDirSync
、removeSync
和 copySync
。
另外,还额外提供了其他的方法:
-
比如操作 JSON 文件读写的
writeJson
和readJson
方法 -
比如清空目录
emptyDir
方法
fs-extra 额外提供的方法很多,我这里挑一些常用的方法做一下讲解。跟 fs 模块的内置方法异样,fs-extra 提供的 API 也会异步和同步版本,为了简单和方便,本文使用同步 API 做下实验。
准备工作
我们新建一个名为 fs-extra-demos
的项目,使用 pnpm 初始化后,在这里进行练习。
$ mkdir fs-extra-demos
$ cd fs-extra-demos
$ pnpm init
$ mkdir fs-extra-demos
$ cd fs-extra-demos
$ pnpm init
按照 fs-extra 依赖:
$ pnpm install fs-extra @types/fs-extra$ pnpm install fs-extra @types/fs-extra
package.json
启用 ES Module。
{ "type": "module", }
在实际练习时,我们经常会用到 __dirname
,但 ES Module 并未提供,下面是获取方式。
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
练习
copySync(src, dest[, options])
用于复制文件或者目录,支持嵌套目录下的复制。
一、如果 src
地址文件不存在会报错
import path from 'node:path'
import { copySync} from 'fs-extra/esm'
// hello 文件不存在的情况下
copySync(path.resolve(__dirname, 'hello'))
// Error: ENOENT: no such file or directory
二、当 src
地址存在,但 dest
参数(也就是目标地址)没赋值时会报错
// hello 文件存在的情况下
copySync(path.resolve(__dirname, 'hello'))
// TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string or an instance of Buffer or URL.
三、复制文件
copySync(path.resolve(__dirname, 'hello'), path.resolve(__dirname, 'hello2'))
// 成功!创建出 hello2
四、复制目录
// 已存在文件 hi/t.txt
copySync(path.resolve(__dirname, 'hi'), path.resolve(__dirname, 'hi2'))
// 成功!复制出 hello2
再次执行也不会报错,同名文件会被复制过来的文件覆盖。
五、复制目录(设置不覆盖文件,出现同名文件时报错)
copySync(path.resolve(__dirname, 'hi'), path.resolve(__dirname, 'hi2'), { overwrite: false, errorOnExist: true })
// Error: 'D:\fe-projects\fs-extra-demos\copySync\hi2\t.txt' already exists
这个很少使用。我们通常不希望意外的报错影响我们的代码流程。
六、复制目录的位置存在同名文件会报错
// 已存在文件 hi/t.txt,已存在文件 hi2
copySync(path.resolve(__dirname, 'hi'), path.resolve(__dirname, 'hi2'))
// Error: Cannot overwrite non-directory 'D:\fe-projects\fs-extra-demos\copySync\hi2' with directory 'D:\fe-projects\fs-extra-demos\copySync\hi'.
同理,复制文件的位置存在同名目录也会报错。
// 已存在文件 hi2, 已存在目录 hi3
copySync(path.resolve(__dirname, 'hi2'), path.resolve(__dirname, 'hi3'))
// Error: Cannot overwrite directory 'D:\fe-projects\fs-extra-demos\copySync\hi3' with non-directory 'D:\fe-projects\fs-extra-demos\copySync\hi2'
当然,这种情况非常少见。记住一个原则就能避免这个错误,就能安全地使用 copySync
方法:在同一个目录结构下,不允许出现同名的目录或者文件。
removeSync(path)
用于删除文件或者目录,支持嵌套目录下的删除。
一、删除文件不存在不会报错
import path from 'node:path'
import { removeSync } from 'fs-extra/esm'
removeSync(path.resolve(__dirname, 'hello'))
二、删除文件或目录
// 已存在文件 hello,已存在文件 hi/t.txt
removeSync(path.resolve(__dirname, 'hello'))
removeSync(path.resolve(__dirname, 'hi'))
// 成功!文件和目录都被删除
目录中即使有内容,也能完全删除,不会报错。总之,removeSync
方法使用起来非常安全,不会报错。
ensureDirSync(dir[,options])
用于创建目录,支持嵌套目录的创建。还有 2 个别名:mkdirsSync
、mkdirpSync
。
多个目录和多层嵌套目录都 OK。
import path from 'node:path'
import { ensureDirSync } from 'fs-extra/esm'
// 创建单层目录 & 多层目录
ensureDirSync(path.resolve(__dirname, 'hello'))
ensureDirSync(path.resolve(__dirname, 'world/x/xx'))
重复执行上述代码也不会报错。对于已存在的目录结构,ensureDirSync
方法什么都不做。
ensureFileSync(file)
跟ensureDirSync类似,ensureFileSync用于创建文件。
一、直接创建文件 & 创建嵌套在目录结构中的文件
import path from 'node:path'
import { ensureFileSync } from 'fs-extra/esm'
// 直接创建文件 & 创建嵌套在目录结构中的文件
ensureFileSync(path.resolve(__dirname, 'hello.txt'))
ensureFileSync(path.resolve(__dirname, 'world/x/xx.txt'))
在创建嵌套在目录结构中的文件时,如果父级目录不存在,会自动创建。
重复执行上述代码也不会报错。对于已存在的文件,ensureFileSync
方法什么都不做。
二、在已存在文件的地方创建同名目录会报错
import path from 'node:path'
import { ensureDirSync } from 'fs-extra/esm'
// 在已存在文件的地方创建同名目录会报错
ensureDirSync(path.resolve(__dirname, 'hello.txt'))
ensureDirSync(path.resolve(__dirname, 'world/x/xx.txt'))
// Error: EEXIST: file already exists, mkdir 'D:\fe-projects\fs-extra-demos\ensureFileSync\hello.txt'
同理,在已存在目录的地方创建同名文件也会报错
import path from 'node:path'
import { ensureDirSync } from 'fs-extra/esm'
// 在已存在文件的地方创建同名目录会报错
ensureDirSync(path.resolve(__dirname, 'hello.txt'))
ensureDirSync(path.resolve(__dirname, 'world/x/xx.txt'))
// Error: EEXIST: file already exists, mkdir 'D:\fe-projects\fs-extra-demos\ensureFileSync\hello.txt'
当然,上面两种情况很少遇到。还是那个原则:在同一个目录结构下,不允许出现同名的目录或者文件。
emptyDirSync(dir)
用于清空目录。支持嵌套目录中的文件清除。
一、清空目录
import path from 'node:path'
import { emptyDirSync } from 'fs-extra/esm'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
// 当前存在文件 you/t/tt/ttt.txt, me/t.txt
emptyDirSync(path.resolve(__dirname, 'you'))
emptyDirSync(path.resolve(__dirname, 'me'))
// 成功清空目录 you & me
二、如果要清空的目录不存在,就会创建这个目录
// 当前没有目录 hi & hello
emptyDirSync(path.resolve(__dirname, 'hello'))
emptyDirSync(path.resolve(__dirname, 'hi'))
// 成功创建目录 hi/ & hello/
emptyDirSync
方法也超好用,不会报错。
readJsonSync(file[, options])
用于读取 JSON 文件。内部使用的是 jsonfile 包提供的方法。
一、读取不存在的文件会报错
import path from 'node:path'
import { readJsonSync } from 'fs-extra/esm'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
console.log(
readJsonSync(path.resolve(__dirname, 'hello.json'))
)
// Error: D:\fe-projects\fs-extra-demos\readJsonSync\hello.json: ENOENT: no such file or directory, open 'D:\fe-projects\fs-extra-demos\readJsonSync\hello.json'
二、会将读取到的文件内容转成 JS 对象
console.log(
readJsonSync(path.resolve(__dirname, '../package.json'))
)
/*
{
name: 'fs-extra-demos',
version: '1.0.0',
description: '',
main: 'index.js',
type: 'module',
scripts: { test: 'echo "Error: no test specified" && exit 1' },
keywords: [],
author: '',
license: 'ISC',
dependencies: { '@types/fs-extra': '^11.0.1', 'fs-extra': '^11.1.1' }
} */
writeJsonSync(file, object[, options])
用于写 JSON 文件。跟 readJsonSync
方法一样,内部使用的是 jsonfile 包提供的方法。
import path from 'node:path'
import { writeJsonSync } from 'fs-extra/esm'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
writeJsonSync(path.resolve(__dirname, 'hello.json'), { hello: 'world' })
// 成功创建文件 hello.json
hello.json
文件内容:
{"hello":"world"}
(注意,有一个空行 EOF,这是默认行为)
当然我们也可以使用第 3 个参数做格式化:
writeJsonSync(path.resolve(__dirname, 'hello.json'), { hello: 'world' }, { spaces: 2, EOL: '\r\n' })
我们使用了 2 个空格的缩进,同时换行符采用 Windows 系统默认的 \r\n
(writeJsonSync
方法默认是 \n
,更好一些)
hello.json
文件被覆盖为:
{
"hello": "world"
}
moveSync(src, dest[, options])
用于移动文件或者目录。
一、移动不存在的文件
import path from 'node:path'
import { moveSync } from 'fs-extra/esm'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
moveSync(path.resolve(__dirname, 'hello'))
// Error: ENOENT: no such file or directory, lstat 'D:\fe-projects\fs-extra-demos\moveSync\hello'
二、移动文件或者目录
// 已存在目录 hello/t/tt/ttt,已存在文件 world.txt
moveSync(path.resolve(__dirname, 'world.txt'), path.resolve(__dirname, 'world2.txt'))
moveSync(path.resolve(__dirname, 'hello'), path.resolve(__dirname, 'hello2'))
// 移动后的结构
// world2.txt
// hello2/t/tt/ttt
三、移动目录的位置存在文件会报错
// hello2 是一个目录,world2.txt 是一个文件
moveSync(path.resolve(__dirname, 'hello2'), path.resolve(__dirname, 'world2.txt'))
// Error: Cannot overwrite non-directory 'D:\fe-projects\fs-extra-demos\moveSync\world2.txt' with directory 'D:\fe-projects\fs-extra-demos\moveSync\hello2'.
同理,移动文件的位置存在目录也会报错
// hello2 是一个目录,world2.txt 是一个文件
moveSync(path.resolve(__dirname, 'world2.txt'), path.resolve(__dirname, 'hello2'))
// Error: Cannot overwrite directory 'D:\fe-projects\fs-extra-demos\moveSync\hello2' with non-directory 'D:\fe-projects\fs-extra-demos\moveSync\world2.txt'.
也就是说,移动的问题存在不同类型的文件,就会报错。
pathExistsSync
判断指定路径下的文件或者目录是否存在。其实就是 fs.existsSync()
方法的别名。
import path from 'node:path'
import { pathExistsSync } from 'fs-extra/esm'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
console.log(
pathExistsSync(path.resolve(__dirname, 'hello'))
)
// false
// 创建 hello 目录或文件
console.log(
pathExistsSync(path.resolve(__dirname, 'hello'))
)
// true
pathExistsSync
方法用起来也很安全,不会报错。
总结
总的来说,使用 fs-extra 还是很安全的,只要记住一个原则就好:在涉及写操作的时候,要确保写入的是同一个文件类型(文件写文件,目录写目录),否则会报错。
-
copySync
:用于文件或目录的复制,只要保证复制的原文件存在就能安全使用 -
removeSync
:用于删除文件或目录,可以安全使用,永远不会报错 -
ensureDirSync
& ensureFileSync:分别用于目录和文件的创建,当目标位置如果有目录或文件存在时,什么都不会做,正如方法名所言,是用来“保证(ensure)”这个位置有目录或文件的 -
readJsonSync
:读取 JSON 文件,只要保证复制的原文件存在就能安全使用 -
writeJsonSync
:写 JSON 文件。可以使用第 3 个参数做写入 JSON 数据的格式化 -
moveSync
:移动文件或目录,只要保证移动的原文件存在就能安全使用 -
pathExistsSync
:判断文件或目录是否存在,可以安全使用,永远不会报错
转载于:fs-extra: 替代 Node.js 内置 fs 模块,更安全、更强大的文件操作库 - 哔哩哔哩