如果你已经写过几个 HarmonyOS 应用,大概率迟早会遇到这个需求:想把某个通用能力沉淀成一个 HAR,发到 OpenHarmony 三方库中心仓,让更多项目直接ohpm install就能用。这个事听起来只是“打包上传”,真正动手才会发现,坑几乎全埋在 Module 配置里。
我第一次发布共享库时,就栽在oh-package.json5上。包的name少了个作用域前缀,校验直接打回;后来version写重复,又被中心仓拒收;最头疼的一次是main字段指向了./Index.ets,但产物里根本没这个文件。前前后后折腾了大半天,最后把所有配置项整理成一份可抄的清单,再发布就再也没有返工过。
这篇送给所有准备发三方库的同行,也算是《精通 HarmonyOS NEXT:鸿蒙 App 开发入门与项目化实战》的读者福利。我们会把“能跑的应用”这一层先放一边,专门聊聊把 Module 变成可发布共享库时,到底要配哪些项、为什么配这些项、以及最容易在哪个环节被中心仓的自动校验卡住。
1. 先搞清楚:你要发布的到底是哪种“共享库”
HarmonyOS 工程里的“共享库”是个容易混的概念,因为官方给出来的形态至少有三种:HAR、HSP、源码共享库。同样是新建一个 Module,选错类型,后面所有配置全是白费。
**HAR(Harmony Archive)**是静态共享包。你把代码、资源、配置打成.har文件,调用方在编译阶段把它合入自己的 HAP。对使用方来说,这个库最终会变成应用的一部分,不涉及运行时加载,也不需要关心动态更新。对发布方来说,中心仓对 HAR 的审核和解析支持也是最成熟的。
**HSP(Harmony Shared Package)**是动态共享包,也叫共享应用包。它和 HAR 的最大区别是运行时按需加载,适合多个模块共享同一份代码、按需分发更新的场景。听起来灵活,但代价是使用方的工程结构必须支持 HSP 的依赖方式。发一个三方库出去,要求每个使用方都得配合做动态拆包设计,这对大部分场景来说太沉重了。除非你明确知道自己的库要服务大型应用的动态拆包,否则别把 HSP 作为发到中心仓的默认形态。
源码共享库则是把源码直接发布到 ohpm 源上。中心仓里也能见到这类库,使用方安装后由自己的工程参与编译。好处是源码可读、调试方便;坏处是没有产物体积优势,而且如果源码里用了不规范的相对路径引用,使用方一编译就炸,排查成本比 HAR 高得多。
所以,从中心仓接受度和调用方友好度来看,发布 HAR 是最稳的选择。这也是下文所有配置的主线。
还有一句要提醒:工程的 SDK 版本标记。HarmonyOS NEXT 的 SDK 版本号写作5.0.0(12),括号里的 12 就是 API Level。如果你的库用到了 API 12 才加入的能力,建议在build-profile.json5里提前把版本定了:
"products": [ { "name": "default", "compileSdkVersion": "5.0.0(12)", "compatibleSdkVersion": "5.0.0(12)" } ]先把 SDK 版本对齐,后面构建时就不会突然冒出 “SDK version not compatible” 的问题。从中心仓生态看,API 12+ 也是当前三方库的主力区间。
2. Module 配置这盘棋:先把每个文件的职责认清楚
发布共享库,说穿了就是“把一个 Module 从应用工程里拆出来,单独构建、单独校验”。痛点通常不在代码本身,而是临时翻一堆配置文件、不知道应该动哪个。
先给一张职责地图,后面逐个讲:
| 文件 | 管什么 | 和发布的关系 |
|---|---|---|
oh-package.json5 | 包名、版本、入口、依赖 | 中心仓校验第一优先级 |
module.json5 | Module 类型、设备形态 | type 写错,产物直接不对 |
build-profile.json5 | SDK 版本、构建目标 | 决定兼容性 |
Index.ets | 对外导出 | main字段指向它 |
README.md/LICENSE/CHANGELOG.md | 面向使用方 | 人工审核时会看 |
2.1 新建一个可发布的 HAR Module
在 DevEco Studio 里,右键工程 -> New -> Module,选择Static Library。生成出来的模块名默认带lib前缀,比如libdemo。新建之后第一件事,打开模块内的module.json5,确认"type": "har"。
这一步看着简单,但坑就在这里:如果你新建时选了 Shared Library,module.json5里的 type 会是shared,后续构建出来的是.hsp而不是.har,拿到中心仓去校验,完全不是同一个物种。
另外,模块名最好只用小写字母和数字,别用驼峰、别用中文。模块名会出现在构建路径、产物文件名以及oh-package.json5的默认 name 里,名字越规整,后面越省事。
2.2 module.json5:别把 entry 的字段复制过来
很多人在配共享库时,第一反应是把 entry 模块的module.json5整段复制过来。这是后续报错的高频来源。作为应用模块,entry 里有abilities、pages、mainElement这些应用描述字段,它们对 HAR 模块毫无意义。强行保留,轻则构建告警,重则把产物打成一个不伦不类的“应用包”。
一个能用于发布的最小共享库module.json5长这样:
{ "module": { "name": "demo", "type": "har", "deviceTypes": [ "phone", "tablet", "2in1" ] } }deviceTypes按需填写。如果库是纯逻辑库,不涉及设备差异,写常见的三类即可。如果库里面用了特定设备才有的 API,建议别在 module 层卡设备,而是在文档里写明适配范围,把选择权留给使用方。
2.3 build-profile.json5 与 SDK 版本
模块自己的build-profile.json5只负责构建目标,不需要写签名信息:
{ "apiType": "stageMode", "buildOption": {}, "targets": [ { "name": "default" } ] }如果你的库需要自定义 ArkTS 编译选项,可以加在buildOption.arkOptions下。但发布三方库我建议保持默认,因为使用方用的是他们自己的编译环境,你在这边压掉一个告警,到对方那边可能变成编译错误。库的代码越“人畜无害”越好。
SDK 版本统一在工程根目录的build-profile.json5里配置。发布到 OpenHarmony 三方库中心仓时,runtimeOS 选HarmonyOS还是OpenHarmony,取决于目标设备。如果库主要在华为 1+8+N 设备上用,写"runtimeOS": "HarmonyOS"没问题;如果重点是开源鸿蒙的产线设备,可能需要改成"runtimeOS": "OpenHarmony"。这个没法一刀切,但先搞清楚自己的目标用户,能少走弯路。
3. oh-package.json5 才是中心仓校验的“身份证”
如果说module.json5管的是“这个模块在工程里怎么构建”,那么oh-package.json5管的就是“这个包在中心仓里叫什么、怎么被安装”。发布校验第一轮扫的就是它。
一个能过审的配置示例:
{ "modelVersion": "5.0.0", "name": "@demo/richtext", "version": "1.0.0", "description": "A lightweight rich text component for HarmonyOS", "main": "./Index.ets", "author": "demo", "license": "Apache-2.0", "keywords": [ "harmonyos", "ohos", "richtext" ], "repository": "https://gitee.com/demo/richtext", "dependencies": {}, "devDependencies": { "@ohos/hypium": "1.0.18" } }3.1 name 字段:唯一性和作用域前缀
name是中心仓对所有包做索引的主键,一个名字只能说只能用一次。如果你在中心仓里搜到同名库,哪怕不是你的,你也没法发第二份。所以强烈建议用@scope/name格式。scope 是你自己的账号或组织名,例如@demo/richtext。
命名规则上有几条硬性红线:
- 只允许小写字母、数字、
-、_、.; - 不能以
.、_开头; - 不能包含中文或空格;
- 长度建议控制在 50 个字符内,虽然中心仓给了 214 字符上限,但名字越长越难记。
不少人第一次发布失败,就是在本地开心地用MyLib或my_lib这种名字,到中心仓校验时被打回。
3.2 version 字段:语义化版本和重复发布
version必须严格遵循 semver 规范,也就是X.Y.Z。中心仓支持预发布版本,例如1.0.0-beta.1、2.1.0-rc.0,但格式上有讲究:预发布标识只能由字母数字和连字符组成,不能乱写。
最关键的是,同一个name下的同一个version不能重复发布。1.0.0发过了,即使你只是想覆盖修复,也不能再发一次1.0.0,必须改成1.0.1。中心仓这么设计,是为了保证使用方锁定的版本是稳定内容。谁也不想一个ohpm install之后,代码“悄悄”变了。
3.3 main 字段:入口文件决定别人怎么 import
main字段指定库的入口。对 ArkTS 库来说,最常见的写法是:
"main": "./Index.ets"也可以省略前面的./,写成"Index.ets",但为了风格统一,我带./。
这里有个容易踩的坑:入口文件名的大小写。DevEco Studio 默认生成的是Index.ets,首字母大写。如果你把 main 写成./index.ets,本地构建可能不报错,但中心仓校验用“文件精确匹配”的办法找入口,大小写不一致就报main entry not found。
入口文件的主体不应该是业务逻辑,而是导出聚合:
export { default as RichText } from './src/main/ets/components/RichText'; export { RichTextModel } from './src/main/ets/models/RichTextModel'; export type { RichTextOptions } from './src/main/ets/models/RichTextOptions';这样调用方就能统一导入:
import { RichText, RichTextModel } from '@demo/richtext';3.4 author、license、description、keywords
author建议写成名字 <邮箱>的格式,例如"author": "demo <demo@example.com>"。中心仓页面会展示作者信息,审核人员也能找到人。不推荐留空。
license必须写 SPDX 标准许可证标识,常见的有MIT、Apache-2.0、BSD-3-Clause。不要写“请查看仓库”或者自创许可证名。自动审核扫到非 SPDX 标识会直接拦截。
description控制在两三句话内,说明“这个库解决什么问题”。它是搜索结果里的主体展示,写得含糊的库,用户连点进去的欲望都没有。
keywords是数组,建议至少包含harmonyos和ohos,再加两三个功能关键词。它影响中心仓搜索命中率,属于低成本高收益的字段。
3.5 dependencies 与 devDependencies 的边界
这两个字段决定别人安装你的库时,还需要额外下载什么。
"dependencies": { "@other/foo": "^2.0.0" }, "devDependencies": { "@ohos/hypium": "1.0.18" }dependencies里的包会随你的库一起被安装解析。如果运行时不需要的包被放进这里,只是增加安装体积,还容易触发依赖冲突。凡是只在单测里用的,统统放devDependencies。
另外要严防一种写法:本地路径依赖。
"dependencies": { "@local/common": "file:../common" }这种写法在本地联调非常香,但发布出去就是深坑。别人的机器上根本不存在../common,安装阶段直接报依赖缺失。要发布,就必须改成中心仓可解析的真实版本号,或者把通用部分拆成一个独立发布的包再引用。
3.6 容易被忽略的其他字段
repository:不是必填,但强烈建议写。审核和用户都需要知道源码在哪。type:一般情况下不用写,DevEco 会自动处理。typings:ArkTS 库通常不需要手动配,编译过程会自动生成声明文件。changelog字段:部分工具链会支持,但我更推荐直接维护一个CHANGELOG.md,在中心仓页面展示更清晰。
4. 构建产物与本地验证:上传前最后一关
配置写得再漂亮,最终交付的是一个.har文件。很多人觉得“配置没问题就一定能过”,结果构建出来的产物根本没有入口,或者包里混入了奇怪的东西。所以上传前,建议按下面这套流程走一遍,总耗时不超过十分钟。
4.1 Make Module,找到正确的 .har
打开 DevEco Studio,确定当前选中的模块是共享库模块,然后菜单栏执行Build -> Make Module。构建成功以后,产物路径通常是:
libdemo/build/default/outputs/default/libdemo.har注意,Make Module 的对象是模块名。如果工程里不小心选中了 entry,构建出来的就不是 har,而是 hap。所以构建完先看路径,路径最后一级是outputs/default,文件后缀是.har,基本就对了。
4.2 解压产物,检查三件事
.har是标准 zip 包,用压缩软件打开后,建议先找三个东西:
Index.ets或编译后的Index.js/Index.d.ts:确认入口在不在,文件名和 main 字段是否一致;module.json:确认里面的 type 是不是har;oh-package.json:确认 name 和 version 是不是这次要发布的版本。
如果解压后看到resources目录,说明资源被打进去了,这是正常的。如果看到abilities、pages这类文件,就怀疑是不是把 entry 的东西打进来了,赶紧检查模块边界。
4.3 本地 ohpm 安装测试
产物本地自测,是发布前最有价值的一步。最省事的做法,在测试工程里执行:
ohpm install /path/to/libdemo.har安装成功后,写一行 import 代码,编译运行。能跑通,说明这个 HAR 被外部工程消费时是闭合的;跑不通,至少不用把问题丢到中心仓审核那边才暴露。
更保险的做法是新建一个空工程,专门用来“消费”待发布的库。这样能排除测试工程里已有资源对其它包的干扰。我见过很多情况:库在自己的 demo 工程里跑得好好的,换到空工程一编译就报错,原因往往是依赖了 demo 工程里某个没有发布的本地模块。
4.4 一套可以直接抄的 Module 配置清单
把前面讲过的关键配置综合成一张检查表,发布前逐项核对:
| 检查项 | 推荐结果 | 说明 |
|---|---|---|
| Module 类型 | har | module.json5的 type 字段 |
| SDK 版本 | 5.0.0(12) 或兼容版本 | compileSdkVersion/compatibleSdkVersion |
| 包名 | @scope/name | 小写、全局唯一 |
| 版本号 | 1.0.0 起,semver | 不能重复发布 |
| 入口 | ./Index.ets | 文件存在、大小写一致 |
| 许可证 | Apache-2.0 / MIT | SPDX 标准 |
| 运行时依赖 | 可解析的 ohpm 版本 | 禁止 file: 路径 |
| 文档 | README、CHANGELOG、LICENSE | 至少 README 要规范 |
5. 发布时常见的 Module 配置报错与排查实录
这一节基本是从我踩过的坑里总结出来的,大概率也是你马上要遇到的。
5.1 高频错误速查表
| 报错或现象 | 原因 | 解决方案 |
|---|---|---|
| Invalid package name | name 含大写、中文或特殊字符 | 改成小写字母、数字、-、_、. |
| Version already exists | 相同版本号重复发布 | 升版本,如 1.0.0 -> 1.0.1 |
| main entry not found | main 写错路径或文件名大小写不一致 | 改成 ./Index.ets 并核对文件 |
| license is required | license 缺失 | 填 SPDX 标准标识 |
| Dependency not found | dependencies 里用了 file: 路径或私有源 | 改成中心仓可解析版本 |
| Package name conflict | scope/name 已被占用 | 换 scope 或换库名 |
| Version too low | 新版本号低于已发布版本 | 使用更高版本号 |
| 产物是 hap 而不是 har | 构建时选中了 entry 模块 | 先选中共享库模块再 Make |
其中,Version too low是我遇到最多的报错。一个人维护多个项目时,配置模板不小心复制了旧版本号,自己还没发现。中心仓会做一次版本递增校验,低于或等于线上版本直接被拒。
5.2 发布命令为什么总是静默失败
你可能会遇到这种情况:本地构建一切正常,配置看起来也对,但执行发布命令时,命令行输出只有一句 “publish failed”。这种静默失败,十有八九是 ohpm 源和凭据没配置对。
发布前,我建议先执行:
ohpm config get registry确认当前源指向的是你打算发布的三方库中心仓。如果之前一直在做应用内依赖开发,源地址很可能还停留在某个测试源或私有源。账号 token 也要重新登录刷新。这个检查做一次,能省掉反复试错的痛苦。
6. 发布之后:版本迭代与维护中的真实体会
HAR 上传成功只是开始。我自己维护过几个库,最深的体会是:Module 配置能保证你“进得了门”,但后续的版本管理决定这个库“活不活”。
每次发版前,务必同步三个文件:oh-package.json5、CHANGELOG.md、README.md。版本号改了,CHANGELOG 要把新增内容和破坏性变更写清楚,README 里的安装命令、API 示例也要跟着更新。中心仓的人工审核看一遍就懂,你的库是认真维护还是随手扔上去的。
升级版本要克制。库刚发布时,很多使用者会按^或~的范围依赖自动获取小版本。你发一个含有破坏性变更的1.1.0,可能让一堆人的项目在静默更新后编译失败。破坏性变更要么放在主版本号,要么在 CHANGELOG 里加粗提醒。
还有一条:别在三方库里依赖其它未发布的私有产物。这个在前面讲file:依赖时说过,但值得再说一次。你在自己工程里联调得再爽,只要依赖在中心仓解析不到,使用者就装不上。宁可把通用部分拆成一个真正发布出去的包,再通过版本号引用回来。
最后分享一个我保留到现在的习惯:每次发布前,在新工程里ohpm install最新版本并编译跑通。这套动作从第一次踩坑后就再没断过,成本五分钟,却能挡住大部分低级错误。库的发布考验的不是炫技,而是把一个模块当成公共交付物去校验的耐心。能把这套 Module 配置吃透,后面再发多少库,都只是复制粘贴的体力活。