没有模块化,何谈大航海?——ArkTS依赖管理这点事儿,你真的搞明白了吗?
本文是一篇关于鸿蒙(HarmonyOS)ArkTS模块化与依赖管理的技术分享。作者结合自身转型经验,针对鸿蒙项目规模扩展后常见的模块混乱问题,从工程结构、配置规范、依赖管理、热更新策略等方面进行系统梳理。文章详细讲解了鸿蒙工程的模块划分(app/feature/shared/infra)、关键配置文件module.json5的编写要点、ArkTS模块的导入导出规范、能力(Ability)的边界管理
我是兰瓶Coding,一枚刚踏入鸿蒙领域的转型小白,原是移动开发中级,如下是我学习笔记《零基础学鸿蒙》,若对你所有帮助,还请不吝啬的给个大大的赞~
前言
先抛一个灵魂拷问:当你的鸿蒙项目从“Hello World”一夜长成“Hello World、Profile、Settings、Pay、IM、Feed、Search、Widget…”时,靠 copy-paste 还能走多远?
是的,我也踩过坑:模块像拼乐高一样越拼越花,却常常“卡扣”对不齐;import 到处乱飞,module.json5 一改就连环报错;明明只是想抽个 shared-ui,却顺手把依赖环给绕成了“莫比乌斯带”。今天这篇文章,我打算用既接地气又不失专业的方式,把 ArkTS 模块化与依赖管理这套“底层秩序”聊透,顺手送上可跑的代码示例,以及热更新/热重载那些“到底能不能上生产”的关键边界。放心,全篇有温度,有“吐槽”,也有硬核;目标是——读完能落地,写完能交付。
目录(你可以当作“路线图”)
-
前言:为什么鸿蒙项目要“强模块化”?
-
从 0 到 1 的模块组织结构
- 2.1 工程粒度划分:
app、feature、library、shared、infra - 2.2 目录长什么样:Bundle / HAP / HAR / HSP 一图看懂
- 2.3 规范命名:别把“好名字”浪费在注释上
- 2.1 工程粒度划分:
-
module.json5 详解
- 3.1
module、abilities、requestPermissions、dependencies - 3.2
type: entry/feature/library与打包产物的关系 - 3.3 常见坑:
srcEntry、pages、resources路径与多模块资源访问
- 3.1
-
ArkTS 的 import/export 心法
- 4.1 “自研模块”与“第三方依赖”的两条线
- 4.2
export type/export default/命名导出最佳实践 - 4.3
index.ets与 Barrel(聚合导出)模式 - 4.4 循环依赖识别与解法
-
能力(Ability)与包管理
- 5.1
UIAbility、ServiceExtensionAbility的职责边界 - 5.2 跨模块唤起与参数约定
- 5.3
bundle、module、hap之间的装配 - 5.4 OHPM 与本地 HAR/HSP:什么时候产物化、什么时候源码化
- 5.1
-
模块热更新:别神话,也别妖魔化
- 6.1 “热重载”(开发期)vs.“热更新”(线上期)
- 6.2 风险识别:接口不兼容、资源变更、审核合规
- 6.3 可行落地方案:插件化/可插拔 Feature、灰度与版本路由
-
可运行示例:从零搭一套“Todo+Settings”的多模块工程
- 7.1 目录结构
- 7.2
module.json5(entry/feature/library) - 7.3 关键 ArkTS 代码:
import/export、Ability 跳转、数据仓 - 7.4 打包与依赖走查清单
-
性能与构建优化
- 8.1 资源分割与按需加载
- 8.2 依赖去重、死代码消除(DCE)与公共模块抽取
- 8.3 “一键体检”脚本:依赖拓扑扫描
-
团队协作与演进策略
- 9.1 API 稳定面与变更日志
- 9.2 版本化依赖与灰度集成
- 9.3 回滚策略与“救火 SOP”
-
结语:写给未来的你——不要让模块成为“技术债收纳箱”
1. 前言:为什么鸿蒙项目要“强模块化”?
我见过太多“用文件夹假装模块化”的项目:UI、网络、状态、资源、能力全都拖在同一个 entry 里。最开始没事,一年后变成“谁都不敢动的 Jenga 塔”。
鸿蒙(HarmonyOS/OpenHarmony)的工程生态有自己一套分层产物:APP(应用包)由若干 HAP(模块包)拼装;HAP 又依赖 HAR(源码库)或 HSP(共享包)。ArkTS 模块化不是锦上添花,而是规模化开发的地基:
- 团队协作:不同小组独立演进,降低合并冲突;
- 构建效率:按模块增量编译与复用;
- 发布灵活:
feature模块可分拆交付与灰度; - 风险管控:依赖拓扑清晰,回滚与替换更容易。
一句话:没有模块化,何谈大航海。
2. 从 0 到 1 的模块组织结构
2.1 工程粒度划分
推荐一个对多数团队都友好的切分法:
- app/entry:应用壳与路由总入口(
UIAbility起点),公共导航与权限分发。 - feature/:按业务域划分(如
todo、settings、profile、search)。 - shared/:横切资源与通用 UI(如
shared-ui、shared-theme、shared-i18n)。 - infra/:基础设施层(如
network、storage、auth、logging、analytics)。 - domain/(可选):领域模型与用例(UseCase),保持业务语义稳定面。
经验之谈:不要把“网络请求”写进
feature,那是infra的活;也不要把“主题资源”揉进entry,那是shared的地盘。
2.2 目录长什么样:Bundle / HAP / HAR / HSP
一个典型仓库(简化)可能是这样:
root/
├─ app/
│ └─ entry/ # entry HAP 模块
│ ├─ src/main/ets/
│ │ ├─ entryability/EntryAbility.ets
│ │ └─ pages/Index.ets
│ ├─ src/main/resources/
│ ├─ module.json5
│ └─ oh-package.json5
├─ features/
│ ├─ todo/ # feature HAP
│ │ ├─ src/main/ets/
│ │ ├─ src/main/resources/
│ │ └─ module.json5
│ └─ settings/ # feature HAP
│ ├─ src/main/ets/
│ └─ module.json5
├─ shared/
│ ├─ ui/ # HAR(源码库)
│ │ ├─ src/main/ets/
│ │ └─ oh-package.json5
│ └─ theme/ # HAR/HSP 视规模而定
│ └─ ...
├─ infra/
│ ├─ network/ # HAR
│ ├─ storage/ # HAR
│ └─ analytics/ # HAR
└─ ohpm-lock.json5
- HAP(.hap):可运行的模块包(
entry/feature)。 - HAR(.har):源码依赖(像 npm 包),编译期引入。
- HSP(.hsp):共享包(“半编译产物”),多模块共享运行时资源/代码。
- APP(.app):最终应用包,装配若干 HAP。
2.3 规范命名:别把“好名字”浪费在注释上
- 模块名用短横线或小驼峰:
shared-ui、infra-network、feature-todo。 - 入口 Ability 与页面:
EntryAbility、TodoHomePage、SettingsPage。 - Resource ID 清晰:
$r("app.string.todo_title"),不要随意复用别的模块的 key。
3. module.json5 详解(配方要准)
友情提示:不同 SDK/工具版本字段细节可能有差异,下文给出的是可工作的典型写法;你在落地时请以本地 DevEco/SDK 校验为准(构建校验永远是最终裁判)。
3.1 关键字段与语义
以 entry 模块为例:
{
"module": {
"name": "entry",
"type": "entry",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:module_desc",
"deviceTypes": ["phone", "tablet"],
"reqCapabilities": [],
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"icon": "$media:app_icon",
"label": "$string:app_name",
"visible": true,
"launchType": "standard",
"skills": [
{
"actions": ["action.system.home"],
"entities": ["entity.system.home"]
}
]
}
],
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" }
],
"dependencies": {
"modules": [
// 依赖其他 HAP(feature)或 HSP
{ "name": "todo", "type": "module" },
{ "name": "settings", "type": "module" }
],
"har": [
// 依赖源码库 HAR(本地或 ohpm)
{ "name": "@shared/ui" },
{ "name": "@infra/network" }
]
}
}
}
type:entry(应用入口)/feature(业务功能)/har(源码库)等。srcEntry:Ability 的 ArkTS 入口。abilities:可声明多个能力(常见UIAbility)。dependencies:两路并行——模块级(HAP/HSP)与源码级(HAR/ohpm)。
3.2 type 与打包产物
entry/feature→ 编译为 HAP,可运行、可装配到 APP;har→ HAR 源码库,供其他模块编译期引入;- 需要多模块共享运行时代码/资源(且避免多份拷贝)时,考虑抽为 HSP;
- 第三方依赖优先走 ohpm(OpenHarmony 包管理),内部共享优先 HAR/HSP。
3.3 常见坑(踩过都疼)
- 路径相对根:
src/main/ets下的源码,srcEntry要相对该路径写。 - 资源访问:跨模块资源不建议直接引用另一个 HAP 的 R 资源,抽到
shared-themeHAR/HSP。 - 权限声明:放在真正发起能力调用的 HAP 里,不要“全家桶权限”。
- 模块依赖闭环:
entry → todo → shared-ui → entry这种环会让增量构建发疯。
4. ArkTS 的 import/export 心法
4.1 自研模块 vs. 第三方依赖
- 自研模块(HAR/HSP):
ohpm的oh-package.json5声明name,被其他模块以import { X } from '@shared/ui'方式引入。 - 第三方依赖:同样走
ohpm i xxx,注意版本锁定与语义化版本(semver)。
4.2 导出策略
**聚合导出(barrel)**是大项目的救命稻草:
// shared/ui/src/main/ets/index.ets
export * from './components/Button';
export * from './components/Card';
export * from './hooks/useTheme';
消费者端:
import { Button, Card, useTheme } from '@shared/ui';
Tips:
- 公共类型
export type独立到types.ts,避免循环引用;- 组件/Hook 对外暴露最小必要 API,别把私有实现细节泄露出去。
4.3 循环依赖识别与解法
症状:A 引 B,B 引 C,C 又经由 barrel 引回 A;构建偶发成功、运行崩。
解法:
- 拆类型层:把
types独立到无实现的包; - 事件/接口反转:用回调或接口而非直接引入实现;
- 资源上移:把公共常量、主题放到
shared; - 别过度 barrel:对“核心层”谨慎使用
export *。
5. 能力(Ability)与包管理
5.1 UIAbility、ServiceExtensionAbility 的职责边界
- UIAbility:页面栈管理、导航与路由、窗口生命周期;
- ServiceExtensionAbility:长驻/后台服务、耗时任务、跨模块服务化接口。
5.2 跨模块唤起与参数约定
约定一套统一的 DeepLink/Router 协议(字符串常量集中在 shared-routing):
// shared/routing/src/main/ets/routes.ts
export const Routes = {
TODO_HOME: 'feature.todo/home',
TODO_DETAIL: 'feature.todo/detail',
SETTINGS_HOME: 'feature.settings/home',
} as const;
export type RouteParams = {
[Routes.TODO_DETAIL]: { id: string };
};
调用侧:
import { router } from '@kit.ArkUI'; // 示例:具体以实际 SDK 导入为准
import { Routes } from '@shared/routing';
router.pushUrl({
url: Routes.TODO_DETAIL,
params: { id: '42' }
});
经验:把路由常量与参数类型独立成一个 HAR,谁也别私自造轮子。
5.3 bundle、module、hap 的装配
- APP 级别通过
bundle.json5/项目配置把多个 HAP 装配为一个应用; - 业务上可将
featureHAP 独立升级(视发布策略与商店规则); entryHAP 只做容器与导航,不要塞业务逻辑。
5.4 包管理:OHPM、本地 HAR/HSP
- OHPM:官方/社区包管理,语义化版本、锁文件(
ohpm-lock.json5)要进仓; - 本地路径依赖:单仓多包用相对路径(monorepo),避免“已发布再回拉”;
- HSP:当多个 HAP 运行期共享同一份资源/代码时优先(避免重复)。
6. 模块热更新:别神话,也别妖魔化
6.1 概念拆分
-
热重载(Hot Reload/Refresh):开发期,DevEco/模拟器侧的快速预览与增量替换;
-
热更新(Hot Update):线上生产动态替换代码/资源。
- 注意:涉及平台合规、应用商店审核与安全边界。不要让“上线后偷偷改代码”成为事故起点。
6.2 风险识别
- 接口不兼容:老 APP 跑新模块,ABI/接口变化导致崩溃;
- 资源变更:资源 ID、样式主题改名,运行期找不到;
- 安全合规:是否允许远程下发脚本/资源,是否需要走完整升级通道。
6.3 可行落地方案
- 插件化/可插拔 Feature:核心容器固定,业务功能以 HAP/插件包为单位灰度;
- 版本路由:容器内置
manifest,根据远端配置选择本地/插件版本; - 强约束 API:对外只暴露稳定的
SPI/Facade,禁止跨越访问内部实现; - 兜底回滚:版本表 + 校验和(hash),一键回滚到上一个稳定版本。
7. 可运行示例:Todo + Settings 多模块工程
7.1 目录结构(精简可跑)
root/
├─ app/entry/
│ ├─ src/main/ets/
│ │ ├─ entryability/EntryAbility.ets
│ │ └─ pages/Index.ets
│ ├─ src/main/resources/
│ │ └─ base/element/string.json
│ └─ module.json5
├─ features/todo/
│ ├─ src/main/ets/
│ │ ├─ pages/TodoHome.ets
│ │ └─ pages/TodoDetail.ets
│ └─ module.json5
├─ features/settings/
│ ├─ src/main/ets/pages/SettingsHome.ets
│ └─ module.json5
├─ shared/ui/
│ ├─ src/main/ets/components/Button.ets
│ ├─ src/main/ets/index.ets
│ └─ oh-package.json5
├─ infra/storage/
│ ├─ src/main/ets/storage.ts
│ └─ oh-package.json5
└─ ohpm-lock.json5
7.2 配置文件
app/entry/module.json5
{
"module": {
"name": "entry",
"type": "entry",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:entry_desc",
"deviceTypes": ["phone"],
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"icon": "$media:app_icon",
"label": "$string:app_name",
"visible": true,
"launchType": "standard",
"skills": [
{ "actions": ["action.system.home"], "entities": ["entity.system.home"] }
]
}
],
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" }
],
"dependencies": {
"modules": [
{ "name": "todo", "type": "module" },
{ "name": "settings", "type": "module" }
],
"har": [
{ "name": "@shared/ui" },
{ "name": "@infra/storage" }
]
}
}
}
features/todo/module.json5
{
"module": {
"name": "todo",
"type": "feature",
"srcEntry": "./ets/pages/TodoHome.ets",
"description": "$string:todo_desc",
"deviceTypes": ["phone"],
"abilities": [
{
"name": "TodoAbility",
"srcEntry": "./ets/pages/TodoHome.ets",
"visible": true,
"launchType": "standard"
}
],
"dependencies": {
"har": [
{ "name": "@shared/ui" },
{ "name": "@infra/storage" }
]
}
}
}
features/settings/module.json5
{
"module": {
"name": "settings",
"type": "feature",
"srcEntry": "./ets/pages/SettingsHome.ets",
"description": "$string:settings_desc",
"deviceTypes": ["phone"]
}
}
shared/ui/oh-package.json5
{
"name": "@shared/ui",
"version": "1.0.0",
"description": "Shared UI components for the app",
"types": "",
"main": "src/main/ets/index.ets",
"license": "MIT"
}
infra/storage/oh-package.json5
{
"name": "@infra/storage",
"version": "1.0.0",
"main": "src/main/ets/storage.ts"
}
7.3 关键 ArkTS 代码
app/entry/src/main/ets/entryability/EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
// 关联首页
windowStage.loadContent('pages/Index', (err) => {
if (err) {
console.error('Load content failed: ' + JSON.stringify(err));
}
});
}
}
app/entry/src/main/ets/pages/Index.ets
import { Button } from '@shared/ui';
import { router } from '@kit.ArkUI'; // 示例化导入,按实际 SDK 为准
@Entry
@Component
struct IndexPage {
private title: string = 'Modular Todo';
build() {
Column({ space: 16 }) {
Text(this.title).fontSize(26).fontWeight(FontWeight.Bold)
Button({ text: 'Go Todo' }).onClick(() => {
router.pushUrl({ url: 'feature.todo/home' });
})
Button({ text: 'Go Settings' }).onClick(() => {
router.pushUrl({ url: 'feature.settings/home' });
})
}.padding(24)
}
}
features/todo/src/main/ets/pages/TodoHome.ets
import { Button, Card } from '@shared/ui';
import { getStore, addTodo } from '@infra/storage';
import { router } from '@kit.ArkUI';
@Entry
@Component
struct TodoHome {
@State todos: string[] = [];
aboutToAppear() {
this.todos = getStore().list();
}
build() {
Column({ space: 12 }) {
Text('Todo · Home').fontSize(22).fontWeight(FontWeight.Bold)
ForEach(this.todos, (t, idx) => {
Card({ title: `#${idx + 1}`, content: t }).onClick(() => {
router.pushUrl({ url: 'feature.todo/detail', params: { id: String(idx) } });
})
})
Button({ text: 'Add Random Todo' }).onClick(() => {
const text = 'New Task ' + Math.floor(Math.random() * 1000);
addTodo(text);
this.todos = getStore().list();
})
}.padding(20)
}
}
features/todo/src/main/ets/pages/TodoDetail.ets
import { getStore } from '@infra/storage';
import { router } from '@kit.ArkUI';
@Entry
@Component
struct TodoDetail {
@State text: string = '';
onPageShow() {
const params = router.getParams() as { id?: string };
const id = Number(params?.id ?? -1);
if (id >= 0) this.text = getStore().list()[id] ?? '';
}
build() {
Column({ space: 12 }) {
Text('Todo · Detail').fontSize(22).fontWeight(FontWeight.Bold)
Text(this.text).fontSize(18)
Button('Back').onClick(() => router.back())
}.padding(20)
}
}
features/settings/src/main/ets/pages/SettingsHome.ets
import { Button } from '@shared/ui';
import { getStore, clearAll } from '@infra/storage';
@Entry
@Component
struct SettingsHome {
@State count: number = 0;
aboutToAppear() {
this.count = getStore().list().length;
}
build() {
Column({ space: 16 }) {
Text('Settings').fontSize(22).fontWeight(FontWeight.Bold)
Text(`Todos count: ${this.count}`)
Button({ text: 'Clear All' }).onClick(() => {
clearAll();
this.count = 0;
})
}.padding(20)
}
}
shared/ui/src/main/ets/components/Button.ets
@Component
export struct Button {
text: string
onClick?: () => void
build() {
Text(this.text)
.fontSize(18)
.padding(12)
.backgroundColor('#007DFF')
.fontColor('#FFFFFF')
.borderRadius(8)
.onClick(() => this.onClick?.())
}
}
shared/ui/src/main/ets/components/Card.ets
@Component
export struct Card {
title: string
content: string
build() {
Column({ space: 8 }) {
Text(this.title).fontSize(14).fontColor('#888888')
Text(this.content).fontSize(18).fontWeight(FontWeight.Medium)
}
.padding(16)
.borderRadius(12)
.backgroundColor('#F6F8FA')
}
}
shared/ui/src/main/ets/index.ets
export * from './components/Button';
export * from './components/Card';
infra/storage/src/main/ets/storage.ts
interface Store {
list(): string[];
add(text: string): void;
clear(): void;
}
class MemoryStore implements Store {
private data: string[] = [];
list() { return [...this.data]; }
add(text: string) { this.data.push(text); }
clear() { this.data = []; }
}
const store = new MemoryStore();
export function getStore(): Store {
return store;
}
export function addTodo(text: string) {
store.add(text);
}
export function clearAll() {
store.clear();
}
说明:上面代码演示架构与依赖组织,
router/导入路径需要按你本机 SDK/DevEco 的实际包名对齐,资源与字符串请按规范补齐。重点在“模块化结构+依赖边界”,跑起来之前做一次 IDE 自动修正即可。
7.4 构建与依赖走查清单
-
entry仅做容器/导航,不承载业务; -
feature之间不互相引用 UI,统一经shared-ui; -
infra不引用feature(方向只向上); -
ohpm-lock.json5入库,确保环境可重现; - 依赖图无环(脚本检查,见第 8 章)。
8. 性能与构建优化
8.1 资源分割与按需加载
- 组件懒加载:把大体积页面拆分;
- 图片与媒体:归档到共享资源包,避免重复打进多个 HAP;
- 国际化:按区域资源拆分,运行期只加载需要的语言。
8.2 依赖去重与公共模块抽取
- 扫描重复依赖(同一库不同版本);
- 抽取
shared-theme/shared-constants,减少“复制黏贴型”常量。
8.3 “一键体检”脚本(Node/ArkTS 皆可)
伪代码思路:遍历各
module.json5与oh-package.json5,生成依赖图,输出环与跨层引用。
// scripts/depcheck.ts(思路示例)
import { readFileSync, readdirSync } from 'fs';
import { join } from 'path';
type Node = { name: string; type: 'entry'|'feature'|'har'|'hsp' };
type Edge = { from: string; to: string };
function scanModules(root: string) {
// 遍历查找 module.json5 / oh-package.json5,解析 name/依赖
}
function detectCycles(edges: Edge[]) {
// DFS 或 Tarjan 强连通分量
}
function main() {
const { nodes, edges } = scanModules(process.cwd());
const cycles = detectCycles(edges);
if (cycles.length) {
console.error('Cyclic deps:\n', cycles);
process.exit(1);
}
console.log('Deps OK, total nodes:', nodes.length);
}
main();
9. 团队协作与演进策略
9.1 API 稳定面
- 对外接口只增不破(向后兼容);
- 废弃策略:先标注
@deprecated+替代方案,两个版本后再移除; - 契约测试:为
shared-ui/infra写“契约用例”,保护调用方。
9.2 版本化依赖与灰度
@shared/ui@1.2.x走补丁灰度,@infra/storage@2.0做大版本升级;- 在
entry维护一份兼容矩阵(哪个 feature 需要哪个公共库版本)。
9.3 回滚与救火 SOP
- 不删除老产物:保留上一个稳定 HAP;
- 远端开关:一键切回内置版本;
- 事故复盘:加上“变更日志模板”,每次升级写清楚**“为什么改、怎么回滚、兼容性如何”**。
10. 结语:写给未来的你
我们都知道,模块既是秩序,也是自由。它让团队像舰队一样并排前行,又允许单船加速冲刺。ArkTS 的模块化与依赖管理不是冷冰冰的规则集合,而是你在产品规模化、团队专业化之后能否从容“进退自如”的护城河。
下次当你想在 entry 里随手加一个“临时工具函数”,请先深呼吸三秒——它属于 infra,还是 shared? 当你准备从 A 模块里直接 import B 的实现类时,再想想:能不能只引它的接口?
愿你写的每一个模块,都不是“技术债收纳箱”,而是可复用、可演进、可交付的积木。等你的项目真的“肥到飞起”,你会感谢今天留下的这些边界与仪式感。
附录 A:常见问题(带点“人味”的 FAQ)
-
Q:我就一个人写,搞这么多模块是不是过度设计?
A:不是让你一开始就开十几个包。先两层:entry+shared/infra,等规模上来再“生 feature”。架构是可演进的。 -
Q:
export *很香,但会不会引循环依赖?
A:香,但要节制。核心层少用,类型单独放,实在绕不开就接口反转。 -
Q:热更新能不能线上直接换逻辑?
A:请遵循平台与商店规则。开发期热重载 ≠ 线上热更新。生产环境以插件化 + 版本路由 + 灰度的组合拳为主,且要有回滚。 -
Q:HAR 和 HSP 什么时候选谁?
A:编译期复用选 HAR,运行期共享(避免重复)倾向 HSP。团队小、需求简单先 HAR,别一上来就 HSP。
附录 B:一键模板(可复制开箱用)
新建 feature 模块:features/xxx/module.json5
{
"module": {
"name": "xxx",
"type": "feature",
"srcEntry": "./ets/pages/XxxHome.ets",
"deviceTypes": ["phone"],
"dependencies": { "har": [ { "name": "@shared/ui" } ] }
}
}
聚合导出模板:shared/ui/src/main/ets/index.ets
export * from './components/Button';
export * from './components/Card';
// add more...
Ability 路由规范片段:shared/routing/src/main/ets/routes.ts
export const Routes = {
XXX_HOME: 'feature.xxx/home'
} as const;
依赖体检脚本(命令行)
# package.json 里加一条
"scripts": {
"dep:check": "node scripts/depcheck.js"
}
…
(未完待续)
更多推荐


所有评论(0)