鸿蒙 PC Node 开发:引入ohos-signpost自动签名工具,安装三方库fs-extra 超全教程,解决原生 fs 文件操作所有痛点
欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
AtomGit 仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_node_vue_ts
本文完整梳理了ARM64架构鸿蒙PC(HarmonyOS/OpenHarmony6.1及以上),基于CodeArts IDE搭建Vite+Vue前端项目的全流程与疑难解决方案。项目实操中,Vite启动会出现rolldown原生模块权限拒绝报错,根源是鸿蒙系统拦截未签名二进制文件,最终解决方案为引入ohos-signpost自动签名工具,配置npm后置钩子,在依赖安装完成后自动为所有.node文件添加系统合法签名,消除权限校验拦截,最终实现CodeArts IDE内Vue+TS项目正常启动、调试。
如果想在鸿蒙上面进行PC适配,可以结合这篇文章:OpenHarmony 鸿蒙 PC + CodeArts IDE 前端 Vite+Vue 完整开发环境搭建指南
一、fs-extra 库完整介绍
1. 库作用
fs-extra 是 Node.js 文件系统增强库,内置覆盖原生 fs 全部能力并拓展大量实用API,弥补原生 fs 缺陷:
- 原生 fs 仅支持回调,fs-extra 同步/异步 Promise 双支持,不用嵌套回调地狱;
- 原生无法直接递归创建多层目录、递归删除文件夹、复制目录;fs-extra 原生支持;
- 新增读写JSON、移动文件、确保目录存在、空目录清理、文件遍历、路径批量操作;
- 自动兼容 Windows / macOS / 鸿蒙OpenHarmony Node 环境,处理路径分隔符;
- 零额外依赖,完全兼容原生 fs 所有方法,可直接替换
require('fs')。
2. 安装命令
# npm 安装
npm i fs-extra
# ts项目额外安装类型定义
npm i -D @types/fs-extra

