rimraf vs remove vs del vs fs-extra
Node.js 文件删除方案深度对比
rimrafremovedelfs-extra类似的npm包:

Node.js 文件删除方案深度对比

delfs-extraremoverimraf 都是 Node.js 生态中用于删除文件或目录的工具,但它们的定位和维护状态截然不同。rimraf 是底层递归删除的标准实现,del 是基于 rimraf 的现代 Promise 封装,fs-extra 提供了增强版的文件系统 API(包含删除功能),而 remove 包已过时不再推荐使用。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
rimraf134,084,0395,842262 kB81 个月前BlueOak-1.0.0
remove124,69911-314 年前MIT
del01,34412.7 kB176 个月前MIT
fs-extra09,62257.7 kB1223 天前MIT

Node.js 文件删除方案深度对比

在 Node.js 开发中,清理构建产物、重置临时目录或删除旧日志是常见需求。虽然原生 fs 模块提供了 unlinkrmdir,但处理递归删除和通配符匹配非常麻烦。delfs-extraremoverimraf 试图解决这些问题,但它们的实现方式和适用场景有很大区别。

🛠️ API 设计风格:回调 vs Promise vs Async

不同的包采用了不同的异步处理模式,这直接影响代码的可读性和维护成本。

del 基于 Promise 设计,天然支持 async/await,符合现代 JavaScript 标准。

// del: 原生 Promise 支持
import { deleteAsync } from 'del';

await deleteAsync(['dist/*.js', 'dist/*.css']);
// 返回已删除路径的数组

fs-extra 同时提供回调和 Promise 风格,API 与原生 fs 模块高度一致。

// fs-extra: 支持 Promise 和 Callback
import fs from 'fs-extra';

// Promise 风格
await fs.remove('dist');

// 回调风格
fs.remove('dist', err => { /*...*/ });

rimraf 早期版本主要基于回调,v5+ 版本转向 ESM 和 Promise,但底层逻辑依然保持简洁。

// rimraf: 现代版本支持 Promise
import { rimraf } from 'rimraf';

await rimraf('dist');
// 专注于递归删除,不返回复杂结果

remove 包采用老旧的回调模式,不支持 Promise,需要手动封装才能在现代代码中使用。

// remove: 仅支持回调 (已过时)
import remove from 'remove';

remove('dist', function (err) {
  if (err) throw err;
  console.log('deleted');
});

🌍 通配符支持:原生 vs 扩展

处理批量文件删除时,是否支持 Glob 模式(如 *.js)是关键差异点。

del 内置了 globby,直接支持通配符匹配,无需额外配置。

// del: 内置 Glob 支持
await deleteAsync(['tmp/**/*.log', '!tmp/important.log']);
// 支持排除模式,非常灵活

rimraf 支持简单的通配符,但主要设计用于删除单个路径或简单模式。

// rimraf: 基础 Glob 支持
await rimraf('tmp/*.log');
// 复杂模式可能需要配合 glob 包使用

fs-extra 本身不支持通配符,需要配合 glob 包手动实现。

// fs-extra: 需配合 glob 使用
import glob from 'glob';
import fs from 'fs-extra';

const files = await glob('tmp/*.log');
await Promise.all(files.map(f => fs.remove(f)));

remove 不支持通配符,只能删除指定路径的文件或目录。

// remove: 无 Glob 支持
remove('tmp/file.log', cb);
// 批量删除需要自己写循环

⚠️ 维护状态与风险提示

选择库时,维护状态直接关系到项目的长期安全和技术债务。

remove 包已经多年未更新,社区普遍认为它已过时。它的功能完全被 fs-extraremove 方法覆盖。在新项目中引入它是一个明显的技术债务信号。

rimraf 是行业标准,被无数核心工具依赖。虽然 API 简单,但非常稳定。注意 v5 版本之后仅支持 ESM 模块。

del 保持活跃更新,专门针对前端构建场景优化。它处理了 Windows 路径锁定等边缘情况,比直接调用 rimraf 更省心。

