import.meta
import.meta.dirname 和 import.meta.filename
import.meta.dirname 是 Node.js、Deno 等运行时在 ES 模块(ESM) 中提供的属性,用于获取当前模块文件所在目录的绝对文件系统路径,是 CommonJS 中 __dirname 的 ESM 替代方案。
同理,import.meta.filename是 CommonJS 中 __filename 的 ESM 替代方案。
关系:import.meta.dirname ≡ path.dirname(import.meta.filename)
Node.js:v20.6.0+ 实验性,v22.16.0 /v24.0.0+ 稳定
// 模块文件:/project/src/utils.js console.log(import.meta.dirname);
// Unix/macOS
输出:/project/src
// Windows
输出:C:\project\src
import.meta.url
在 ES 模块(ESM)中,import.meta.url 会返回当前模块文件的 URL 字符串,格式是:
file:///Users/xxx/project/index.js
-
是 URL 格式,不是普通文件路径
-
有
file://协议头 -
路径分隔符是
/
fileURLToPath,Node.js 内置工具函数,来自:
import { fileURLToPath } from 'node:url';
把 file://xxx 这种文件 URL 转换成操作系统原生文件路径。
合起来:fileURLToPath(import.meta.url),它的结果就是:当前执行文件的绝对路径(字符串)。
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
console.log(__filename);
在 macOS/Linux 输出:
/Users/xxx/project/index.js
在 Windows 输出:
C:\Users\xxx\project\index.js
以前 CommonJS 里有:
__filename:当前文件完整路径__dirname:当前文件所在目录
ESM 里没有这两个变量,所以标准兼容写法是:
import { fileURLToPath } from 'node:url';
import path from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
为什么不直接用 import.meta.dirname?
import.meta.dirname是 Node.js 新版本才支持(v22.16+ / v24+)- 老项目、服务器环境可能不支持
fileURLToPath(import.meta.url)兼容性极强,几乎所有现代 Node.js ESM 都能用
总结:
-
import.meta.url→ 拿到file://格式的当前文件地址 -
fileURLToPath(...)→ 把它转成系统能用的正常绝对文件路径
path
path 模块专门用来处理文件路径,解决跨平台(Windows/macOS/Linux)路径格式不一致的问题,是 Node.js 必备工具。
path.join()
拼接路径(自动处理斜杠、跨平台兼容,最常用!)
import path from 'node:path';
const fullPath = path.join('a', 'b', 'c'); // a/b/c (mac/linux)
2. path.resolve()
解析为绝对路径(从右往左拼接,遇到绝对路径停止)
path.resolve('foo', '/bar', 'baz'); // /bar/baz
3. path.basename()
获取文件名(含 / 不含后缀)
path.basename('/a/b/c.js'); // c.js
path.basename('/a/b/c.js', '.js'); // c
4. path.dirname()
获取文件所在目录(就是 __dirname)
path.dirname('/a/b/c.js'); // /a/b
5. path.extname()
获取文件后缀名
path.extname('/a/b/c.js'); // .js
path.extname('index.html'); // .html
6. path.relative()
计算从一个路径到另一个路径的相对路径
path.relative('/a/b', '/a/c/d'); // ../c/d
7. path.normalize()
规范化路径(解析 .. . 多余斜杠)
path.normalize('/a//b/c/../d'); // /a/b/d
URL
node:url 是 Node.js 官方用来解析、构造、转换 URL的核心模块,专门处理各种网址、文件路径(file://)、data: 协议等。
import url from 'node:url';
const myURL =
new url.URL('https://user:pass@sub.example.com:8080/xxx/yyy?a=1&b=2#hash');
console.log(myURL.hash, myURL.host, myURL.searchParams);
console.log(myURL.searchParams.get('a'));
myURL.searchParams.set('b', 222);
myURL.searchParams.append('c', 333);
console.log(myURL.searchParams.toString())
创建一个 URL 实例,传入 url 字符串。
它会解析出 url 中各部分的内容,并且会把 query string 封装成 URLSearchParams 的实例。
URLSearchParams 有 get、set、append、toString 等方法。
当然,你也可以直接 new URLSearhParams:
const params = new url.URLSearchParams('?aa=1&bb=2');
console.log(params);
for (const [name, value] of params) {
console.log(name, value);
}
fs
rm
删除文件 或 删除文件夹(包括非空)
Node.js ≥14.14 支持,现在所有项目都应该用它。
import fs from 'node:fs/promises';
await fs.rm('要删的路径', {
recursive: true, // 递归删除(删文件夹必须加)
force: true // 不存在也不报错
});
能删什么?
- 文件
- 空文件夹
- 非空文件夹(加
recursive: true)
注意:fs.rmdir(旧版)仅删空文件夹,文件夹里有东西,直接报错。
mkdir
import fs from 'node:fs/promises';
// 1. 创建单层文件夹 await fs.mkdir('my-folder');
// 2. 创建多层文件夹(最常用!)
await fs.mkdir('a/b/c/d', { recursive: true });
// 3. 安全创建(已存在不报错)
await fs.mkdir('dist', { recursive: true });
注意:
-
创建多层文件夹时,一定要加
recursive: true; -
创建的路径不能是绝对路径,可以是
aaaa/bbb,但不能是/aaa/bbb,这是 在系统根目录 / 下创建(需要 root 权限),可以这样写process.cwd() + '/aaa/bbb'
还有就是同步的写法:
fs.mkdirSync('dist/assets', { recursive: true });
下面介绍下对文件的操作。
writeFile
fs.writeFile 是异步写入文件**,不会阻塞主线程,Node.js 开发更推荐用它(尤其是服务端)。
-
writeFileSync:同步,阻塞代码
-
writeFile:异步,非阻塞(现代写法用
async/await)
import fs from 'node:fs/promises'
async function write() {
const data = { name: '张三', age: 22 }
// 异步写入 JSON 文件
await fs.writeFile('data.json', JSON.stringify(data, null, 2), 'utf-8')
console.log('写入成功!')
}
write()
console.log('先执行') // 这一行会先打印
JSON.stringify(data, null, 2)这里的第三个参数的意思是,每个层级缩进 2 个空格,作用是让生成的 JSON 格式化、好看、人类可读。
fs.writeFile 第三个参数可以是对象,也可以是字符串,如果是字符串一般是'utf-8'。
await fs.writeFile('log.txt', '新内容\n', {
encoding: 'utf-8',
flag: 'a' // 追加,不覆盖
})
-
'w'→ 覆盖写入(默认) -
'a'→ 追加写入(不覆盖)
还有一点要注意,目录不存在时,要先创建目录,然后在写入:
import fs from 'node:fs/promises'
import path from 'node:path'
async function safeWrite() {
const filePath = 'dist/data.json'
// 自动创建目录
await fs.mkdir(path.dirname(filePath), { recursive: true })
// 再写入
await fs.writeFile(filePath, '内容', 'utf-8')
}
readFile
readFile 就是 异步读取文件内容,和 writeFile 是一对。
import fs from 'node:fs/promises';
async function read() {
// 读取文本文件
const content = await fs.readFile('test.txt', 'utf-8');
console.log(content);
}
read();
不传编码,返回 Buffer(二进制),如:<Buffer 68 65 6c 6c 6f>。
appendFile
writeFile:覆盖整个文件appendFile:追加到文件末尾
import fs from 'node:fs/promises';
await fs.appendFile('log.txt', '异步追加一行\n', 'utf-8');
unlink
只能删文件,不能删文件夹,文件不存在会报错。
import fs from 'node:fs'
try {
fs.unlinkSync('test.txt')
console.log('文件删除成功')
} catch (err) {
// 文件不存在也不崩溃
if (err.code !== 'ENOENT') throw err
}
ENOENT 是 Node.js(以及所有类 Unix 系统)里最常见的文件系统错误码,你可以把它直接理解成:文件或目录不存在。
- E = Error
- NO = No
- ENT = Entry
直译:没有这个条目,也就是:你要操作的路径不存在。
readdir
用来读取一个目录下的所有文件和子文件夹名称。
import fs from 'node:fs/promises';
async function readDir() {
// 读取当前目录
const files = await fs.readdir('./');
console.log(files); // [ 'a.txt', 'b.js', 'dist', ... ]
}
readDir();
带上文件信息(withFileTypes):
const files = fs.readdirSync('aaa', { withFileTypes: true })
for (const item of files) {
console.log(item.name) // 文件名
console.log(item.isFile()) // 是否是文件
console.log(item.isDirectory()) // 是否是文件夹
}
递归读取所有文件(recursive):包括文件和文件夹。
stat
用来获取文件 / 文件夹的详细信息:大小、创建时间、是不是文件、是不是文件夹等。
async function showStat() {
const stat = await fs.stat('data.json')
console.log(stat)
}
showStat()
stat.size // 文件大小(字节)
stat.birthtimeMs // 创建时间戳
stat.mtimeMs // 修改时间戳
stat.uid / stat.gid // 用户/组ID
最常用的判断方法:
stat.isFile() // 是否是文件
stat.isDirectory() // 是否是文件夹
stat.isSymbolicLink() // 是否是软链接
async function info() {
const s = await fs.stat('package.json')
console.log('是文件:', s.isFile())
console.log('大小:', s.size)
console.log('修改时间:', new Date(s.mtimeMs))
}
info()
copyFile vs cp
copyFile 只复制文件,不能复制文件夹。目标文件已存在会直接覆盖。
import fs from 'node:fs/promises';
// 源文件 → 目标文件
await fs.copyFile('source.txt', 'target.txt');
console.log('复制成功!');
cp是 Node.js 现代、全能、推荐的复制 API,既能复制文件,也能复制整个文件夹。
不过 fs 这个 cpSync 方法直到 node 22 才不再是实验性的。
所以低版本 node 还是要自己实现,或者用 fs-extra:
实现原理也很简单:
import fs from 'node:fs'
import path from 'node:path'
function copyDir(srcDir, destDir) {
fs.mkdirSync(destDir, { recursive: true })
for (const file of fs.readdirSync(srcDir)) {
const srcFile = path.resolve(srcDir, file)
const destFile = path.resolve(destDir, file)
copy(srcFile, destFile)
}
}
function copy(src, dest) {
const stat = fs.statSync(src)
if (stat.isDirectory()) {
copyDir(src, dest)
} else {
fs.copyFileSync(src, dest)
}
}
copy('aaa', 'aaa2')
用 fs.statSync 拿到文件的信息,如果是目录就递归复制目录,否则就 fs.copyFileSync 复制文件。
目录的复制就是用先用 fs.mkdirSync 创建目录,然后用 fs.readdirSync 读取目录的内容。
用 path.resolve 拼接下路径为绝对路径,之后继续递归 copy。