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
Maintainers
Readme
fs-syn
一个轻量级的纯同步 Node.js 文件操作工具,基于原生 fs 模块构建,零依赖。它简化了常见的文件和目录操作,并提供清晰的 TypeScript 类型提示,专为需要同步操作的场景设计。
功能特性
- 🚫 纯同步操作:所有操作都是同步的,无需 async/await 或 Promise
- 📦 零依赖:完全基于 Node.js 原生
fs模块,无外部库依赖 - 🔧 核心操作:涵盖文件 I/O、目录管理、路径匹配和哈希计算
- ✅ TypeScript 支持:完整的类型定义,提供类型安全和 IDE 自动补全
- 🖥️ 跨平台:在 Windows、macOS 和 Linux 上统一处理路径分隔符
安装
安装核心包
npm install fs-syn --saveTypeScript 类型支持(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。