fs-extra 是文件系统操作的瑞士军刀,维护非常积极。如果你的项目已经用它来做 copymove,那么用它来做 remove 是自然的选择。

📦 实际场景推荐

场景 1:构建脚本清理

你需要在构建前清理 dist 目录,并删除所有临时日志。

  • 最佳选择del
  • 原因:通配符支持好,API 简洁,专门为此场景设计。
// 构建脚本示例
import { deleteAsync } from 'del';
await deleteAsync(['dist', 'tmp/*.log']);

场景 2:通用文件工具库

你正在编写一个内部工具库,需要统一的文件操作 API。

  • 最佳选择fs-extra
  • 原因:API 统一,减少依赖数量,文档完善。
// 工具库示例
import fs from 'fs-extra';
await fs.remove('/tmp/cache');

场景 3:底层 CLI 工具

你正在开发一个需要极致兼容性的命令行工具。

  • 最佳选择rimraf
  • 原因:依赖最少,社区信任度高,行为可预测。
// CLI 工具示例
import { rimraf } from 'rimraf';
await rimraf(process.argv[2]);

场景 4:遗留项目维护

你正在维护一个 5 年前的老项目。

  • ⚠️ 注意:如果看到 remove 包,建议计划迁移到 fs-extra
  • 原因:避免未来 Node.js 版本升级导致的兼容性问题。

📊 总结对比表

特性delfs-extrarimrafremove
异步模式PromisePromise / CallbackPromise / CallbackCallback
Glob 支持✅ 内置❌ 需配合 glob⚠️ 基础支持❌ 无
维护状态🟢 活跃🟢 活跃🟢 活跃🔴 过时
主要用途构建清理通用文件操作底层递归删除已淘汰
模块类型ESMCJS / ESMESM (v5+)CJS

💡 核心建议

del 是前端构建任务的首选 🧹 — 它解决了通配符和 Promise 的痛点,让清理代码变得非常干净。

fs-extra 是通用文件操作的最佳拍伴 🤝 — 如果你已经在使用它,就不要引入额外的删除库,保持一致性更重要。

rimraf 是底层实现的基石 🪨 — 当你需要最纯粹的递归删除功能,或者作为库的依赖时,它是最可靠的选择。

remove 包应该被移除 🗑️ — 它不再符合现代开发标准,迁移成本很低,但收益很高。

最终建议:在现代前端工程中,优先使用 del 处理构建清理,使用 fs-extra 处理应用内的文件操作。避免引入过时的 remove 包,除非你正在维护无法修改的遗留代码。

