1.1.0-beta.0 • Published 5 months ago

@hadss/hmrouter-plugin v1.1.0-beta.0

Weekly downloads
-
License
Apache-2.0
Repository
-
Last release
5 months ago

HMRouterPlugin

HMRouter路由框架编译插件,需要配合HMRouter使用

插件简介

HMRouter编译插件是一个基于hvigor的编译插件,用于:

  1. 扫描代码中的@HMRouter@HMInterceptor@HMLifecycle@HMAnimator@HMService等注解
  2. 生成路由表和NavDestination页面代码
  3. 生成混淆规则
  4. 支持自定义模板
  5. 编译时自定义扩展

插件安装与配置

工程级配置

在工程的hvigor/hvigor-config.json文件中添加路由编译插件:

{
  "dependencies": {
    "@hadss/hmrouter-plugin": "^1.1.0-beta.0"  // 使用最新版本
  },
  // ...其余配置
}

模块级配置

在使用HMRouter的模块中引入路由编译插件,修改hvigorfile.ts

// entry/hvigorfile.ts  entry模块的hvigorfile.ts
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
import { hapPlugin } from '@hadss/hmrouter-plugin';

export default {
  system: hapTasks,
  plugins: [hapPlugin()] // 使用HMRouter标签的模块均需要配置,与模块类型保持一致
}

// libHar/hvigorfile.ts  libHar模块的hvigorfile.ts
import { harTasks } from '@ohos/hvigor-ohos-plugin';
import { harPlugin } from '@hadss/hmrouter-plugin';

export default {
  system: harTasks,
  plugins:[harPlugin()]  // 使用HMRouter标签的模块均需要配置,与模块类型保持一致
}

// libHsp/hvigorfile.ts  libHsp模块的hvigorfile.ts
import { hspTasks } from '@ohos/hvigor-ohos-plugin';
import { hspPlugin } from '@hadss/hmrouter-plugin';

export default {
  system: hspTasks,
  plugins: [hspPlugin()]  // 使用HMRouter标签的模块均需要配置,与模块类型保持一致
}

注意:模块类型与插件类型必须匹配:

  • Har模块使用harPlugin()
  • Hsp模块使用hspPlugin()
  • Hap模块使用hapPlugin()

配置文件详解

在项目根目录或模块目录创建hmrouter_config.json文件,配置插件行为:

{
  // 扫描目录配置,如果不配置则扫描src/main/ets目录
  "scanDir": [
    "src/main/ets/components",
    "src/main/ets/interceptors"
  ],
  
  // 是否保留生成文件,默认为false
  "saveGeneratedFile": false,
  
  // 是否自动配置混淆规则,默认为false
  "autoObfuscation": false,
  
  // 默认模板文件,不配置时使用插件内置模板
  "defaultPageTemplate": "./templates/defaultTemplate.ejs",
  
  // 特殊页面模版文件,匹配原则支持文件通配符
  "customPageTemplate": [
    {
      "srcPath": [
        "**/component/Home/**/*.ets"
      ],
      "templatePath": "templates/home_shopping_template.ejs"
    },
    {
      "srcPath": [
        "**/live/**/*.ets"
      ],
      "templatePath": "templates/live_template.ejs"
    }
  ]
}

配置选项说明

配置项类型是否必填说明
scanDirarray指定扫描当前模块路径,默认值为src/main/ets
saveGeneratedFileboolean默认为 false,不保留插件自动生成的代码,如果需要保留,需要设置为 true
autoObfuscationboolean默认为 false,不自动配置混淆规则,只会生成hmrouter_obfuscation_rules.txt文件帮助开发者配置混淆规则;如果设置为 true,会自动配置混淆规则,并在编译完成后删除hmrouter_obfuscation_rules.txt文件,详细说明请参考混淆配置文档。
defaultPageTemplatestring默认模版路径,相对于hmrouter_config.json文件,例如:./templates/defaultTemplate.ejs
customPageTemplateobjectsrcPath为匹配的代码文件路径,支持通配符,templatePath为模版路径,可以实现不同的代码使用不同的模版来生成

配置文件读取规则

配置文件读取规则为 模块 > 工程 > 默认

