HarmonyOS Node.js + oh-package.json5:三方库依赖分层错配预检【鸿蒙心迹】
项目准备交付时,某个动画组件在开发机上显示正常,并不意味着正式包一定携带了它所需的依赖。这里最容易误会的一点,是把“本机安装成功”当成“运行依赖声明正确”。在 HarmonyOS 工程中,模块级 oh-package.json5 同时容纳 dependencies 和 devDependencies,两者面向的构建阶段不同。真正值得做的,不是再写一遍安装命令,而是在提交前问清楚:项目里哪些库被业务运行代码使用,它们是不是落在生产依赖层。本文围绕 DepBoundary(编号 DEP-2202)做一个可独立执行的 Node.js 检查器设计,不把自定义门禁说成系统编译器能力。

一、安装正常,为什么发布前仍要看依赖层
华为开发者文档把 dependencies 定义为参与编译或运行的生产依赖;devDependencies 主要面向开发和测试。一个模块中声明的包,会因为构建方式、模块引用关系和打包路径不同而表现出不同的可用性。尤其在多人协作工程中,某位同事可能曾在本地将库安装过,另一位只看能否启动模拟器就结束检查。如果一开始分类错误,后续排查会被“我的电脑上没问题”这句话不断打断。
本例把风险缩成三条可解释的记录:@ohos/axios 属于运行期使用,检查结果是 PASS;@ohos/lottie 被运行页面导入,却写进开发依赖,检查结果为 WRONG_SCOPE;@ohos/hypium 仅供测试,放在开发依赖,结果为 PASS。因此验收看板应展示 2/3 PASS 和 BLOCKED。这只是专门构造的演示清单,不是在声称某个真实应用存在这样的错误,也不承诺示例里的版本可以直接安装。
工程边界要先划清:这里检查模块级依赖字段的归属,不通过猜测包名决定运行或测试性质;判断依据是项目维护的“用途清单”。若业务导入从运行阶段移到测试阶段,开发者需要同步更新用途清单。对于 HSP 的动态加载以及项目根目录的覆盖规则,还要另做分析,不能用这几行脚本代替完整的依赖树和构建验证。
二、先建立可信的输入,而不是扫描整个工程猜结论
oh-package.json5 使用 JSON5 表达方式。标准 Node.js 的 JSON.parse 不能可靠解析其中的注释、单引号和尾随逗号;因此审计脚本应明确依赖一个 JSON5 解析包,而不是简单用正则删注释后强行解析。这样做还可以保证解析失败时返回清晰错误,避免把格式异常当成“未声明依赖”。脚本只读配置,不改写它,也不直接解析 oh-package-lock.json5 的内部结构。
以下是输入读取的第一段代码。先在脚本工具目录的 Node.js 环境安装 json5,再保存为 tools/dep-scope-audit.mjs。entry/oh-package.json5 路径按实际模块调整。JSON5 是开发侧使用的第三方解析工具,并非 HarmonyOS 的系统 API。所有 I/O 都使用异步 readFile;读取失败时抛出异常交给入口捕获,不静默生成空对象。
// tools/dep-scope-audit.mjs;工具环境先安装 npm 的 json5 包
import { readFile } from 'node:fs/promises';
import JSON5 from 'json5';
async function readModuleConfig(path) {
const raw = await readFile(path, 'utf8');
const parsed = JSON5.parse(raw);
if (!parsed || typeof parsed !== 'object') {
throw new Error('MODULE_CONFIG_INVALID');
}
return parsed;
}
这里的 path 由调用方传入,方便 CI 为不同模块分别运行。异常不应该被转换为 PASS:读取失败、JSON5 解析失败、字段不是对象都属于无法确认的状态,需要阻断内部准入。正式工程要把脚本作为开发工具维护在独立的 Node.js 工具目录,不能把 Node.js 的 fs 当成 ArkTS 手机上的文件访问接口。
三、用业务用途清单判断范围是否放错
用途清单可由代码评审维护,也可由成熟的静态分析工具产生,但在未验证分析器准确率前,最好先走显式维护的方案。示例使用三个条目,每条都有 name 和 usage。检查器不是比对具体版本大小,而是核验 runtime 必须属于 dependencies、test 可以属于 devDependencies 的项目规则。若同一包同时出现在两个分层,应明确记录为 AMBIGUOUS,不要任选一侧放行。
下面这段是核心判定。真实的 moduleConfig 从第一段读取;为了使演示可复现,列出的包名和状态在正文、图表、日志中保持一致。usage 是业务自定义枚举,WRONG_SCOPE 也是脚本的结果代码,不能误写为官方 ohpm 报错码。
const expectedUsage = [
{ name: '@ohos/axios', usage: 'runtime' },
{ name: '@ohos/lottie', usage: 'runtime' },
{ name: '@ohos/hypium', usage: 'test' }
];
function inspectScopes(moduleConfig, expected) {
const runtime = moduleConfig.dependencies ?? {};
const dev = moduleConfig.devDependencies ?? {};
return expected.map(({ name, usage }) => {
const inRuntime = Object.hasOwn(runtime, name);
const inDev = Object.hasOwn(dev, name);
if (inRuntime && inDev) return { name, status: 'AMBIGUOUS' };
if (!inRuntime && !inDev) return { name, status: 'MISSING' };
const correct = usage === 'runtime' ? inRuntime : inDev;
return { name, status: correct ? 'PASS' : 'WRONG_SCOPE' };
});
}
Object.hasOwn 检查直接声明,避免把原型链上的属性视作真实依赖。?? {} 仅对缺字段提供空映射,并不意味着“没有依赖就安全”;只要用途清单期望该包,状态仍会是 MISSING。test 在本示例表示测试专用,若某个包兼顾运行与测试,应该把用途定为 runtime 后继续检查,而不是允许开发层覆盖生产层。对于 dynamicDependencies 的 HSP 场景,后续应扩充枚举与规则,当前版本选择报告不覆盖。
生产依赖或开发依赖的文本本身无法证明包能否正确加载。范围表达式、目标 SDK 兼容性、三方库是否适配 HarmonyOS 仍需结合官方字段、ohpm 及实际构建来核验。本文的价值是在大量复杂检查前,把一个低成本且可解释的团队规则先跑起来。

