HarmonyOS 7 HAR + dynamic import:三方模块按需加载中的 runtimeOnly 白名单、包名对齐与构建失败定位【鸿蒙心迹】
这篇起因不是业务 bug,而是一条很容易把人带偏的运行错误。
我把远程配置能力封装成第三方 HAR,静态 import 一直正常。后来为了减少首屏初始化,把它改成变量形式的动态加载:
const moduleName = '@demo/remoteconfig'
结果真机直接报 Cannot find module。更麻烦的是,换一份 HAR 后又遇到 OhmUrl 解析失败;再检查依赖,发现 entry 里的依赖名和 HAR 自己的 name 还可能不一致。
我最后做了 DynamicHarGateLab,把这些问题拆成构建前检查。最终会话是 har_load_20261001_14,模块 @demo/remoteconfig,runtimeOnly=true,Dependency Match true,OhmUrl RESOLVED,Load Cost 86 ms。

一、dependencies 里有它,不等于变量 import 一定能找到它
HAR 已经写进 oh-package.json5,只能说明工程声明了生产依赖。变量表达式 import(moduleName) 还需要让构建系统知道,哪些包可能在运行时按名称加载。
我先对齐两份配置:
// entry/oh-package.json5
{
"dependencies": {
"@demo/remoteconfig":
"file:../libs/remoteconfig"
}
}
// build-profile.json5 中的核心配置意图
{
"buildOption": {
"runtimeOnly": {
"packages": [
"@demo/remoteconfig"
]
}
}
}
不同工程模板下,runtimeOnly 所在配置层级应以当前 schema 为准;这里真正不能错的是 packages 里的包名,它必须与工程依赖名一致。
runtimeOnly 不应该塞进所有三方依赖,只保留真正通过变量表达式动态加载的包。
二、第二个坑是“依赖名”和 HAR 内部 name 不是一回事
entry 里写 @demo/remoteconfig,本地 HAR 自己的 oh-package.json5 也必须使用匹配的包名。否则安装或构建日志里可能出现 dependency name mismatch,后面再看 Cannot find module 就会误判成动态 import 本身有问题。
我做了一个很小的 Guard:
class HarDependencyGuard {
static verify(
dependencyName: string,
harPackageName: string
): boolean {
if (dependencyName !== harPackageName) {
hilog.error(
0x0000,
'HarGuard',
`name mismatch: ${dependencyName} != ${harPackageName}`
)
return false
}
return true
}
}
这一步只负责把模糊的构建问题提前变成确定判断。Demo 最终 Dependency Match=true、Package Name=@demo/remoteconfig;如果这里失败,就不再继续 import。
三、动态加载本身反而是最简单的一段
真正执行时,我把阶段日志写清楚:
private async loadModule(): Promise<void> {
const moduleName =
'@demo/remoteconfig'
this.state = 'IMPORTING'
const start = Date.now()
try {
const ns: ESObject =
await import(moduleName)
if (!ns) {
throw new Error('empty module namespace')
}
this.loadCost =
Date.now() - start
this.state = 'RESOLVED'
} catch (err) {
this.buildErrors++
this.state = 'FAILED'
throw err
}
}
我关心的是错误发生在哪一层:依赖未声明、包名不一致、runtimeOnly 漏配、OhmUrl 解析失败,还是模块本身异常。HiLog 会先打印 dependency match,再打印 runtimeOnly match,最后才进入 import。
四、遇到 OhmUrl 错误时,我不再第一时间删缓存
构建日志里如果出现 Failed to resolve OhmUrl 或类似解析错误,最常见的做法是删除 oh_modules、重新 Sync。偶尔能解决,但也可能只是暂时掩盖真正的配置问题。
我的排查顺序固定成:
- 看代码里的 import 名称;
- 看 entry 的 dependencies;
- 看 HAR 内部
oh-package.json5的name; - 看 runtimeOnly packages;
- 最后才检查本地 HAR 路径、安装缓存和工具链。
如果是本地压缩包依赖,还要确认 CI 环境里对应文件真实存在。开发机上的相对路径能用,不代表流水线工作目录也一样。
五、我又加了一个构建前预检
不想等真机启动后才看到 Cannot find module,最有效的方法不是写更多运行时 catch,而是在构建前直接阻断错误配置。
function validateDynamicHar(
dependencyName: string,
packageName: string,
runtimeOnly: string[]
): string[] {
const errors: string[] = []
if (dependencyName !== packageName) {
errors.push('package-name-mismatch')
}
if (!runtimeOnly.includes(dependencyName)) {
errors.push('runtimeOnly-missing')
}
return errors
}
正式工程可以让脚本读取 JSON5,再挂到构建任务前。只要 errors.length > 0 就直接失败,而不是把问题留给真机。
本次 Demo 最终 Fallback Count=0、Build Errors=0,说明不需要任何兜底加载。
六、调试页只展示构建链路真正需要的字段
项目结构里保留 DynamicHarPage.ets、entry 的 build-profile.json5 和 oh-package.json5,以及 libs/remoteconfig/oh-package.json5、index.ets,再配一个 HarDependencyGuard。
HiLog 固定出现:dependency name matched: @demo/remoteconfig、runtimeOnly package found、import module=@demo/remoteconfig、OhmUrl resolved、loadCost=86ms、State: VALIDATING -> RESOLVED。

七、手机结果页用来确认“配置正确”而不是“碰巧能跑”
最终运行页数据是:
- Session:
har_load_20261001_14 - Module:
@demo/remoteconfig - State:
RESOLVED - runtimeOnly:
true - Dependency Match:
true - Package Name:
@demo/remoteconfig - OhmUrl:
RESOLVED - Load Cost:
86 ms - Fallback Count:
0 - Build Errors:
0 - Last Build:
14:41:26
这张结果页真正想证明的是:模块能被动态加载,是因为依赖名、HAR 包名、runtimeOnly 和最终解析结果都对齐,而不是某次 Sync 以后“突然好了”。

八、三方 HAR 还有几个容易被忽略的边界
HAR 很适合共享代码和资源,但动态加载策略应该跟模块边界一起设计。并不是所有库都适合为了“按需”而改成变量 import。
本地 file 依赖在 CI 最容易暴露路径问题,依赖升级后也要重新检查 HAR 的 name。另外 debug 和 release 的构建行为可能不同,正式上架前我会把预检放进 release 构建链路。
九、这次我把“Cannot find module”拆成五个检查点
最后的排查链路是:
DEPENDENCY → PACKAGE NAME → RUNTIMEONLY → OHMURL → IMPORT
以前看到 Cannot find module,我会先怀疑 import 语法。现在我更愿意从工程配置往前核对。对共享 HAR 或三方 SDK 来说,最理想的是错误的包名、白名单和路径根本进不了安装包。
更多推荐



所有评论(0)