我是兰瓶Coding,一枚刚踏入鸿蒙领域的转型小白,原是移动开发中级,如下是我学习笔记《零基础学鸿蒙》,若对你所有帮助,还请不吝啬的给个大大的赞~

前言

先抛一个灵魂拷问:当你的鸿蒙项目从“Hello World”一夜长成“Hello World、Profile、Settings、Pay、IM、Feed、Search、Widget…”时,靠 copy-paste 还能走多远?
  是的,我也踩过坑:模块像拼乐高一样越拼越花,却常常“卡扣”对不齐;import 到处乱飞,module.json5 一改就连环报错;明明只是想抽个 shared-ui,却顺手把依赖环给绕成了“莫比乌斯带”。今天这篇文章,我打算用既接地气又不失专业的方式,把 ArkTS 模块化与依赖管理这套“底层秩序”聊透,顺手送上可跑的代码示例,以及热更新/热重载那些“到底能不能上生产”的关键边界。放心,全篇有温度,有“吐槽”,也有硬核;目标是——读完能落地,写完能交付。

目录(你可以当作“路线图”)

  1. 前言:为什么鸿蒙项目要“强模块化”?

  2. 从 0 到 1 的模块组织结构

    • 2.1 工程粒度划分:appfeaturelibrarysharedinfra
    • 2.2 目录长什么样:Bundle / HAP / HAR / HSP 一图看懂
    • 2.3 规范命名:别把“好名字”浪费在注释上
  3. module.json5 详解

    • 3.1 moduleabilitiesrequestPermissionsdependencies
    • 3.2 type: entry/feature/library 与打包产物的关系
    • 3.3 常见坑:srcEntrypagesresources路径与多模块资源访问
  4. ArkTS 的 import/export 心法

    • 4.1 “自研模块”与“第三方依赖”的两条线
    • 4.2 export type/export default/命名导出最佳实践
    • 4.3 index.ets 与 Barrel(聚合导出)模式
    • 4.4 循环依赖识别与解法
  5. 能力(Ability)与包管理

    • 5.1 UIAbilityServiceExtensionAbility 的职责边界
    • 5.2 跨模块唤起与参数约定
    • 5.3 bundlemodulehap 之间的装配
    • 5.4 OHPM 与本地 HAR/HSP:什么时候产物化、什么时候源码化
  6. 模块热更新:别神话,也别妖魔化

    • 6.1 “热重载”(开发期)vs.“热更新”(线上期)
    • 6.2 风险识别:接口不兼容、资源变更、审核合规
    • 6.3 可行落地方案:插件化/可插拔 Feature、灰度与版本路由
  7. 可运行示例:从零搭一套“Todo+Settings”的多模块工程

    • 7.1 目录结构
    • 7.2 module.json5(entry/feature/library)
    • 7.3 关键 ArkTS 代码:import/export、Ability 跳转、数据仓
    • 7.4 打包与依赖走查清单
  8. 性能与构建优化

    • 8.1 资源分割与按需加载
    • 8.2 依赖去重、死代码消除(DCE)与公共模块抽取
    • 8.3 “一键体检”脚本:依赖拓扑扫描
  9. 团队协作与演进策略

    • 9.1 API 稳定面与变更日志
    • 9.2 版本化依赖与灰度集成
    • 9.3 回滚策略与“救火 SOP”
  10. 结语:写给未来的你——不要让模块成为“技术债收纳箱”


1. 前言:为什么鸿蒙项目要“强模块化”?

我见过太多“用文件夹假装模块化”的项目:UI、网络、状态、资源、能力全都拖在同一个 entry 里。最开始没事,一年后变成“谁都不敢动的 Jenga 塔”。
  鸿蒙(HarmonyOS/OpenHarmony)的工程生态有自己一套分层产物APP(应用包)由若干 HAP(模块包)拼装;HAP 又依赖 HAR(源码库)或 HSP(共享包)。ArkTS 模块化不是锦上添花,而是规模化开发的地基

  • 团队协作:不同小组独立演进,降低合并冲突;
  • 构建效率:按模块增量编译与复用;
  • 发布灵活feature 模块可分拆交付与灰度;
  • 风险管控:依赖拓扑清晰,回滚与替换更容易。

一句话:没有模块化,何谈大航海。


2. 从 0 到 1 的模块组织结构

2.1 工程粒度划分

推荐一个对多数团队都友好的切分法:

  • app/entry:应用壳与路由总入口(UIAbility 起点),公共导航与权限分发。
  • feature/:按业务域划分(如 todosettingsprofilesearch)。
  • shared/:横切资源与通用 UI(如 shared-uishared-themeshared-i18n)。
  • infra/:基础设施层(如 networkstorageauthlogginganalytics)。
  • 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-uiinfra-networkfeature-todo
  • 入口 Ability 与页面:EntryAbilityTodoHomePageSettingsPage
  • 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" }
      ]
    }
  }
}
  • typeentry(应用入口)/feature(业务功能)/har(源码库)等。
  • srcEntry:Ability 的 ArkTS 入口。
  • abilities:可声明多个能力(常见 UIAbility)。
  • dependencies两路并行——模块级(HAP/HSP)与源码级(HAR/ohpm)。

3.2 type 与打包产物

  • entry / feature → 编译为 HAP,可运行、可装配到 APP;
  • harHAR 源码库,供其他模块编译期引入;
  • 需要多模块共享运行时代码/资源(且避免多份拷贝)时,考虑抽为 HSP
  • 第三方依赖优先走 ohpm(OpenHarmony 包管理),内部共享优先 HAR/HSP

3.3 常见坑(踩过都疼)

  • 路径相对根src/main/ets 下的源码,srcEntry 要相对该路径写。
  • 资源访问:跨模块资源不建议直接引用另一个 HAP 的 R 资源,抽到 shared-theme HAR/HSP。
  • 权限声明:放在真正发起能力调用的 HAP 里,不要“全家桶权限”。
  • 模块依赖闭环entry → todo → shared-ui → entry 这种环会让增量构建发疯。

4. ArkTS 的 import/export 心法

4.1 自研模块 vs. 第三方依赖

  • 自研模块(HAR/HSP):ohpmoh-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;构建偶发成功、运行崩。
解法:

  1. 拆类型层:把 types 独立到无实现的包;
  2. 事件/接口反转:用回调或接口而非直接引入实现;
  3. 资源上移:把公共常量、主题放到 shared
  4. 别过度 barrel:对“核心层”谨慎使用 export *

5. 能力(Ability)与包管理

5.1 UIAbilityServiceExtensionAbility 的职责边界

  • 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 bundlemodulehap 的装配

  • APP 级别通过 bundle.json5/项目配置把多个 HAP 装配为一个应用;
  • 业务上可将 feature HAP 独立升级(视发布策略与商店规则);
  • entry HAP 只做容器与导航,不要塞业务逻辑。

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.json5oh-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"
}

(未完待续)

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