四、让检查结果成为可追溯的构建门禁
审计结果需要能被本地开发者和 CI 同时理解。只在控制台输出红字是不够的:构建系统还要知道应不应该阻断,开发者还要知道是哪一个包、预期用途是什么。本例使用 process.exitCode 设置失败状态,使日志有完整输出机会;脚本自身不触碰用户项目的依赖声明,不执行安装或卸载动作,也不会替团队自动修改配置。
第三段代码构造本文固定的三条数据,并输出稳定格式。示例 JSON5 内容特意只表达声明归属,不宣称演示版本已经发布到公共仓库。调试时可先使用小型数据对象验证业务判定,再把输入切回第一段读取的真实文件。正式接入时不要把示例数据对象和真实读取结果混为一谈。
const demoConfig = {
dependencies: { '@ohos/axios': '*' },
devDependencies: { '@ohos/lottie': '*', '@ohos/hypium': '*' }
};
const rows = inspectScopes(demoConfig, expectedUsage);
const pass = rows.filter((row) => row.status === 'PASS').length;
const blocked = pass !== rows.length;
for (const row of rows) console.log(`DEP-2202 ${row.name} ${row.status}`);
console.log(`DEP-2202 ${pass}/${rows.length} PASS ${blocked ? 'BLOCKED' : 'READY'}`);
if (blocked) process.exitCode = 1;
在实际脚本里,应将 demoConfig 替换为 await readModuleConfig('entry/oh-package.json5'),并在入口统一 catch 中写出具体路径、解析错误类型以及非零退出状态。演示逻辑打印的三个结果是 @ohos/axios PASS、@ohos/lottie WRONG_SCOPE、@ohos/hypium PASS,汇总行为 DEP-2202 2/3 PASS BLOCKED。这些日志是按照自定义规则设计的预期输出,不是 HarmonyOS 编译器或 ohpm 的真实运行记录。
五、检查结果如何落到可视化页面
为了让产品、测试和研发都能看懂审计意义,可以给已有 HarmonyOS 项目做一个只读结果页:顶部显示“依赖分层预检”,中部显示三个包及状态,底部醒目标出 BLOCKED。页面数据来自受控的静态演示结果或经过授权的构建报告文件,不能因为手机页面能显示就声称设备在本地运行了 Node.js 脚本。
当业务同事看到第二行红色的 WRONG_SCOPE,就能将问题反馈给对应模块负责人:请确认 @ohos/lottie 是否确实在运行页面被引用。若确认成立,应修改模块级 oh-package.json5 的字段,再执行依赖安装与构建链验证;若发现它只被测试代码引用,则需要修改用途清单而不是盲目移动依赖。审计系统必须允许人工核对证据,不能将脚本判断当成不可质疑的官方审查结果。

六、回归验证应该专门制造反例
第一组用例是正常声明:把三个包放在与用途一致的层级,期望 3/3 PASS READY。第二组是当前演示:将运行库错误放入开发依赖,期望 2/3 PASS BLOCKED。第三组删除运行库声明,期望 MISSING。第四组让同一个包同时出现两个字段,期望 AMBIGUOUS。第五组将 JSON5 文件故意写坏,期望解析失败、退出码非零,不能让门禁悄悄退化成允许通过。
还要测试没有 devDependencies 节点、用途清单出现重名、模块路径输入错误以及配置里存在合法注释这几类边界。对重名期望项建议在读取后进行去重或视作审计输入错误,不能因此增加分母,让“通过率”看起来更高。对日志只记录包名与状态,避免在控制台打印路径中可能包含的开发人员账号、内部仓库令牌或敏感环境变量。
对于 CI,建议把本次结果当作早期阻断条件而不是最终放行凭证。通过这一关以后仍要进行 ohpm 安装、真实编译、包体检查以及必要的目标设备验证。不同模块需要分别生成报告,之后按模块合并,不应该拿一个 entry 模块的 PASS 覆盖其他模块的失败。
七、能够解决什么,不能解决什么
这个方法特别适合大型工程中“谁都能改依赖声明,但没有人维护依赖用途”的情况。它让一个容易在口头沟通中反复争论的问题变成了明确的输入契约:哪些库在运行时被调用,写在哪个字段,发现冲突由谁复核。检查器本身没有复杂算法,却能提前阻断类型明显不对的提交。
限制也很清楚:静态用途清单需要维护;动态 import、HSP 与本地 HAR 的交付关系更加复杂;脚本只看模块直接声明,不代替依赖树分析。华为文档还强调锁文件用于可复现安装,并不建议其他工具直接读取其内部内容,因此这里故意没有扫描锁文件。技术上可解释的门禁,比宣称“检测一切三方库风险”的脚本更有用。
参考资料:华为开发者官方《oh-package.json5》与《配置依赖项》。相关链接:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-hmos-oh-package-json5 ;https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-hmos-hvigor-dependencies 。
更多推荐


所有评论(0)