优先使用本模块内的配置,如果没有配置,则使用工程根目录配置,若都找不到则使用默认配置

1.0.0-rc.6 版本开始,支持混淆配置autoObfuscation

1.0.0-rc.9 版本开始,支持自定义模版配置customPageTemplate

多模块配置

在多模块项目中,每个使用HMRouter的模块都需要配置插件。

方式一:每个模块单独配置

在每个模块的根目录下创建hmrouter_config.json文件,分别配置。

方式二:共享配置

在项目根目录创建hmrouter_config.json文件,所有模块共享配置。

如果需要为不同模块指定不同的配置,可以在模块根目录创建hmrouter_config.json文件,覆盖共享配置。

注意:当前多模块配置时不会做字段合并,即模块配置会直接覆盖工程配置

自定义模板使用

HMRouterPlugin 中,EJS模板用于生成动态页面或组件,可以在模板文件中使用 EJS 的语法

自定义模板使用场景

EJS语法可阅读参考官网链接

模板文件示例:

import { <%= componentName %> } from '<%= importPath %>'

@Builder
export function <%= componentName %>Builder(name: string, param: Object) {
  <%= componentName %>Generated()
}

@Component
export struct <%= componentName %>Generated {
  private pageUrl: string = '<%= pageUrl %>'

  build() {
    NavDestination() {
      <%= componentName %>()
    }
    <% if(dialog){ %>.mode(NavDestinationMode.DIALOG)<% } %>
    .hideTitleBar(true)
  }
}

模板文件中至少需要包含NavDestination组件代码和相对应的build函数,缺少会导致编译失败或者页面白屏,插件中会内置一套默认模板,其中包含了页面展示、生命周期注册、转场动画注册

默认模板介绍

插件默认模板根据HMRouter版本不同有两种实现:

1.1.0及以上版本

1.1.0及以上版本使用NavDestinationHelper类来处理页面生命周期和手势动画:

// auto-generated <%= componentName %>Builder.ets by HMRouter
import <%if(isDefaultExport) {%> <%= componentName %> <% } else {%> { <%= componentName %> } <% } %> from '<%= importPath %>'
import { NavDestinationHelper } from '@hadss/hmrouter';

@Builder
export function <%= componentName %>Builder(name: string, param: Object) {
  <%= componentName %>Generated()
}

export const <%= componentName %>BuilderWrapBuilder = wrapBuilder(<%= componentName %>Builder)

@Component
export struct <%= componentName %>Generated {
  private helper: NavDestinationHelper = new NavDestinationHelper(this);

  build() {
    NavDestination() {
      <%= componentName %>()
    }
    <% if(dialog){ %>.mode(NavDestinationMode.DIALOG)<% } %>
    .hideTitleBar(true)
    .attributeModifier(this.helper.modifier)
    .gestureModifier(this.helper.gestureModifier)
    .onWillAppear(()=>{this.helper.onWillAppear()})
    .onAppear(() => {this.helper.onAppear()})
    .onWillShow(()=>{this.helper.onWillAppear()})
    .onShown(()=>{this.helper.onShown()})
    .onWillHide(()=>{this.helper.onWillHide()})
    .onHidden(()=>{this.helper.onHidden()})
    .onWillDisappear(()=>{this.helper.onWillDisappear()})
    .onDisAppear(()=>{this.helper.onDisAppear()})
    .onReady((ctx)=>{this.helper.onReady(ctx)})
    .onBackPressed(()=> this.helper.onBackPressed())
  }
}

1.0.0版本

1.0.0版本使用TemplateService类来处理页面生命周期和动画:

// auto-generated <%= componentName %>Builder.ets by HMRouter
import <%if(isDefaultExport) {%> <%= componentName %> <% } else {%> { <%= componentName %> } <% } %> from '<%= importPath %>'
import { TemplateService, TranslateOption, ScaleOption, OpacityOption, HMRouterMgr } from '@hadss/hmrouter'

@Builder
export function <%= componentName %>Builder(name: string, param: Object) {
  <%= componentName %>Generated()
}

export const <%= componentName %>BuilderWrapBuilder = wrapBuilder(<%= componentName %>Builder)

