npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

fs-syn

v1.1.0

Published

Lightweight purely synchronous file operation utility for Node.js, built on native fs module with zero dependencies

Downloads

11

Readme

fs-syn

一个轻量级的纯同步 Node.js 文件操作工具,基于原生 fs 模块构建,零依赖。它简化了常见的文件和目录操作,并提供清晰的 TypeScript 类型提示,专为需要同步操作的场景设计。

功能特性

  • 🚫 纯同步操作:所有操作都是同步的,无需 async/await 或 Promise
  • 📦 零依赖:完全基于 Node.js 原生 fs 模块,无外部库依赖
  • 🔧 核心操作:涵盖文件 I/O、目录管理、路径匹配和哈希计算
  • TypeScript 支持:完整的类型定义,提供类型安全和 IDE 自动补全
  • 🖥️ 跨平台:在 Windows、macOS 和 Linux 上统一处理路径分隔符

安装

安装核心包

npm install fs-syn --save

TypeScript 类型支持(Node.js < 16)

对于 Node.js 16 以下版本,安装 @types/node 以获得完整的 TypeScript 类型提示:

npm install @types/node --save-dev

快速开始

import fs from 'fs-syn';

try {
  // 1. 创建目录(如需要会递归创建)
  fs.mkdir('./example');
  
  // 2. 写入文件内容(自动创建父目录)
  fs.write('./example/hello.txt', 'Hello World!');
  
  // 3. 读取文件内容
  const content = fs.read('./example/hello.txt');
  console.log(content); // 输出: Hello World!
  
  // 4. 清理(删除目录及其内容)
  fs.remove('./example');
} catch (err: any) {
  console.error('操作失败:', err.message);
}

完整 API 示例

所有方法在失败时都会抛出描述性错误(如文件不存在、权限问题等)。使用 try/catch 块来优雅地处理错误。

1. 文件操作

write(file: string, content: string | Buffer, options?: fs.WriteFileOptions)

将内容写入文件(如果目录不存在会自动创建)。

// 将字符串写入日志文件
fs.write('./logs/access.log', '用户在上午10:00登录');

// 将二进制数据(Buffer)写入文件
const binaryData = Buffer.from('原始二进制内容', 'utf8');
fs.write('./data/bin.dat', binaryData);

// 使用自定义选项写入(例如追加模式、特定编码)
fs.write('./config.ini', 'debug=true', { 
  encoding: 'utf8', 
  flag: 'a' // 追加到文件而不是覆盖
});

read(file: string, options?: { encoding?: BufferEncoding | null; flag?: string })

从文件读取内容(默认返回字符串,如果 encoding: null 则返回 Buffer)。

// 以 UTF-8 字符串形式读取文件(默认)
const text = fs.read('./notes.txt');
console.log('文件内容:', text);

// 以 Buffer 形式读取文件(适用于二进制文件如图片)
const imageBuffer = fs.read('./assets/logo.png', { encoding: null });

// 使用自定义标志读取(例如只读模式)
const lockedContent = fs.read('./protected.txt', { flag: 'r' });

readJSON<T>(file: string, options?: { encoding?: BufferEncoding | null; flag?: string })

直接读取并解析 JSON 文件(为 TypeScript 用户返回类型化对象)。

// 为类型安全定义 TypeScript 接口
interface AppConfig {
  port: number;
  debug: boolean;
  theme: string;
}

// 读取并解析带类型提示的 JSON
const config = fs.readJSON<AppConfig>('./config.json');
console.log('服务器端口:', config.port); // 类型检查: config.port 是 number

// 使用自定义编码读取(例如 ASCII)
const legacyData = fs.readJSON('./legacy-data.json', { encoding: 'ascii' });

writeJSON(file: string, data: unknown, spaces?: number)

将 JavaScript 对象写入 JSON 文件并自动格式化。

// 将用户对象写入 JSON(默认 2 空格缩进)
const user = {
  id: 123,
  name: '张三',
  roles: ['编辑者', '管理员']
};
fs.writeJSON('./users/zhang.json', user);

// 使用 4 空格缩进写入以提高可读性
fs.writeJSON('./config.json', { theme: 'dark', timeout: 5000 }, 4);

appendFile(file: string, content: string | Buffer, options?: fs.WriteFileOptions)

向现有文件追加内容(如果文件不存在则创建)。

// 追加带时间戳的日志条目
const logEntry = `[INFO] 服务器在 ${new Date().toISOString()} 重启\n`;
fs.appendFile('./app.log', logEntry);

// 向日志文件追加二进制数据
const sensorData = Buffer.from([0x01, 0x0A, 0xFF]); // 示例传感器值
fs.appendFile('./sensor/log.dat', sensorData);

copy(from: string, to: string, options?: { force?: boolean; preserveTimestamps?: boolean })

递归复制文件或目录(支持覆盖和保留文件时间戳)。

