鸿蒙应用构建流程与动态构建实践

 

本文讲解开发 HarmonyOS NEXT 应用时候 IDE 是如何进行构建已经构建工具开放了哪些可利用的 hook ,并学以致用,讲解在我的团队进行鸿蒙应用开发时候,如何利用这些 hook。

前言

从问题出发,在官方的 最佳实践–应用导航设计 中,实现一个页面的路由需要大约4个步骤,过程略显繁琐。

  1. 新建 LoginPage.ets 页面编码登录页面组件;
  2. 提供构造函数与调用将构造函数注册到路由表的方法;
  3. 在需要被跳转的模块入口声明开放函数给基础路由库调用;
  4. 在主hap模块配置动态import变量表达式加入编译.
// 项目下的 ./features/login/src/main/ets/pages/LoginPage.ets

// 这里提供登录页面(组件)的构造函数
@Builder
export function loginPageBuilder() {
  LoginPage()
}
 
// 调用基础路由库的方法将页面构造函数注册到路由表,跳转时候会调用生成页面组件
RouterModule.registerBuilder(BuilderNameConstants.LOGIN_LOGINPAGE, wrapBuilder(loginPageBuilder));

// 组件渲染代码
@Component
struct LoginPage {
 
    build(){
        // 这里必须使用 NavDestination 包裹住你的页面
        NavDestination() {
            ...
        }
    }
}

其中第2步和第3步生成组件构造函数和将组件构造函数注册到路由表的部分是每一个页面组件都需要配置的,可重复的,那么是否有方法自动生成呢?

Java 语言 的服务器端开发框架 SpringBoot 有强大的基于装饰器插桩生成模板代码机制, Node.js 服务器端应用程序的开发框架 NestJs 也有基于装饰器的插桩生成机制。那么鸿蒙有没有?

鸿蒙构建流程

javaScript 的依赖管理和构建能力都由 npm 来负责不一样。鸿蒙的依赖管理和构建工具是分开两个工具管理的。

依赖管理工具是 ohpm, 构建工具是 hvigor

hvigor 构建工具是一款基于TS实现的构建任务编排工具,主要提供任务管理机制,包括任务注册编排、工程模型管理、配置管理等关键能力,提供专用于构建和测试应用的流程和可配置设置。

DevEco Studio使用构建工具hvigor来自动执行和管理构建流程,实现应用/服务构建任务流的执行,完成HAP/APP的构建打包。

我们为实现基于装饰器的构建插桩生成机制,就是利用 hvigor 构建工具的插入点。下面是官方给出的构建生命周期与可利用的hook示意。

鸿蒙构建生命周期及hook点

构建的生命周期中hvigor使用两个脚本文件来完成插件、任务以及生命周期hook的注册:

  • hvigorconfig.ts:此文件在整个项目中只有根目录下存在一份,不是构建必须文件并且默认不存在,如有需要可自行创建,此文件被解析执行的时间较早,可用于在hvigor生命周期刚开始时操作某些数据。
  • hvigorfile.ts:此文件在每个node下都有一份,是构建的必须文件,在此文件中可以注册插件、任务以及生命周期hook等操作。

动态构建实践

插件实现路由页面组件自动封装自定义构建函数与注册到系统路由表