@Component
export struct <%= componentName %>Generated {
  @State translateOption: TranslateOption = new TranslateOption()
  @State scaleOption: ScaleOption = new ScaleOption()
  @State opacityOption: OpacityOption = new OpacityOption()
  private pageUrl: string = ''
  private ndId: string = ''
  private navigationId: string = ''

  aboutToAppear(): void {
    this.navigationId = this.queryNavigationInfo()!.navigationId;
    const allPathName = HMRouterMgr.getPathStack(this.navigationId)?.getAllPathName();
    this.pageUrl = allPathName ? allPathName[allPathName.length - 1] : '';
    TemplateService.aboutToAppear(this.navigationId, this.pageUrl, <%= dialog %>,
      this.translateOption, this.scaleOption, this.opacityOption)
  }

  aboutToDisappear(): void {
    TemplateService.aboutToDisappear(this.navigationId, this.pageUrl, this.ndId)
  }

  build() {
    NavDestination() {
      <%= componentName %>()
    }
    <% if(dialog){ %>.mode(NavDestinationMode.DIALOG)<% } %>
    .hideTitleBar(true)
    .gesture(PanGesture()
      .onActionStart((event: GestureEvent) => {
        TemplateService.interactiveStart(this.navigationId, this.ndId, event)
      })
      .onActionUpdate((event: GestureEvent) =>{
        TemplateService.interactiveProgress(this.navigationId, this.ndId, event)
      })
      .onActionEnd((event: GestureEvent) =>{
        TemplateService.interactiveFinish(this.navigationId, this.ndId, event)
      })
      .onActionCancel(() =>{
        TemplateService.interactiveCancel(this.navigationId, this.ndId)
      })
    )
    .translate(this.translateOption)
    .scale(this.scaleOption)
    .opacity(this.opacityOption.opacity)
    .onAppear(() => {
      TemplateService.onAppear(this.navigationId, this.pageUrl, this.ndId)
    })
    .onDisAppear(() => {
      TemplateService.onDisAppear(this.navigationId, this.pageUrl, this.ndId)
    })
    .onShown(() => {
      TemplateService.onShown(this.navigationId, this.pageUrl, this.ndId)
    })
    .onHidden(() => {
      TemplateService.onHidden(this.navigationId, this.pageUrl, this.ndId)
    })
    .onWillAppear(() => {
      TemplateService.onWillAppear(this.navigationId, this.pageUrl)
    })
    .onWillDisappear(() => {
      TemplateService.onWillDisappear(this.navigationId, this.pageUrl, this.ndId)
    })
    .onWillShow(() => {
      TemplateService.onWillShow(this.navigationId, this.pageUrl, this.ndId)
    })
    .onWillHide(() => {
      TemplateService.onWillHide(this.navigationId, this.pageUrl, this.ndId)
    })
    .onReady((navContext: NavDestinationContext) => {
      this.ndId = navContext.navDestinationId!
      TemplateService.onReady(this.navigationId, this.pageUrl, navContext)
    })
    .onBackPressed(() => {
      return TemplateService.onBackPressed(this.navigationId, this.pageUrl, this.ndId)
    })
  }
}

版本差异说明

  1. NavDestinationHelper vs TemplateService

    • 1.1.0版本中,TemplateServiceNavDestinationHelper类替代
    • NavDestinationHelper采用实例化方式使用,而TemplateService则采用静态方法调用
    • NavDestinationHelper通过modifiergestureModifier简化了动画和手势处理
  2. 生命周期处理

    • 1.1.0版本使用更简洁的代理模式处理生命周期回调
    • 1.0.0版本需要手动调用每个静态方法并传递参数
  3. 动画处理

    • 1.1.0版本通过attributeModifiergestureModifier处理动画
    • 1.0.0版本需要手动管理TranslateOptionScaleOptionOpacityOption

注意:自定义模板时,请确保使用的API版本与HMRouter版本匹配。1.1.0及以上版本应使用NavDestinationHelper,1.0.0版本应使用TemplateService。

模板变量

属性描述
pageUrl标签中配置的pageUrl的值
importPath原组件的导入路径
componentName原组件名
dialog是否dialog页面

