Bootstrap

[HarmonyOS] 解决HMRouter路由地址无法抽取的问题

解决HMRouter路由地址无法抽取的问题

背景

最近开始学习HarmonyOS开发,搭建项目的时候采用了 HMRouter 路由框架,在项目里使用到路由跳转,官方链接在这:

https://gitee.com/hadss/hmrouter/blob/master/HMRouterLibrary/README.md

但是发现一个比较严重的问题,就是路由地址无法抽取到一起进行管理,目前只能在当前的页面文件中写死路由地址,这样在项目里如果路由地址有修改,就需要在每一个页面文件里都进行修改,非常不方便。

上述表述可以用一个鲜明的例子来举证一下:

比如我们的项目结构如下:

harmony_os(项目名)
 |-entry
 |-features(业务组件模块路径)
    |-home(首页模块)
    |-mine(我的模块)
    |-...(其他业务模块)
    
 |-commons(公共组件模块路径)
    |-lib_net(网络组件)
    |-router_scheme(路由地址管理组件)
    |-...(其他公共组件)

大致如上的结构,我们希望的目标是,能够 在 router_scheme 模块里定义了所有路由地址常量,然后对于需要注册路由的 module,都来依赖这个 router_scheme 模块,这样在 router_scheme 模块里定义的路由地址常量,就能被其他 module 引用,从而避免在每一个页面文件里都写死路由地址,方便维护。

比如 将所有的 路由地址定义的常量存放到一个类或者N个类中(可以根据不同的业务module来拆分不同的类文件存放)

export class RouterConstants {
	public static readonly router_name_1: string = "router_name_1_value"
	public static readonly router_name_2: string = "router_name_2_value"
	public static readonly router_name_3: string = "router_name_3_value"
	public static readonly DemoNetPage: string = "routerscheme://entry/DemoNetPage"
	public static readonly DialogPage: string = "routerscheme://entry/DialogPage"
	public static readonly ProgramPage: string = "routerscheme://entry/ProgramPage"
}

如下图所示,红色的是路由 module,用来存放所有的路由常量类;绿色的是需要依赖路由常量类的 module,比如 feature1entry 模块,只需要依赖 router_scheme 模块,然后就可以直接引用 router_scheme 模块里定义的路由常量了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是。。。。。理想很丰满,现实很骨感啊。。。。。。。。。官方gitee 上好多 issue都提出,如果将常量抽取到单独的 har 中,会识别不到。。。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

备用链接

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是本着 办法总比困难多 的想法,还是想到了一个折中的实现方案。。。

实现方案

我们的初始目标是希望能够将所有的路由地址常量存放到一个类或者N个类中,然后对于需要注册路由的 module,都来依赖这个 router_scheme 模块,这样在 router_scheme 模块里定义的路由地址常量,就能被其他 module 引用,从而避免在每一个页面文件里都写死路由地址,方便维护。

那么现在既然官方不支持,或者说是还没支持,那我们就要想个办法,先将所有的路由常量集中到一个类中去,这样万一后续他们支持了,迁移也很方便了~~~

如下简述实现方案,技术有限,仅供参考哈~~~

主要分这么几个步骤:

- 1.实现插件,通过插件将常量类写入到每一个需要支持路由的module中,并且统一命名
- 2.添加路由键值对映射配置表(其实就是你所有的路由名字与路由地址的映射表),便于第一步的插件识别用来生成路由类
- 3.执行插件
- 4.代码引用

1.实现 HarmonyOS plugin

通过官方文档,查找到编写插件的逻辑,实现该插件,插件的主要目的是将路由映射表插入到每一个需要支持路由的 module 中,并且统一命名,方便插件识别。

将如下的 plugin 代码,命名为 RouterNamesGeneratePlugin.ts,放在 config 文件夹下,然后在项目的根路径的 hvigorfile.ts 中引入,如下所示:

这样后续只要执行 sync 就会执行下述插件代码

源码如下:

import { FileUtil, hvigor, getNode, HvigorNode, HvigorTask, HvigorPlugin } from '@ohos/hvigor';