还记得前文提到的 最佳实践–应用导航设计 步骤复杂吗,也是因为这个原因,我在之前的文章 [https://blog.abbenyyy.cn/2024/05/19/鸿蒙应用路由Navigation介绍与项目应用.html] 中介绍到团队最终选择了 Navigation 的系统路由表方案。而要使用系统路由表方案,实现一个一个页面的路由需要下面3步。

  1. 新建 LoginPage.ets 页面编码登录页面组件;
  2. LoginPage.ets 页面使用 @Builder 封装自定义构建函数;
  3. 将自定义构建函数注册到系统路由表.

3个步骤比 最佳实践–应用导航设计 步骤清晰而且简单。 我们可以发现第二步和第三步是有规则的


// 2.自定义构建函数
@Builder
export function loginPageBuilder() {
  LoginPage()
}

// 1.组件渲染代码
@Component
struct LoginPage {
 
    build(){
        // 这里必须使用 NavDestination 包裹住你的页面
        NavDestination() {
            ...
        }
    }
}
// 3.将 页面自定义构建函数 注册到 系统路由表 src/main/resources/base/profile/route_map.json
{
  "routerMap": [
    {
      "name": "LoginPage",
      "pageSourceFile": "src/main/ets/pages/LoginPage.ets",
      "buildFunction": "loginPageBuilder"
    },
    ...
  ]
}

那么我们就可以利用hvigor插件插入到每一个子模块的执行阶段,遍历固定 page 目录下的代码,若符合某种规则就自动将路由页面组件自动实现自定义构建函数与注册到路由表.

这里我简单地讲一下这个插件的工作流程与原理

鸿蒙自动注册路由插件流程

hvigor插件实际上是一个node项目,而鸿蒙的 arkTs 是 typeScipt 的子集.
因此我这边使用 TypeScript 库来解析项目代码,盘点是否含有需要自动注册的路由装饰器. 使用node的api与 Handlebars 根据模版生成路由自定义构建函数的合并文件. 再使用 JSON5 重写模块配置文件 module.json5.

hvigorfile.ts 初始化实现私有库本地依赖开发更新

根据官方的最佳实践-分层架构设计 , 会将代码模块分为三个层次:产品定制层、基础特性层和公共能力层。我们的团队由于有多个鸿蒙应用,因此我们会将部分公共能力层做封装成单独的基础库har ,上传到私有仓库做远程分发。

但在特定情况下,我们需要使用到基础库的源码依赖到应用中,例如需要对基础库功能进行迭代。那么我们如何兼顾到基础库、功能应用的功能开发同时在一个项目中进行迭代中。

我们的解决思路是利用项目根目录下的 oh-package.json5 文件的依赖配置会覆盖所有模块的依赖声明的规则,在项目根目录下的 oh-package.json5 文件声明基础库的依赖为本地依赖。

// 模块 oh-package.json5 文件
{
  ...
  "dependencies": {
     "@dsl/kit_router": "0.0.4",
     ...
   }
}
// 项目根目录 oh-package.json5 文件
{
  ...
  "dependencies": {
     "@dsl/kit_router": "file:./commons/kit_router",
     ...
   }
}

项目根目录下的 oh-package.json5 文件对于基础库的依赖声明会覆盖模块的声明,就会让所有模块对于该基础库的远程依赖失效,变为本地依赖。

那么如何自动下载基础库的源码到本地呢?我们利用 hvigor脚本文件 在项目构建生命周期时候提前克隆基础库代码到本地并声明本地依赖。

// 项目根目录下的 hvigorconfig.ts
import { hvigor } from '@ohos/hvigor'

// 是否启动下载基础库的源码到本地. 修改这里为true后可以修改基础库声明目录下的 index文件 的 kitDependencies 声明下的 local 字段是否为真,对于单个基础库是否克隆到本地.
const startCloneKitToLocal = false

// 获取当前文件所在目录
const currentDir = process.cwd()
// 获取基础库专用目录
const commonsDir = path.join(currentDir, 'commons')
// 获取基础库声明目录
const kitDependenciesDir = path.join(currentDir, 'kit_dependencies')

/**
 * 遍历基础依赖,若不在本地就克隆下来
 */
const checkKitDependencies = () => {
  for(const kitDependency of kitDependencies) {
    const { shell } = os.userInfo()
    console.log(kitDependency)
    if(kitDependency.local) {
      const kitRouterDir = path.join(currentDir, 'commons', kitDependency.name);
      // 检查文件夹是否存在
      if (!fs.existsSync(kitRouterDir)) {
        // 文件夹不存在,执行适当的操作
        console.log(`${kitDependency.name}不存在`)
        execSync(`git clone ${kitDependency.gitUrl}`, { cwd: commonsDir })
        console.log(`'git clone ${kitDependency.name} 成功--进行动态添加模块'`)
        // 执行加入本地依赖,这一步需要你在系统变量加入 ohpm 命令,不然可以手动一个个依赖声明到项目根目录下的 oh-package.json5
        mHvigorConfig.includeNode(kitDependency.name, `./commons/${kitDependency.name}`)
      } else {
        console.log(`${kitDependency.name}已在本地--进行动态添加模块`)
        mHvigorConfig.includeNode(kitDependency.name, `./commons/${kitDependency.name}`)
      }
    }
  }
}


if(startCloneKitToLocal) {
  // 动态import基础库声明文件
  dynamicImport()
  // 遍历声明文件里的基础依赖列表,若不在本地就克隆下来
  checkKitDependencies()
}

// 利用hook生命周期.在项目构建的初始化阶段配置
hvigor.configEvaluated((hvigorConfig)=>{
  console.log('---project--configEvaluated---')
  mHvigorConfig = hvigorConfig
})

我们利用项目构建的初始化阶段配置的 configEvaluated 的 hook,只需要修改 布尔值 startCloneKitToLocal ,就一键切换到对本地基础库的依赖,方便我们更新基础库的代码与版本.

总结

本文从两个实践案例讲解如何利用鸿蒙的构建hook,希望对大家有所启发,感谢。