如何选择: rimraf vs remove vs del vs fs-extra

  • rimraf:

    选择 rimraf 如果你需要最底层的递归删除能力,或者在编写 CLI 工具。它是许多其他工具(包括 del)的底层依赖,兼容性极强,但 API 相对基础。

  • remove:

    不要在新技术栈中使用 remove 包。它已多年未更新,缺乏 Promise 支持,且功能已被 fs-extrarimraf 完全覆盖,存在维护风险。

  • del:

    选择 del 如果你正在编写构建脚本或需要处理通配符模式(如 dist/*.js)。它基于 Promise,支持异步操作,并且默认忽略不存在的文件,非常适合现代前端工作流。

  • fs-extra:

    选择 fs-extra 如果你的项目已经依赖它来处理其他文件操作(如拷贝、移动)。它的 remove 方法 API 一致性好,支持同步和异步,且维护活跃。

rimraf的README

The UNIX command rm -rf for node in a cross-platform implementation.

Install with npm install rimraf.

[!CAUTION]

Please Be Safe, this tool deletes and moves stuff, by design

The intended purpose of this tool is to remove files and directories from the filesystem, aggressively and recursively removing all items that it can find under a given target.

It goes without saying that you must not pass untrusted input to this function or CLI tool, just as you would not to the rm(1) command or the unlink(2) function. It is very challenging to guarantee that any user input will be safe to remove recursively in this way.

Furthermore, note that if you allow untrusted parties to provide arguments to the rimraf command line tool, they may also specify the --tmp=<dir> folder used by the --impl=move-remove strategy, which can move files to an arbitrary place on disk.

Because the intended purpose of this tool is the permanent destruction of filesystem entries, any security reports that rely on untrusted input being passed to the function or command line tool will be rejected.

It is your responsibility as a user to never pass untrusted user input to this module, or your system can be destroyed or compromised.

Major Changes

v5 to v6

  • Require node 20 or >=22
  • Add --version to CLI

v4 to v5

  • There is no default export anymore. Import the functions directly using, e.g., import { rimrafSync } from 'rimraf'.

v3 to v4

  • The function returns a Promise instead of taking a callback.
  • Globbing requires the --glob CLI option or glob option property to be set. (Removed in 4.0 and 4.1, opt-in support added in 4.2.)
  • Functions take arrays of paths, as well as a single path.
  • Native implementation used by default when available, except on Windows, where this implementation is faster and more reliable.
  • New implementation on Windows, falling back to "move then remove" strategy when exponential backoff for EBUSY fails to resolve the situation.
  • Simplified implementation on POSIX, since the Windows affordances are not necessary there.
  • As of 4.3, return/resolve value is boolean instead of undefined.

API

Hybrid module, load either with import or require().

// 'rimraf' export is the one you probably want, but other
// strategies exported as well.
import { rimraf, rimrafSync, native, nativeSync } from 'rimraf'
// or
const { rimraf, rimrafSync, native, nativeSync } = require('rimraf')

All removal functions return a boolean indicating that all entries were successfully removed.

The only case in which this will not return true is if something was omitted from the removal via a filter option.

rimraf(f, [opts]) -> Promise

This first parameter is a path or array of paths. The second argument is an options object.

Options:

  • preserveRoot: If set to boolean false, then allow the recursive removal of the root directory. Otherwise, this is not allowed.

  • tmp: Windows only. Temp folder to place files and folders for the "move then remove" fallback. Must be on the same physical device as the path being deleted. Defaults to os.tmpdir() when that is on the same drive letter as the path being deleted, or ${drive}:\temp if present, or ${drive}:\ if not.

  • maxRetries: Windows and Native only. Maximum number of retry attempts in case of EBUSY, EMFILE, and ENFILE errors. Default 10 for Windows implementation, 0 for Native implementation.

  • backoff: Windows only. Rate of exponential backoff for async removal in case of EBUSY, EMFILE, and ENFILE errors. Should be a number greater than 1. Default 1.2

  • maxBackoff: Windows only. Maximum total backoff time in ms to attempt asynchronous retries in case of EBUSY, EMFILE, and ENFILE errors. Default 200. With the default 1.2 backoff rate, this results in 14 retries, with the final retry being delayed 33ms.

  • retryDelay: Native only. Time to wait between retries, using linear backoff. Default 100.

  • signal Pass in an AbortSignal to cancel the directory removal. This is useful when removing large folder structures, if you'd like to limit the time spent.

    Using a signal option prevents the use of Node's built-in fs.rm because that implementation does not support abort signals.

  • glob Boolean flag to treat path as glob pattern, or an object specifying glob options.

  • filter Method that returns a boolean indicating whether that path should be deleted. With async rimraf methods, this may return a Promise that resolves to a boolean. (Since Promises are truthy, returning a Promise from a sync filter is the same as just not filtering anything.)

    The first argument to the filter is the path string. The second argument is either a Dirent or Stats object for that path. (The first path explored will be a Stats, the rest will be Dirent.)

    If a filter method is provided, it will only remove entries if the filter returns (or resolves to) a truthy value. Omitting a directory will still allow its children to be removed, unless they are also filtered out, but any parents of a filtered entry will not be removed, since the directory will not be empty in that case.

    Using a filter method prevents the use of Node's built-in fs.rm because that implementation does not support filtering.

Any other options are provided to the native Node.js fs.rm implementation when that is used.

This will attempt to choose the best implementation, based on the Node.js version and process.platform. To force a specific implementation, use one of the other functions provided.

rimraf.sync(f, [opts])
rimraf.rimrafSync(f, [opts])

Synchronous form of rimraf()

Note that, unlike many file system operations, the synchronous form will typically be significantly slower than the async form, because recursive deletion is extremely parallelizable.

rimraf.native(f, [opts])

Uses the built-in fs.rm implementation that Node.js provides. This is used by default on Node.js versions greater than or equal to 14.14.0.

rimraf.native.sync(f, [opts])
rimraf.nativeSync(f, [opts])

Synchronous form of rimraf.native

rimraf.manual(f, [opts])

Use the JavaScript implementation appropriate for your operating system.

rimraf.manual.sync(f, [opts])
rimraf.manualSync(f, opts)

Synchronous form of rimraf.manual()

rimraf.windows(f, [opts])

JavaScript implementation of file removal appropriate for Windows platforms. Works around unlink and rmdir not being atomic operations, and EPERM when deleting files with certain permission modes.

First deletes all non-directory files within the tree, and then removes all directories, which should ideally be empty by that time. When an ENOTEMPTY is raised in the second pass, falls back to the rimraf.moveRemove strategy as needed.

rimraf.windows.sync(path, [opts])
rimraf.windowsSync(path, [opts])

Synchronous form of rimraf.windows()

rimraf.moveRemove(path, [opts])

Moves all files and folders to the parent directory of path with a temporary filename prior to attempting to remove them.

Note that, in cases where the operation fails, this may leave files lying around in the parent directory with names like .file-basename.txt.0.123412341. Until the Windows kernel provides a way to perform atomic unlink and rmdir operations, this is, unfortunately, unavoidable.

To move files to a different temporary directory other than the parent, provide opts.tmp. Note that this must be on the same physical device as the folder being deleted, or else the operation will fail.

This is the slowest strategy, but most reliable on Windows platforms. Used as a last-ditch fallback by rimraf.windows().

rimraf.moveRemove.sync(path, [opts])
rimraf.moveRemoveSync(path, [opts])

Synchronous form of rimraf.moveRemove()

Command Line Interface

rimraf version 6.0.1

Usage: rimraf <path> [<path> ...]
Deletes all files and folders at "path", recursively.

Options:
  --                   Treat all subsequent arguments as paths
  -h --help            Display this usage info
  --version            Display version
  --preserve-root      Do not remove '/' recursively (default)
  --no-preserve-root   Do not treat '/' specially
  -G --no-glob         Treat arguments as literal paths, not globs (default)
  -g --glob            Treat arguments as glob patterns
  -v --verbose         Be verbose when deleting files, showing them as
                       they are removed. Not compatible with --impl=native
  -V --no-verbose      Be silent when deleting files, showing nothing as
                       they are removed (default)
  -i --interactive     Ask for confirmation before deleting anything
                       Not compatible with --impl=native
  -I --no-interactive  Do not ask for confirmation before deleting

  --impl=<type>        Specify the implementation to use:
                       rimraf: choose the best option (default)
                       native: the built-in implementation in Node.js
                       manual: the platform-specific JS implementation
                       posix: the Posix JS implementation
                       windows: the Windows JS implementation (falls back to
                                move-remove on ENOTEMPTY)
                       move-remove: a slow reliable Windows fallback

Implementation-specific options:
  --tmp=<path>        Temp file folder for 'move-remove' implementation
  --max-retries=<n>   maxRetries for 'native' and 'windows' implementations
  --retry-delay=<n>   retryDelay for 'native' implementation, default 100
  --backoff=<n>       Exponential backoff factor for retries (default: 1.2)

mkdirp

If you need to create a directory recursively, check out mkdirp.