// 复制单个文件到新位置
fs.copy('./docs/manual.pdf', './public/downloads/user-manual.pdf');

// 复制整个目录(如果目标存在则强制覆盖)
fs.copy('./src', './src-backup', { force: true });

// 复制时保留时间戳(保留原始创建/修改时间)
fs.copy('./archive/2023', './backup/2023', { preserveTimestamps: true });

move(from: string, to: string, options?: { force?: boolean })

移动或重命名文件/目录(对于跨设备移动会降级为"复制+删除")。

// 重命名文件(同一目录内)
fs.move('./tmp/report-draft.txt', './reports/final-report.txt');

// 将目录移动到新的父文件夹
fs.move('./old-docs', './archive/2024-docs');

// 强制覆盖现有的目标文件
fs.move('./new-config.json', './current-config.json', { force: true });

remove(path: string)

递归删除文件或目录(相当于 Unix 上的 rm -rf 或 Windows 上的 rmdir /s /q)。

// 删除单个文件
fs.remove('./temp.log');

// 删除整个目录(及其所有内容)
fs.remove('./node_modules');

// 删除嵌套子目录
fs.remove('./dist/assets/old-images');

hashFile(file: string, algorithm?: string)

计算文件的加密哈希(支持 Node.js 的 crypto 模块提供的所有算法)。

// 计算 SHA-256 哈希(默认算法)
const sha256Hash = fs.hashFile('./downloads/installer.exe');
console.log('SHA-256:', sha256Hash);

// 计算 MD5 哈希(常用于文件完整性检查)
const md5Hash = fs.hashFile('./docs.pdf', 'md5');
console.log('MD5:', md5Hash);

// 计算 SHA-1 哈希(遗留用途)
const sha1Hash = fs.hashFile('./legacy-data.bin', 'sha1');

2. 目录操作

mkdir(dirPath: string)

创建目录(以及所有父目录)递归(相当于 Unix 上的 mkdir -p)。

// 创建单个目录
fs.mkdir('./new-folder');

// 创建嵌套目录(无需先创建父目录)
fs.mkdir('./src/assets/images/icons');

// 创建名称包含空格的目录
fs.mkdir('./docs/user guides');

readDir(dir: string, options?: { withFileTypes?: boolean })

读取目录内容(默认返回文件名数组,或返回 fs.Dirent 对象以获取类型信息)。

// 将目录内容读取为文件名数组
const files = fs.readDir('./src');
console.log('src 中的文件:', files); // 例如: ["index.ts", "utils/"]

// 读取包含文件类型信息的内容(区分文件和目录)
const entries = fs.readDir('./docs', { withFileTypes: true });
entries.forEach(entry => {
  if (entry.isDirectory()) {
    console.log(`目录: ${entry.name}`);
  } else {
    console.log(`文件: ${entry.name}`);
  }
});

emptyDir(dir: string)

清空目录内容但不删除目录本身

// 清空日志目录(保留 ./logs 文件夹)
fs.emptyDir('./logs');

// 清空缓存目录(保留 ./node_modules/.cache 文件夹)
fs.emptyDir('./node_modules/.cache');

isDir(path: string)

检查给定路径是否指向现有目录。

// 检查目录是否存在
if (fs.isDir('./src')) {
  console.log('./src 是一个有效目录');
}

// 动态路径检查
const targetPath = './unknown-folder';
console.log(`${targetPath} ${fs.isDir(targetPath) ? '是' : '不是'}目录`);

isFile(path: string)

检查给定路径是否指向现有文件。

// 检查文件是否存在且不是目录
if (fs.isFile('./package.json')) {
  console.log('package.json 存在(且是文件)');
}

// 条件性处理文件
const dataFile = './data.csv';
if (fs.isFile(dataFile)) {
  const content = fs.read(dataFile);
  // 处理文件内容...
}

exists(...paths: string[])

检查路径是否存在(支持多个路径段来构建完整路径)。

// 检查单个路径是否存在
if (fs.exists('./config.json')) {
  console.log('找到配置文件');
}

// 从多个段构建并检查路径(避免手动连接)
if (fs.exists('src', 'utils', 'helpers.ts')) {
  console.log('helpers.ts 存在于 src/utils/ 中');
}

3. 路径工具

[expand(patterns: string | string[], options?: ExpandOptions)]

使用类似 glob 的模式匹配路径(支持 * 进行单级匹配和 ** 进行多级匹配)。

ExpandOptions 接口:

  • cwd?: string: 搜索的工作目录(默认: process.cwd())。
  • dot?: boolean: 包含隐藏文件/目录(以 . 开头,默认: false)。
  • onlyFiles?: boolean: 仅返回文件(排除目录,默认: false)。
  • onlyDirs?: boolean: 仅返回目录(排除文件,默认: false)。
// 查找项目中所有 TypeScript 文件(递归)
const tsFiles = fs.expand('**/*.ts');
console.log('TypeScript 文件:', tsFiles); // 例如: ["src/index.ts", "tests/utils.ts"]