3. 完整无报错大篇幅示例代码(覆盖90%高频业务场景)
新建 fsDemo.js(CommonJS)
// 引入fs-extra,同时引入原生path辅助路径处理
const fs = require('fs-extra');
const path = require('path');
// 定义测试目录、文件路径常量
const BASE_DIR = path.resolve(__dirname, './fsTestWorkspace');
const DIR_MULTI = path.join(BASE_DIR, 'level1', 'level2', 'level3'); // 多层嵌套目录
const FILE_TXT = path.join(BASE_DIR, 'demo.txt');
const FILE_JSON = path.join(BASE_DIR, 'config.json');
const COPY_SRC = path.join(BASE_DIR, 'sourceFolder');
const COPY_DEST = path.join(BASE_DIR, 'targetFolder');
const MOVE_SRC = path.join(BASE_DIR, 'moveSrc.txt');
const MOVE_DEST = path.join(BASE_DIR, 'moveDest', 'result.txt');
const EMPTY_DIR = path.join(BASE_DIR, 'emptyDir');
/**
* 统一封装异步主流程,全部使用async/await,无回调嵌套
*/
async function runAllFsDemo() {
console.log('===== 1. 递归创建多层目录 ensureDir / mkdirs =====');
// 递归创建多级文件夹,不存在则创建,存在不会报错
await fs.ensureDir(DIR_MULTI);
console.log('多层目录创建完成:', DIR_MULTI);
// 同步版本
fs.ensureDirSync(EMPTY_DIR);
console.log('空目录同步创建完成:', EMPTY_DIR);
console.log('\n===== 2. 写入文本文件 + 追加文本 =====');
// 覆盖写入文件,不存在自动创建上级目录
await fs.writeFile(FILE_TXT, '第一行初始文本\n', 'utf8');
console.log('文件初始写入完成');
// 追加内容,不覆盖原有数据
await fs.appendFile(FILE_TXT, '第二行追加内容\n第三行测试换行', 'utf8');
console.log('文件追加写入完成');
console.log('\n===== 3. 读取文本文件内容 =====');
const txtContent = await fs.readFile(FILE_TXT, 'utf8');
console.log('读取txt文件内容:\n', txtContent);
console.log('\n===== 4. JSON文件读写、修改配置 =====');
// 写入JSON对象,自动格式化缩进
const jsonData = {
appName: '鸿蒙前端Demo',
version: '1.0.0',
port: 5173,
feature: ['fs操作', '文件复制', '目录遍历'],
enable: true
};
await fs.writeJSON(FILE_JSON, jsonData, { spaces: 2, encoding: 'utf8' });
console.log('JSON配置文件写入成功');
// 读取JSON
const readJson = await fs.readJSON(FILE_JSON);
console.log('读取JSON原始数据:', readJson);
// 修改字段并重写
readJson.version = '2.1.0';
readJson.port = 8080;
await fs.writeJSON(FILE_JSON, readJson, { spaces: 2 });
console.log('JSON配置更新版本号完成');
console.log('\n===== 5. 文件/目录存在性判断 exists / pathExists =====');
const isTxtExist = await fs.pathExists(FILE_TXT);
const fakePath = path.join(BASE_DIR, 'notExist.txt');
const isFakeExist = await fs.pathExists(fakePath);
console.log(`txt文件是否存在:${isTxtExist},不存在文件:${isFakeExist}`);
console.log('\n===== 6. 复制文件夹(递归复制全部子文件子目录) =====');
// 先创建源目录+测试文件
await fs.ensureDir(COPY_SRC);
await fs.writeFile(path.join(COPY_SRC, 'a.txt'), '源文件夹文件A');
await fs.writeFile(path.join(COPY_SRC, 'sub', 'b.txt'), '源子目录文件B');
// 递归复制整个文件夹到目标,目标不存在自动创建
await fs.copy(COPY_SRC, COPY_DEST, { overwrite: true });
console.log('完整目录递归复制完成');
// 复制单个文件
await fs.copyFile(FILE_TXT, path.join(BASE_DIR, 'copy-single.txt'));
console.log('单个文件复制完成');
console.log('\n===== 7. 文件/目录移动 rename / move =====');
// 生成待移动源文件
await fs.writeFile(MOVE_SRC, '待移动的文件内容', 'utf8');
// move自动创建目标上级目录,原生fs.rename做不到
await fs.move(MOVE_SRC, MOVE_DEST, { overwrite: true });
console.log('文件移动完成');
console.log('\n===== 8. 遍历目录 readdir + stat 获取文件详情 =====');
const dirEntries = await fs.readdir(BASE_DIR, { withFileTypes: true });
console.log('根目录全部资源列表:');
for (const entry of dirEntries) {
const fullPath = path.join(BASE_DIR, entry.name);
const stat = await fs.stat(fullPath);
console.log(`名称:${entry.name} | 类型:${entry.isDirectory() ? '文件夹' : '文件'} | 大小:${stat.size} 字节`);
}
console.log('\n===== 9. 清空目录(保留文件夹,删除内部所有内容) =====');
await fs.emptyDir(EMPTY_DIR);
console.log('空目录已清空内部全部文件');
console.log('\n===== 10. 删除文件、递归删除文件夹 =====');
// 删除单个文件
const delSingleFile = path.join(BASE_DIR, 'copy-single.txt');
await fs.unlink(delSingleFile);
console.log('单个文件删除完成');
// 递归删除整个文件夹(无论内部多少文件层级)
await fs.remove(COPY_DEST);
console.log('目标文件夹递归删除完成');
console.log('\n===== 11. 获取文件详细状态 lstat =====');
const fileStat = await fs.lstat(FILE_TXT);
console.log(`文件创建时间:${fileStat.birthtime.toLocaleString()}`);
console.log(`文件修改时间:${fileStat.mtime.toLocaleString()}`);
console.log(`是否是普通文件:${fileStat.isFile()}`);
console.log('\n===== 全部文件系统操作执行完毕!');
}
// 捕获全局异常,避免程序崩溃
runAllFsDemo().catch(err => {
console.error('文件操作执行异常:', err);
});

