Skip to content

Latest commit

 

History

History
497 lines (375 loc) · 13.9 KB

File metadata and controls

497 lines (375 loc) · 13.9 KB

Rush-FS

English | 中文

Written in Rust NPM Version License Contributors

与 Node.js fs API 对齐,可无痛替换现有项目中的 fs;在海量文件操作场景下获得数倍于内置 fs 的性能,由 Rust 驱动。

安装

npm install rush-fs
# or
pnpm add rush-fs

安装 rush-fs 时,包管理器会通过 optionalDependencies 自动安装当前平台的本地绑定(例如 macOS ARM 上的 @rush-fs/rush-fs-darwin-arm64)。若未安装或出现「Cannot find native binding」:

  1. 删除 node_modules 和锁文件(package-lock.jsonpnpm-lock.yaml)后重新执行 pnpm install(或 npm i)。
  2. 或手动安装对应平台包:
    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 可复现。

Rush-FS 显著更快的场景

这些场景中 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

与 Node.js 持平的场景

单文件操作有约 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

Node.js 更快的场景

极轻量级的内置调用,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

单文件操作(statreadFilewriteFilechmod 等)是原子系统调用,不适用并行化。

核心结论

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"]
Loading

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"]
Loading

状态与路线图

我们正在逐个重写 fs 的 API。

图例

  • ✅:完全支持
  • 🚧:部分支持 / 开发中
  • ✨:rush-fs 的新增能力
  • ❌:暂未支持

readdir

  • Node.js 参数
    path: string; // ✅
    options?: {
      encoding?: string; // 🚧(默认 'utf8';'buffer' 暂不支持)
      withFileTypes?: boolean; // ✅
      recursive?: boolean; // ✅
      concurrency?: number; // ✨
    };
  • 返回类型
      string[]
      | {
        name: string, // ✅
        parentPath: string, // ✅
        isDir: boolean // ✅
      }[]

readFile

  • Node.js 参数
    path: string; // ✅
    options?: {
      encoding?: string; // ✅ (utf8, ascii, latin1, base64, base64url, hex)
      flag?: string; // ✅ (r, r+, w+, a+ 等)
    };
  • 返回类型string | Buffer

writeFile

  • Node.js 参数
    path: string; // ✅
    data: string | Buffer; // ✅
    options?: {
      encoding?: string; // ✅ (utf8, ascii, latin1, base64, base64url, hex)
      mode?: number; // ✅
      flag?: string; // ✅ (w, wx, a, ax)
    };

appendFile

  • Node.js 参数
    path: string; // ✅
    data: string | Buffer; // ✅
    options?: {
      encoding?: string; // ✅ (utf8, ascii, latin1, base64, base64url, hex)
      mode?: number; // ✅
      flag?: string; // ✅
    };

copyFile

  • Node.js 参数
    src: string; // ✅
    dest: string; // ✅
    mode?: number; // ✅ (COPYFILE_EXCL)

cp

  • 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; // ✨
    };

mkdir

  • Node.js 参数
    path: string; // ✅
    options?: {
      recursive?: boolean; // ✅
      mode?: number; // ✅
    };
  • 返回类型string | undefined(recursive 模式下返回首个创建的路径)

rm

  • Node.js 参数
    path: string; // ✅
    options?: {
      force?: boolean; // ✅
      maxRetries?: number; // ✅
      retryDelay?: number; // ✅(默认 100ms)
      recursive?: boolean; // ✅
      concurrency?: number; // ✨
    };

rmdir

  • Node.js 参数
    path: string // ✅

stat

  • Node.js 参数
    path: string // ✅
  • 返回类型Stats
    • 数值字段:dev, mode, nlink, uid, gid, rdev, blksize, ino, size, blocks, atimeMs, mtimeMs, ctimeMs, birthtimeMs
    • Date 字段atime, mtime, ctime, birthtimeDate 对象 ✅
    • 方法:isFile(), isDirectory(), isSymbolicLink(), ...
  • 错误区分ENOENT vs EACCES

lstat

  • Node.js 参数
    path: string // ✅
  • 返回类型Stats

fstat

  • 状态:❌

access

  • Node.js 参数
    path: string; // ✅
    mode?: number; // ✅ (F_OK, R_OK, W_OK, X_OK)

exists

  • Node.js 参数
    path: string // ✅
  • 返回类型boolean

open

  • 状态:❌

opendir

  • 状态:❌

close

  • 状态:❌

unlink

  • Node.js 参数
    path: string // ✅

rename

  • Node.js 参数
    oldPath: string // ✅
    newPath: string // ✅

readlink

  • Node.js 参数
    path: string // ✅
  • 返回类型string

realpath

  • Node.js 参数
    path: string // ✅
  • 返回类型string

chmod

  • Node.js 参数
    path: string // ✅
    mode: number // ✅

chown

  • Node.js 参数
    path: string // ✅
    uid: number // ✅
    gid: number // ✅

utimes

  • Node.js 参数
    path: string // ✅
    atime: number // ✅
    mtime: number // ✅

truncate

  • Node.js 参数
    path: string; // ✅
    len?: number; // ✅

glob

  • Node.js 参数
    pattern: string; // ✅
    options?: {
      cwd?: string; // ✅
      withFileTypes?: boolean; // ✅
      exclude?: string[]; // ✅
      concurrency?: number; // ✨
      gitIgnore?: boolean; // ✨
    };

symlink

  • Node.js 参数
    target: string // ✅
    path: string // ✅
    type?: 'file' | 'dir' | 'junction' // ✅(仅 Windows 有效,Unix 忽略)

link

  • Node.js 参数
    existingPath: string // ✅
    newPath: string // ✅

mkdtemp

  • Node.js 参数
    prefix: string // ✅
  • 返回类型string
  • 使用系统随机源(Unix: /dev/urandom,Windows: BCryptGenRandom),最多重试 10 次 ✅

watch

  • 状态:❌

更新日志

各版本变更见 CHANGELOG.md。发布 tag 列表见 GitHub Releases

贡献

参阅 CONTRIBUTING-CN.md 获取完整开发指南:环境搭建、参考 Node.js 源码、编写 Rust 实现、测试与性能基准。

发布(维护者专用)

发布由 Release 工作流 完成:在 macOS(x64/arm64)、Windows、Linux 上构建原生二进制,并发布各平台包与主包到 npm。

  1. Secrets: 在仓库 Settings → Secrets and variables → Actions 中添加 NPM_TOKEN(npm Classic 或 Automation token,需具备 Publish 权限)。
  2. 发布:Actions → Release → Run workflow 中手动运行(使用当前 main 上的 package.json 版本),或先更新 package.jsonCargo.toml 中的版本号并推送到 main,再创建并推送 tag:git tag v<版本号> && git push origin v<版本号>
  3. 更新日志: 发布前或发布后更新 CHANGELOG.md(将 [Unreleased] 下的条目移到新版本标题下并补充 compare 链接)。

工作流会自动注入 optionalDependencies 并发布所有包,无需在 package.json 中手动填写。

许可证

MIT