// 查找 ./src 目录中的所有 JSON 文件(非递归)
const srcJsonFiles = fs.expand('./src/*.json');

// 查找匹配 "docs-*" 的目录(例如 docs-v1, docs-v2)
const docDirs = fs.expand('docs-*', { onlyDirs: true });

// 在 ./config 目录中包含隐藏文件(例如 .env, .gitignore)
const hiddenConfigFiles = fs.expand('*', { cwd: './config', dot: true, onlyFiles: true });

isPathAbsolute(path: string)

检查路径是否为绝对路径(跨平台工作)。

// POSIX 系统(macOS/Linux)
console.log(fs.isPathAbsolute('/usr/local/bin')); // true
console.log(fs.isPathAbsolute('./relative/path')); // false

// Windows 系统
console.log(fs.isPathAbsolute('C:\\Windows\\System32')); // true
console.log(fs.isPathAbsolute('..\\relative\\path')); // false

[doesPathContain(ancestor: string, ...paths: string[])]

检查一个或多个"子"路径是否包含在"祖先"目录中(防止路径遍历问题)。

// 检查多个文件是否在 ./src 目录内
const allInSrc = fs.doesPathContain(
  './src', 
  './src/index.ts', 
  './src/utils/helpers.ts'
);
console.log('所有文件都在 src 中:', allInSrc); // true

// 检查日志是否包含在 /var/log 中(POSIX)
const logsInVar = fs.doesPathContain('/var/log', '/var/log/app.log', '/var/log/nginx');

createSymlink(target: string, linkPath: string, options?: { type?: 'file' | 'dir' | 'junction' })

创建指向文件或目录的符号链接(symlink)。

// 创建指向文件的符号链接(指向 ./dist/index.js)
fs.createSymlink('./dist/index.js', './current.js', { type: 'file' });

// 创建指向目录的符号链接(指向 ./docs/latest)
fs.createSymlink('./docs/latest', './docs/current', { type: 'dir' });

// 创建连接点(仅 Windows)用于网络共享
if (process.platform === 'win32') {
  fs.createSymlink('\\\\server\\data', './network-data', { type: 'junction' });
}

realpath(path: string)

将符号链接解析为其真实的物理路径(跟随嵌套符号链接)。

// 解析符号链接到其目标
const realFilePath = fs.realpath('./current.js');
console.log('符号链接指向:', realFilePath); // 例如: "./dist/index.js"

// 解析嵌套符号链接
const resolvedPath = fs.realpath('./deep/link/to/file');

错误处理

所有方法都会抛出带有可读消息的 Error 对象。使用 try/catch 处理特定的失败场景:

try {
  // 尝试读取不存在的文件
  fs.read('./nonexistent-file.txt');
} catch (err: any) {
  console.error('错误名称:', err.name); // "Error"
  console.error('错误消息:', err.message); // "文件不存在: ./nonexistent-file.txt"
  console.error('受影响路径:', err.path); // (可选) 导致错误的路径
}

常见错误场景

| 错误消息 | 原因 | 解决方法 | |----------|------|----------| | 文件不存在: [path] | 目标文件不存在 | 验证路径,或先创建文件 | | 目录不存在: [path] | 目标目录不存在 | 使用 fs.mkdir 先创建目录 | | 目标已存在(设 force: true 覆盖): [path] | copy/move 的目标路径已存在 | 在选项中添加 force: true 以覆盖 | | 路径是目录: [path] | 你试图将目录当作文件使用(例如 fs.read('./src')) | 验证路径指向文件而不是目录 |

重要说明

1. 同步操作会阻塞事件循环

同步 I/O 会阻塞 Node.js 的事件循环直到操作完成。仅在以下场景使用 fs-syn

  • 简单脚本、CLI 工具或构建过程
  • 配置文件加载(在启动时,在服务请求之前)
  • 小到中等大小的文件处理(避免导致长时间延迟的大文件)

避免在高性能服务器应用(如 Express/Koa API)中使用 fs-syn,在这些场景中非阻塞 I/O 至关重要。

2. remove 操作具有破坏性

fs.remove 方法会递归删除文件和目录(类似 rm -rf)。始终仔细检查路径以防止意外数据丢失(例如,永远不要使用 fs.remove('/')fs.remove('./'))。

3. 模式匹配限制

expand 方法支持基本的 glob 模式(***),但不如专用库如 glob 强大(例如,不支持否定模式如 !exclude-me.ts)。对于复杂匹配,考虑将 fs-syn 与轻量级 glob 库结合使用。

兼容性

  • Node.js: 14.0.0 或更高版本(支持完整的 fs 模块同步 API)
  • TypeScript: 4.5 或更高版本(用于类型定义)
  • 操作系统: Windows, macOS, Linux

许可证

MIT 许可证。详见 LICENSE