一、头部导入与路径常量定义
// 引入fs-extra,同时引入原生path辅助路径处理
const fs = require('fs-extra');
const path = require('path');
fs-extra:Node增强文件系统库,完全覆盖原生fs能力,新增递归建目录、复制文件夹、JSON读写等便捷API,全部支持Promise异步写法;path:Node内置路径工具,用来拼接、解析、转换文件/文件夹绝对路径,兼容Windows、鸿蒙、macOS、Linux不同系统路径分隔符。
// 定义测试目录、文件路径常量
const BASE_DIR = path.resolve(__dirname, './fsTestWorkspace');
const DIR_MULTI = path.join(BASE_DIR, 'level1', 'level2', 'level3'); // 多层嵌套目录
const FILE_TXT = path.join(BASE_DIR, 'demo.txt');
const FILE_JSON = path.join(BASE_DIR, 'config.json');
const COPY_SRC = path.join(BASE_DIR, 'sourceFolder');
const COPY_DEST = path.join(BASE_DIR, 'targetFolder');
const MOVE_SRC = path.join(BASE_DIR, 'moveSrc.txt');
const MOVE_DEST = path.join(BASE_DIR, 'moveDest', 'result.txt');
const EMPTY_DIR = path.join(BASE_DIR, 'emptyDir');
path.resolve(__dirname, xxx):把相对路径转为绝对路径,避免运行时找不到文件;__dirname代表当前js文件所在文件夹;path.join():安全拼接多层路径,自动适配不同系统斜杠/反斜杠;- 统一把所有测试文件、文件夹路径抽成常量,方便统一修改、复用,避免代码里重复写路径字符串。
二、主异步函数入口 runAllFsDemo
async function runAllFsDemo() {
使用 async/await 异步语法,替代传统回调函数,消除回调地狱,代码从上到下顺序执行,逻辑清晰,所有fs-extra异步API都返回Promise,可以直接await等待操作完成。
1. 递归创建多层目录 ensureDir / ensureDirSync
console.log('===== 1. 递归创建多层目录 ensureDir / mkdirs =====');
// 递归创建多级文件夹,不存在则创建,存在不会报错
await fs.ensureDir(DIR_MULTI);
console.log('多层目录创建完成:', DIR_MULTI);
// 同步版本
fs.ensureDirSync(EMPTY_DIR);
console.log('空目录同步创建完成:', EMPTY_DIR);
原生Node fs.mkdir 只能创建单层文件夹,多层嵌套会直接报错;fs.ensureDir 会自动递归创建所有上级文件夹,目录已存在也不会抛出错误,是项目初始化最常用API;
ensureDir:异步版本,搭配await;ensureDirSync:同步阻塞版本,不用await,适合启动脚本、初始化流程。
2. 覆盖写入文件 + 追加文本内容
console.log('\n===== 2. 写入文本文件 + 追加文本 =====');
// 覆盖写入文件,不存在自动创建上级目录
await fs.writeFile(FILE_TXT, '第一行初始文本\n', 'utf8');
console.log('文件初始写入完成');
// 追加内容,不覆盖原有数据
await fs.appendFile(FILE_TXT, '第二行追加内容\n第三行测试换行', 'utf8');
console.log('文件追加写入完成');
fs.writeFile:覆盖式写入文件,如果文件不存在自动新建,上层目录不存在会自动创建;第三个参数指定编码utf8;fs.appendFile:追加写入,不会清空原有内容,日志记录、持续写入文本场景专用。
3. 读取本地文本文件
console.log('\n===== 3. 读取文本文件内容 =====');
const txtContent = await fs.readFile(FILE_TXT, 'utf8');
console.log('读取txt文件内容:\n', txtContent);
readFile 读取文件二进制内容,指定utf8直接返回字符串,不指定则返回Buffer二进制缓冲区。
4. JSON配置文件读写、修改
console.log('\n===== 4. JSON文件读写、修改配置 =====');
// 写入JSON对象,自动格式化缩进
const jsonData = {
appName: '鸿蒙前端Demo',
version: '1.0.0',
port: 5173,
feature: ['fs操作', '文件复制', '目录遍历'],
enable: true
};
await fs.writeJSON(FILE_JSON, jsonData, { spaces: 2, encoding: 'utf8' });
console.log('JSON配置文件写入成功');
// 读取JSON
const readJson = await fs.readJSON(FILE_JSON);
console.log('读取JSON原始数据:', readJson);
// 修改字段并重写
readJson.version = '2.1.0';
readJson.port = 8080;
await fs.writeJSON(FILE_JSON, readJson, { spaces: 2 });
console.log('JSON配置更新版本号完成');
fs-extra独有的JSON专属API,原生fs需要手动 JSON.stringify / JSON.parse:
writeJSON:直接传入JS对象,自动序列化写入文件,spaces:2代表格式化缩进2空格,配置文件可读性更高;readJSON:读取文件并自动解析为JS对象,省去手动转换步骤;
适用场景:项目配置、本地缓存、持久化参数存储。
5. 判断文件/文件夹是否存在 pathExists
console.log('\n===== 5. 文件/目录存在性判断 exists / pathExists =====');
const isTxtExist = await fs.pathExists(FILE_TXT);
const fakePath = path.join(BASE_DIR, 'notExist.txt');
const isFakeExist = await fs.pathExists(fakePath);
console.log(`txt文件是否存在:${isTxtExist},不存在文件:${isFakeExist}`);
原生fs.exists官方已废弃,pathExists是安全替代方案,返回布尔值,不管目标是文件还是文件夹都能判断。
6. 递归复制文件夹、复制单个文件
console.log('\n===== 6. 复制文件夹(递归复制全部子文件子目录) =====');
// 先创建源目录+测试文件
await fs.ensureDir(COPY_SRC);
await fs.writeFile(path.join(COPY_SRC, 'a.txt'), '源文件夹文件A');
await fs.writeFile(path.join(COPY_SRC, 'sub', 'b.txt'), '源子目录文件B');
// 递归复制整个文件夹到目标,目标不存在自动创建
await fs.copy(COPY_SRC, COPY_DEST, { overwrite: true });
console.log('完整目录递归复制完成');
// 复制单个文件
await fs.copyFile(FILE_TXT, path.join(BASE_DIR, 'copy-single.txt'));
console.log('单个文件复制完成');
原生fs无法一键复制带多层子目录的文件夹,fs.copy 会递归复制所有子文件、子文件夹:
{overwrite:true}:允许覆盖已存在的目标文件;copyFile:仅复制单个独立文件,不处理文件夹。
7. 文件移动 move(跨目录/跨分区)
console.log('\n===== 7. 文件/目录移动 rename / move =====');
// 生成待移动源文件
await fs.writeFile(MOVE_SRC, '待移动的文件内容', 'utf8');
// move自动创建目标上级目录,原生fs.rename做不到
await fs.move(MOVE_SRC, MOVE_DEST, { overwrite: true });
console.log('文件移动完成');
原生fs.rename存在缺陷:无法跨磁盘/跨分区移动文件,且目标上级目录不存在时直接报错;fs.move 自动补齐目标目录,支持跨分区迁移文件,兼容所有系统。
8. 遍历文件夹,获取所有文件、文件夹信息
console.log('\n===== 8. 遍历目录 readdir + stat 获取文件详情 =====');
const dirEntries = await fs.readdir(BASE_DIR, { withFileTypes: true });
console.log('根目录全部资源列表:');
for (const entry of dirEntries) {
const fullPath = path.join(BASE_DIR, entry.name);
const stat = await fs.stat(fullPath);
console.log(`名称:${entry.name} | 类型:${entry.isDirectory() ? '文件夹' : '文件'} | 大小:${stat.size} 字节`);
}
readdir({withFileTypes:true}):遍历目录,直接区分条目是文件还是文件夹,不用额外判断;fs.stat():获取文件元信息:字节大小、创建时间、修改时间、是否目录/文件;
业务场景:批量扫描资源、过滤指定后缀文件、统计文件夹大小。
9. 清空目录 emptyDir
console.log('\n===== 9. 清空目录(保留文件夹,删除内部所有内容) =====');
await fs.emptyDir(EMPTY_DIR);
console.log('空目录已清空内部全部文件');
只删除文件夹内部所有子文件、子目录,保留外层文件夹本身,适合缓存目录、临时文件夹定期清理。
10. 删除文件、递归删除非空文件夹 remove
console.log('\n===== 10. 删除文件、递归删除文件夹 =====');
// 删除单个文件
const delSingleFile = path.join(BASE_DIR, 'copy-single.txt');
await fs.unlink(delSingleFile);
console.log('单个文件删除完成');
// 递归删除整个文件夹(无论内部多少文件层级)
await fs.remove(COPY_DEST);
console.log('目标文件夹递归删除完成');
unlink:删除单个文件;remove:核心高频API,原生fs无法直接删除带内容的文件夹,该方法会递归删除文件夹内所有内容后删除目录,常用于打包清理dist、删除临时资源。
11. 获取文件完整元信息 lstat
console.log('\n===== 11. 获取文件详细状态 lstat =====');
const fileStat = await fs.lstat(FILE_TXT);
console.log(`文件创建时间:${fileStat.birthtime.toLocaleString()}`);
console.log(`文件修改时间:${fileStat.mtime.toLocaleString()}`);
console.log(`是否是普通文件:${fileStat.isFile()}`);
lstat 获取文件完整状态对象,包含创建时间、修改时间、文件大小、文件类型,可用于日志溯源、文件过期清理。
三、全局异常捕获
// 捕获全局异常,避免程序崩溃
runAllFsDemo().catch(err => {
console.error('文件操作执行异常:', err);
});
所有异步文件操作如果报错(权限不足、路径非法、文件被占用等),都会进入catch打印完整错误堆栈,防止Node进程直接闪退,方便调试定位问题。
补充关键业务说明
- 鸿蒙PC前端项目适配
fs-extra是纯JS库,无.node二进制原生模块,不需要ohos-signpost签名,直接node运行不会报permission denied;常用于Vite脚本清理打包目录、复制静态资源、读写项目配置JSON。 - 同步/异步选择规范
- 业务脚本、接口逻辑:优先
await异步API,不阻塞事件循环; - 程序启动初始化、简单一次性脚本:可用
xxxSync同步方法简化代码。
- 核心优势对比原生fs
- 原生fs:仅单层目录创建、无JSON工具、不能复制/删除非空文件夹、rename跨盘失效、全回调写法;
- fs-extra:递归目录、一键复制/移动/删除、JSON读写、Promise异步、自动创建父级目录,大幅减少自定义工具函数。
运行前置命令
# 安装依赖
npm i fs-extra
# 执行代码
node fsDemo.js
运行后会在代码同级目录自动生成fsTestWorkspace测试文件夹,按代码顺序完成全部文件操作并打印每一步日志。
TS 版本补充(fsDemo.ts,无类型报错)
import fs from 'fs-extra';
import path from 'path';
const BASE_DIR = path.resolve(__dirname, './fsTestWorkspace');
const FILE_TXT = path.join(BASE_DIR, 'demo.txt');
async function tsFsTest() {
await fs.ensureDir(BASE_DIR);
await fs.writeFile(FILE_TXT, 'TS fs-extra测试文本', 'utf8');
const content: string = await fs.readFile(FILE_TXT, 'utf8');
console.log('TS读取文件:', content);
}
tsFsTest().catch(e => console.error(e));
二、核心API分段说明
- 目录创建
ensureDir / ensureDirSync
原生fs.mkdir只能创建单层,多层目录直接报错;fs-extra 自动递归创建所有父级文件夹,开发最常用。 - 文件读写
writeFile / appendFile / readFile
支持Promise异步,自动补全不存在的上级目录,appendFile安全追加文本。 - JSON专用
writeJSON / readJSON
自动序列化/反序列化JSON,支持格式化缩进,不用手动 JSON.stringify / JSON.parse。 - 路径存在判断
pathExists
替代原生 fs.exists(官方废弃API),安全判断文件/文件夹是否存在。 - 目录复制
copy
递归完整复制文件夹、嵌套子目录,支持覆盖、过滤文件,原生fs无对应能力。 - 移动
move
跨分区/跨目录移动文件,自动创建目标文件夹,原生rename无法跨盘。 - 清空目录
emptyDir
保留文件夹本体,删除内部所有文件、子文件夹,缓存清理场景高频使用。 - 递归删除
remove
一键删除非空文件夹,不用递归遍历删除内部文件,清理构建产物超方便。 - 同步方法全部带 Sync 后缀
ensureDirSync/writeJsonSync,同步阻塞代码,适合初始化脚本。
三、业务开发高频使用场景
- Vite/Vue/鸿蒙前端项目:清理dist打包目录、复制静态资源;
- 配置文件持久化读写(JSON配置、环境缓存);
- 脚本批量处理文件:遍历目录、批量复制迁移资源;
- 日志文件追加写入、日志文件夹自动创建;
- 本地缓存目录自动初始化、定期清空缓存;
- 导出文件、模板目录拷贝生成新项目模板。
四、鸿蒙PC Node环境运行步骤
- 安装依赖
npm i fs-extra
npm i -D @types/fs-extra
- 执行代码
node fsDemo.js
鸿蒙Node环境兼容fs-extra,无需额外签名,纯JS库无.node二进制,不会出现permission denied报错。
五、代码运行输出简述
程序会自动在当前目录生成 fsTestWorkspace 测试文件夹,依次执行:创建多层目录、读写文本、读写JSON、复制文件夹、移动文件、遍历打印文件信息、清空目录、删除资源,全程无报错,控制台打印每一步执行日志。
更多推荐



所有评论(0)