npm install rush-fs
# or
pnpm add rush-fs安装 rush-fs 时,包管理器会通过 optionalDependencies 自动安装当前平台的本地绑定(例如 macOS ARM 上的 @rush-fs/rush-fs-darwin-arm64)。若未安装或出现「Cannot find native binding」:
- 删除
node_modules和锁文件(package-lock.json或pnpm-lock.yaml)后重新执行pnpm install(或npm i)。 - 或手动安装对应平台包:
macOS ARM:pnpm add @rush-fs/rush-fs-darwin-arm64
macOS x64:pnpm add @rush-fs/rush-fs-darwin-x64
Windows x64:pnpm add @rush-fs/rush-fs-win32-x64-msvc
Linux x64 (glibc):pnpm add @rush-fs/rush-fs-linux-x64-gnu
import { readdir, stat, readFile, writeFile, mkdir, rm } from 'rush-fs'
// 读取目录
const files = await readdir('./src')
// 递归 + 返回文件类型
const entries = await readdir('./src', {
recursive: true,
withFileTypes: true,
})
// 读写文件
const content = await readFile('./package.json', { encoding: 'utf8' })
await writeFile('./output.txt', 'hello world')
// 文件信息
const s = await stat('./package.json')
console.log(s.size, s.isFile())
// 创建目录
await mkdir('./new-dir', { recursive: true })
// 删除
await rm('./temp', { recursive: true, force: true })测试环境:Apple Silicon (arm64),Node.js 24.0.2,release 构建(开启 LTO)。 运行
pnpm build && pnpm bench可复现。
这些场景中 Rust 的并行遍历和零拷贝 I/O 发挥了真正优势:
| 场景 | Node.js | Rush-FS | 加速比 |
|---|---|---|---|
readdir 递归(node_modules,约 3 万条目) |
281 ms | 23 ms | 12x |
glob 递归(**/*.rs) |
25 ms | 1.46 ms | 17x |
glob 递归 vs fast-glob |
102 ms | 1.46 ms | 70x |
copyFile 4 MB |
4.67 ms | 0.09 ms | 50x |
readFile 4 MB utf8 |
1.86 ms | 0.92 ms | 2x |
readFile 64 KB utf8 |
42 µs | 18 µs | 2.4x |
rm 2000 个文件(4 线程) |
92 ms | 53 ms | 1.75x |
access R_OK(目录) |
4.18 µs | 1.55 µs | 2.7x |
cp 500 文件平铺目录(4 线程) |
86.45 ms | 32.88 ms | 2.6x |
cp 树形目录 ~363 节点(4 线程) |
108.73 ms | 46.88 ms | 2.3x |
单文件操作有约 0.3 µs 的 napi 桥接开销,整体表现基本一致:
| 场景 | Node.js | Rush-FS | 比率 |
|---|---|---|---|
stat(单文件) |
1.45 µs | 1.77 µs | 1.2x |
readFile 小文件(Buffer) |
8.86 µs | 9.46 µs | 1.1x |
writeFile 小文件(string) |
74 µs | 66 µs | 0.9x |
writeFile 小文件(Buffer) |
115 µs | 103 µs | 0.9x |
appendFile |
30 µs | 27 µs | 0.9x |
极轻量级的内置调用,napi 开销占比较大:
| 场景 | Node.js | Rush-FS | 说明 |
|---|---|---|---|
existsSync(已存在文件) |
444 ns | 1.34 µs | Node.js 内部有 fast path |
accessSync F_OK |
456 ns | 1.46 µs | 同上——napi 开销占主导 |
writeFile 4 MB string |
2.93 ms | 5.69 ms | 大字符串跨 napi 桥传输 |
Rush-FS 在文件系统遍历类操作中使用多线程并行:
| API | 并行库 | concurrency 选项 |
默认值 |
|---|---|---|---|
readdir(递归) |
jwalk | ✅ | auto |
glob |
ignore | ✅ | 4 |
rm(递归) |
rayon | ✅ | 1 |
cp(递归) |
rayon | ✅ | 1 |
单文件操作(stat、readFile、writeFile、chmod 等)是原子系统调用,不适用并行化。
Rush-FS 在递归/批量文件系统操作上表现卓越(readdir、glob、rm、cp),Rust 的并行遍历器带来 2–70 倍加速。单文件操作与 Node.js 基本持平。napi 桥接带来固定约 0.3 µs 的每次调用开销,仅在亚微秒级操作(如 existsSync)中有感知。
cp 基准详情(Apple Silicon,release 构建):
| 场景 | Node.js | Rush-FS 1 线程 | Rush-FS 4 线程 | Rush-FS 8 线程 |
|---|---|---|---|---|
| 平铺目录(500 文件) | 86.45 ms | 61.56 ms | 32.88 ms | 36.67 ms |
| 树形目录(宽度=4,深度=3,~84 节点) | 23.80 ms | 16.94 ms | 10.62 ms | 9.76 ms |
| 树形目录(宽度=3,深度=5,~363 节点) | 108.73 ms | 75.39 ms | 46.88 ms | 46.18 ms |
cp 的最优并发数在 Apple Silicon 上为 4 线程——超过后受 I/O 带宽限制,收益趋于平稳。
Node.js 原生的 fs 在底层串行执行,且需要较多内存将系统对象与字符串解析为 JS 形式:
graph TD
A["JS: readdir"] -->|Call| B("Node.js C++ Binding")
B -->|Submit Task| C{"Libuv Thread Pool"}
subgraph "Native Layer (Serial)"
C -->|"Syscall: getdents"| D[OS Kernel]
D -->|"Return File List"| C
C -->|"Process Paths"| C
end
C -->|"Results Ready"| E("V8 Main Thread")
subgraph "V8 Interaction (Heavy)"
E -->|"Create JS String 1"| F[V8 Heap]
E -->|"String 2"| F
E -->|"String N..."| F
F -->|"GC Pressure Rising"| F
end
E -->|"Return Array"| G["JS Callback/Promise"]
Rust 实现则把重计算放在 Rust 侧,减少与 V8 的交互与 GC 压力:
graph TD
A["JS: readdir"] -->|"N-API Call"| B("Rust Wrapper")
B -->|"Spawn Thread/Task"| C{"Rust Thread Pool"}
subgraph "Rust 'Black Box'"
C -->|"Rayon: Parallel work"| D[OS Kernel]
D -->|"Syscall: getdents"| C
C -->|"Store as Rust Vec<String>"| H[Rust Heap]
H -->|"No V8 Interaction yet"| H
end
C -->|"All Done"| I("Convert to JS")
subgraph "N-API Bridge"
I -->|"Batch Create JS Array"| J[V8 Heap]
end
J -->|Return| K["JS Result"]
我们正在逐个重写 fs 的 API。
图例
- ✅:完全支持
- 🚧:部分支持 / 开发中
- ✨:rush-fs 的新增能力
- ❌:暂未支持
- Node.js 参数:
path: string; // ✅ options?: { encoding?: string; // 🚧(默认 'utf8';'buffer' 暂不支持) withFileTypes?: boolean; // ✅ recursive?: boolean; // ✅ concurrency?: number; // ✨ };
- 返回类型:
string[] | { name: string, // ✅ parentPath: string, // ✅ isDir: boolean // ✅ }[]
- Node.js 参数:
path: string; // ✅ options?: { encoding?: string; // ✅ (utf8, ascii, latin1, base64, base64url, hex) flag?: string; // ✅ (r, r+, w+, a+ 等) };
- 返回类型:
string | Buffer
- Node.js 参数:
path: string; // ✅ data: string | Buffer; // ✅ options?: { encoding?: string; // ✅ (utf8, ascii, latin1, base64, base64url, hex) mode?: number; // ✅ flag?: string; // ✅ (w, wx, a, ax) };
- Node.js 参数:
path: string; // ✅ data: string | Buffer; // ✅ options?: { encoding?: string; // ✅ (utf8, ascii, latin1, base64, base64url, hex) mode?: number; // ✅ flag?: string; // ✅ };
- Node.js 参数:
src: string; // ✅ dest: string; // ✅ mode?: number; // ✅ (COPYFILE_EXCL)
- Node.js 参数(Node 16.7+):
src: string; // ✅ dest: string; // ✅ options?: { recursive?: boolean; // ✅ force?: boolean; // ✅(默认 true) errorOnExist?: boolean; // ✅ preserveTimestamps?: boolean; // ✅ dereference?: boolean; // ✅ verbatimSymlinks?: boolean; // ✅ concurrency?: number; // ✨ };
- Node.js 参数:
path: string; // ✅ options?: { recursive?: boolean; // ✅ mode?: number; // ✅ };
- 返回类型:
string | undefined(recursive 模式下返回首个创建的路径)
- Node.js 参数:
path: string; // ✅ options?: { force?: boolean; // ✅ maxRetries?: number; // ✅ retryDelay?: number; // ✅(默认 100ms) recursive?: boolean; // ✅ concurrency?: number; // ✨ };
- Node.js 参数:
path: string // ✅
- Node.js 参数:
path: string // ✅
- 返回类型:
Stats- 数值字段:
dev,mode,nlink,uid,gid,rdev,blksize,ino,size,blocks,atimeMs,mtimeMs,ctimeMs,birthtimeMs - Date 字段:
atime,mtime,ctime,birthtime→Date对象 ✅ - 方法:
isFile(),isDirectory(),isSymbolicLink(), ...
- 数值字段:
- 错误区分:
ENOENTvsEACCES✅
- Node.js 参数:
path: string // ✅
- 返回类型:
Stats
- 状态:❌
- Node.js 参数:
path: string; // ✅ mode?: number; // ✅ (F_OK, R_OK, W_OK, X_OK)
- Node.js 参数:
path: string // ✅
- 返回类型:
boolean
- 状态:❌
- 状态:❌
- 状态:❌
- Node.js 参数:
path: string // ✅
- Node.js 参数:
oldPath: string // ✅ newPath: string // ✅
- Node.js 参数:
path: string // ✅
- 返回类型:
string
- Node.js 参数:
path: string // ✅
- 返回类型:
string
- Node.js 参数:
path: string // ✅ mode: number // ✅
- Node.js 参数:
path: string // ✅ uid: number // ✅ gid: number // ✅
- Node.js 参数:
path: string // ✅ atime: number // ✅ mtime: number // ✅
- Node.js 参数:
path: string; // ✅ len?: number; // ✅
- Node.js 参数:
pattern: string; // ✅ options?: { cwd?: string; // ✅ withFileTypes?: boolean; // ✅ exclude?: string[]; // ✅ concurrency?: number; // ✨ gitIgnore?: boolean; // ✨ };
- Node.js 参数:
target: string // ✅ path: string // ✅ type?: 'file' | 'dir' | 'junction' // ✅(仅 Windows 有效,Unix 忽略)
- Node.js 参数:
existingPath: string // ✅ newPath: string // ✅
- Node.js 参数:
prefix: string // ✅
- 返回类型:
string - 使用系统随机源(Unix:
/dev/urandom,Windows:BCryptGenRandom),最多重试 10 次 ✅
- 状态:❌
各版本变更见 CHANGELOG.md。发布 tag 列表见 GitHub Releases。
参阅 CONTRIBUTING-CN.md 获取完整开发指南:环境搭建、参考 Node.js 源码、编写 Rust 实现、测试与性能基准。
发布由 Release 工作流 完成:在 macOS(x64/arm64)、Windows、Linux 上构建原生二进制,并发布各平台包与主包到 npm。
- Secrets: 在仓库 Settings → Secrets and variables → Actions 中添加 NPM_TOKEN(npm Classic 或 Automation token,需具备 Publish 权限)。
- 发布: 在 Actions → Release → Run workflow 中手动运行(使用当前
main上的package.json版本),或先更新package.json与Cargo.toml中的版本号并推送到main,再创建并推送 tag:git tag v<版本号> && git push origin v<版本号>。 - 更新日志: 发布前或发布后更新 CHANGELOG.md(将
[Unreleased]下的条目移到新版本标题下并补充 compare 链接)。
工作流会自动注入 optionalDependencies 并发布所有包,无需在 package.json 中手动填写。
MIT