文件通配符说明

  • *:匹配任意数量的字符,不包括目录分隔符
  • **:匹配任意数量的字符,包括目录分隔符
  • ?:匹配单个字符
  • [abc]:匹配方括号中的任意一个字符
  • [a-z]:匹配a到z范围内的任意一个字符
  • {a,b,c}:匹配花括号中的任意一个模式
  • !:否定模式,排除匹配的文件

匹配优先级

  • customPageTemplate > defaultPageTemplate > 库中携带的模板
  • 如遇到customPageTemplate中的多个srcPath都能匹配上,优先取前面的模板文件

插件自定义扩展

插件扩展指南

编译产物

HMRouter编译插件生成的文件包括:

  1. 路由表文件src/main/resources/base/profile/hm_router_map.json,包含所有页面的路由信息
  2. rawfile资源文件src/main/resources/rawfile/hm_router_map.json, 包含所有页面的路由信息与本次编译中包含是hsp模块包名信息
  3. 页面代码文件src/main/ets/generated/HM{PageName}{Hash}.ets,为每个页面生成的NavDestination包装代码, 命名格式为HM + 组件名 + pageUrl的hash值
  4. 混淆规则文件hmrouter_obfuscation_rules.txt,包含需要保留的类名

hmrouter_config.json中设置saveGeneratedFile: true可保留所有编译产物

系统版本要求

最低版本:

DevEco Studio 5.0.3.700及以上
hvigor 5.5.1及以上

推荐使用

DevEco Studio 5.0.3.800及以上
hvigor 5.7.3及以上

低于DevEco Studio 5.0.3.800(hvigor 5.7.3)可能会导致远程的 HSP 中定义的 HMRouter 标签与路由表失效,会有如下 WARN 日志

[HMRouterPlugin] Your DevEco Studio version less than 5.0.3.800, may cause remote hsp dependencies get failed

常见问题

编译失败,提示重复的routerMap对象名称

hvigorERROR: Duplicate 'routerMap' object names detected.
     
* Try the following:
> Change the 'routerMap' object names listed below under routerMap in the respective router configuration files. Make sure the names are unique across the current module and the modules on which it depends.
{"home":["BootView"]}

解决方案: 1. 检查是否有多个页面使用了相同的pageUrl 2. 升级DevEco Studio版本,已知Build Version: 5.0.3.600版本会偶现该问题 3. 清理项目并重新编译

编译成功但找不到生成的文件

解决方案: 1. 在hmrouter_config.json中设置saveGeneratedFile: true,保留生成文件 2. 检查生成的文件是否正确,通常位于src/main/ets/generated目录 3. 确认编译插件配置正确,每个使用HMRouter的模块都需要配置对应的插件

多target问题

出现编译报错The "to" argument must be of type string. Received undefined

解决方案: 1. 确保一个target对应一个product,不要出现多余的target定义 2. 不要有一个模块中多个target给一个product的情况 3. 遵循官方说明:同一个module的不同target不能打包到同一个product中

使用建议

  1. 指定扫描目录:配置scanDir,减少编译时间
  2. 开发阶段保留生成文件:设置saveGeneratedFile: true,方便排查问题
  3. 启用自动混淆配置:设置autoObfuscation: true,自动处理混淆规则
  4. 使用自定义模板:根据项目需求,自定义页面模板
  5. 模块类型与插件类型匹配:确保使用正确的插件类型
  6. 定期清理生成文件:在发布前,设置saveGeneratedFile: false,清理生成文件
  7. 注意版本兼容性:确保自定义模板与HMRouter版本匹配

HMRouter路由框架使用

详见@hadss/hmrouter

1.1.0-beta.0

6 months ago

1.0.0-rc.11

10 months ago

1.0.0-rc.10

11 months ago

1.1.1-alpha.0

5 months ago

1.0.0-rc.9

1 year ago

1.0.0-rc.7

1 year ago

1.0.0-rc.8

1 year ago

1.0.0-rc.6

1 year ago

1.0.0-rc.5

1 year ago

1.0.0-rc.4

1 year ago

1.0.0-rc.3

1 year ago

1.0.0-rc.2

1 year ago

1.0.0-rc.1

1 year ago

1.0.0-rc.0

1 year ago