// 实现自定义插件,sync的时候自动生成路由类
export function RouterNamesGeneratePlugin(): HvigorPlugin {
  // 路由表JSON文件地址
  let namesFilePath = "config/router_names.json5"
  // 路由生成规则配置文件地址
  let configFilePath = "config/router_config.json5"
  // 路由类文件名称
  let fileName = "RouterConstants"

  return {
    pluginId: 'RouterNamesGenerate',
    apply(node: HvigorNode) {
      // 路由数据源
      let routerNames = FileUtil.readJson5(namesFilePath)
      let routerList = routerNames['rent'] // 路由对象列表
      if (!routerList) {
        console.log(`debug_hvigorfile: routerList is null`)
        return
      }

      // 路由配置表
      let routerConfig = FileUtil.readJson5(configFilePath)
      let moduleNames = routerConfig['moduleNames'] // 路由对象列表
      if (!moduleNames) {
        console.log(`debug_hvigorfile: moduleNames 配置的module为空,不生成路由类 `)
        return
      }
      console.log(`debug_hvigorfile: 路由配置表 ↓↓↓↓↓↓↓↓↓↓↓↓`)
      moduleNames.forEach(element => {
        console.log(`debug_hvigorfile: moduleName=${element}`)
      })
      console.log(`debug_hvigorfile: 路由配置表 ↑↑↑↑↑↑↑↑↑↑↑↑`)

      console.log(`debug_hvigorfile: 路由表原始数据 ↓↓↓↓↓↓↓↓↓↓↓↓`)
      routerList.forEach(element => {
        console.log(`debug_hvigorfile: key=${element["key"]}, value=${element["value"]}`)
      })
      console.log(`debug_hvigorfile: 路由表原始数据 ↑↑↑↑↑↑↑↑↑↑↑↑`)

      // 获取所有节点
      const allNodes = hvigor.getAllNodes();
      for (let i = 0; i < allNodes.length; i++) {
        let nodeName = allNodes[i].getNodeName()
        let nodeDir = allNodes[i].getNodeDir()
        let getPath = nodeDir.getPath()

        // console.log(`debug_hvigorfile: module: nodeName=${nodeName}, getPath=${getPath}`)
        if (moduleNames.includes(nodeName)) {
          // console.log(`debug_hvigorfile: module: nodeName=${nodeName}, getPath=${getPath}  可以写入`)
          let newFilePath = getPath + `/src/main/ets/${fileName}.ets`
          // 判断是否存在
          FileUtil.ensureFileSync(newFilePath)
          let exist2 = FileUtil.exist(newFilePath)
          // console.log(`debug_hvigorfile: exist2: ${exist2}`)

          let date = new Date()
          let formatDate = date.toLocaleString()
          let tips = `// 自动生成,请勿修改 \n// 生成时间:${formatDate}`

          let a = `${tips} \nexport class ${fileName} {\n\t`
          let length = routerList.length
          for (let j = 0; j < length; j++) {
            let element = routerList[j]
            // console.log(`debug_hvigorfile: 开始写入文件>>>>> key=${element["key"]}, value=${element["value"]}`)
            let key = element["key"]
            let value = element["value"]
            let prefix = (j == length - 1) ? "\n" : "\n\t"
            a += `public static readonly ${key}: string = "${value}"${prefix}`
          }
          a += `}`
          // 备注:这个 写入文件,不要使用异步写入,异步写入的话会存在先执行 HMRouter 的插件的case,就容易出问题了,需要保证在执行 HMRouter 插件之前,生成完所有的路由映射类
          FileUtil.writeFileSync(newFilePath, a)
          console.log(`debug_hvigorfile: moduleName[${nodeName}]: 写入完成`)
        }
      }

      // FileUtil.readFile('entry/test.txt').then(function(data) {
      //   console.log('readFile: ' + data);
      // });
    }
  }
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


export default {
  system: appTasks,
  plugins: [RouterNamesGeneratePlugin()]
}

// 注册Task, 用于在添加路由之后,执行此任务来重新生成路由类
// 执行: hvigorw generateRouterNames  即可重新生成新的路由类
node.registerTask({
  name: 'generateRouterNames',
  run() {
    // 自动执行 RouterNamesGeneratePlugin
  }
});

2.添加配置信息

在项目的根路径下,新建 config 目录,将上述的 RouterNamesGeneratePlugin.ts 文件放进去,然后新建下述两个 json5 文件

  • 1.router_names.json5:用于存放所有的路由映射关系,json5的格式存储,键值对存储便于记录
  • 2.router_config.json5:用于配置那些 module 需要生成路由类,格式具体参考下方

举例:

router_names.json5 文件的内容规范如下:

  • rent: 便于后续区分不同的业务线,目前只设置这一个也行
  • key: 路由的名字,也就是最终在Page文件中的路由名称
  • value: 路由的具体地址
{
  "rent": [
    {
      "key": "DemoNetPage",
      "value": "routerscheme://entry/DemoNetPage"
    },
    {
      "key": "DialogPage",
      "value": "routerscheme://entry/DialogPage"
    },
    {
      "key": "ProgramPage",
      "value": "routerscheme://entry/ProgramPage"
    }
  ]
}

router_config.json5 文件的内容规范如下:

  • moduleNames: 用于配置需要生成路由类的module名字,需要在这配置一下

比如我的例子中,是要在 feature1entry 两个 module 中生成路由类,那么就将这俩module的名字配置一下。。。

{
    // 配置:需要生成路由类的module名字,需要在这配置一下
    "moduleNames": [
    "entry",
    "feature1"
    ]
}

3.执行插件

上述第一、第二步完成之后,就可以执行插件了,执行命令如下:
在项目根路径中的命令行中执行: hvigorw generateRouterNames 即可重新生成新的路由类;或者后续每次添加新的路由映射,也可以用它来执行重新生成路由映射类。

或者直接在项目根路径的 hvigorfile.ts 中随便回车一下,触发 sync 也可以执行构建。

hvigorw generateRouterNames

插件执行完毕后,会在 entryfeature1 两个 module 中生成 RouterConstants.ets 文件,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4.代码引用

完成上述三步之后,那么在模块中就可以直接引用了,比如 我定义了 router_name_3 这个路由,那么在模块中就可以这样引用了:

import { RouterConstants } from “…/RouterConstants”;
@HMRouter({ pageUrl: RouterConstants.router_name_3 })

就这么简单~~~~~~

import { HMRouter } from "@hadss/hmrouter";
import { RouterConstants } from "../RouterConstants";

@HMRouter({ pageUrl: RouterConstants.router_name_3 })

@Entry
@Component
export struct MainPage {
  @State message: string = 'Hello feature1_MainPage: ' + RouterConstants.router_name_3;

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(10)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
    }
    .height('100%')
  }
}

至此,完结有需要的小伙伴拿走不谢~
更加希望 HMRouter 后续能够支持常量抽取~~~

感谢~~~

;