鸿蒙构建工具hvigor

462 阅读14分钟

一、简介

  hvigor构建工具是一款基于TS实现的构建任务构建工具,鸿蒙使用构建工具hvigor来完成HAP/APP的构建打包。hvigor将工程解析为一个树形结构,项目为树的根节点,hvigor将项目或模块称为一个节点。本文将详细的介绍hvigor生命周期、hvigor任务、hvigor插件。

二、hvigor生命周期

  hvigor生命周期分为初始化、配置和执行。

2、1初始化

  • 根据命令参数和hvigor-config.json5文件中的配置,设置hvigor的构建参数,并构造出hvigor对象和hvigorConfig对象
  • 通过项目根目录下的build-profile.json5文件,创建出根节点的节点描述对象,并通过其中的modules字段,来初始化出工程中所有模块的节点描述对象。
  • 执行项目根目录下的hvigorconfig.ts文件,可以在此文件中通过hvigor的相关API来为生命周期注册hook或在构建开始时进行其他处理。
  • 根据节点描述对象构造出每个节点的HvigorNode对象实例。

2、2配置

  • 执行每个节点(项目或者模块)中的hvigorfile.ts文件,为每个节点添加插件(向hvigor注册任务),执行插件的apply方法,并添加插件上下文。
  • 根据前一步加载出的插件和任务,根据任务执行的依赖关系构造出有向无环图。

2、3执行

  • 执行选定的任务。
  • 任务之间的依赖关系决定了任务执行顺序。
  • 任务可以并行执行。

2、4生命周期及hook点

  在hvigor的生命周期中有多个hook点可供使用,下图中所有绿色标记的线框为可以使用的hook点。 image.png

三、hvigor任务

3、1了解任务

  任务是hvigor构建过程中的执行基本单元,任务中通常包含一段编译过程处理的可执行代码;一个任务可以依赖其他多个任务。hvigor任务调度执行时通过解析依赖关系确定任务执行时序。
  下图是在打包过程中执行的一些任务,打包其实就是按照有向无环图执行任务。
  UP-TO-DATE是任务标识,表示任务未实际执行。hvigor任务增量跳过机制,在二次执行任务时检测任务输入输出条件未发生变化,则任务跳过执行提高构建效率。
  default@MakePackInfo、default@ProcessProfile等是任务名称。 image.png

3、2注册任务

  在打包过程中,除了已经内置的任务,开发者可以自行注册任务。

1、编辑工程下hvigorfile.ts文件。

```
// 导入模块
import { getNode, HvigorNode, HvigorTask } from '@ohos/hvigor';
```

2、编写任务代码。

// 获取当前hvigorNode节点对象
const node: HvigorNode = getNode(__filename);

// 注册Task
node.registerTask({
   name: 'customTask',
   run() {
       console.log('这是一个任务');
   }
});

3、执行任务。

hvigorw customTask

  需要注意的是,目前编译器对于ts文件不支持代码提示,直接在hvigorfile.ts文件中敲代码跟使用记事本敲代码没区别,非常容易出错。为了解决这个问题,可以使用VS Code,后面会教大家如何使用VS Code。

四、hvigor插件

  hvigor允许开发者开发自己的插件,并与他人共享。hvigor主要提供了两种方式来实现插件:基于hvigorfile脚本开发插件、基于typescript项目开发。

4、1基于hvigorfile脚本开发

  优点是可实现快速开发,直接编辑工程或模块下hvigorfile.ts即可编写插件代码。不足之处是在多个项目中,无法方便的进行插件代码的复用和共享分发。并且编译器对于ts文件不支持代码提示,直接在hvigorfile.ts文件中敲代码跟使用记事本敲代码没区别,非常容易出错。

1、编辑工程下hvigorfile.ts文件。

// 导入模块
import { HvigorPlugin, HvigorNode } from '@ohos/hvigor';

2、编写插件代码。在hvigorfile.ts中定义插件方法,实现HvigorPlugin接口。

// 实现自定义插件
function customPlugin(): HvigorPlugin {
   return {
       pluginId: 'customPlugin',
       apply(node: HvigorNode) {
           // 插件主体
           console.log('这是个自定义插件');
       }
   }
}

3、在导出声明中使用插件。

export default {
   system: appTasks,
   plugins:[
       customPlugin()  // 应用自定义Plugin
   ]
}

4、执行hvigor命令时,在hvigor生命周期配置阶段执行插件中的apply方法。

image.png

4、2使用VS Code基于typescript项目开发

  基于typescript项目开发较好地弥补了上一小节中使用hvigorfile脚本方式编写插件代码不易复用和共享分发的问题。同时可以使用VS Code开发,这样就有代码提示。因此通常情况下推荐此方式开发。

4、2、1初始化typescript项目

  1、使用node -v命令查看node.js版本,如果node.js版本低于18.14.1,先将node.js版本升级到18.14.1或者以上,否则有些依赖会下载失败。

node -v

  2、在鸿蒙项目的根目录下创建空目录,使用VS Code打开目录。 image.png   3、在VS Code的菜单栏中依次点击终端——新建终端,在终端依次输入下面的命令。

// 全局安装TypeScript,如果已经全局安装了TypeScript,就不用执行此命令
npm install typescript -g
// 初始化一个npm项目
npm init
// 初始化TypeScript配置文件
tsc --init

  4、typescript项目初始化完成。

image.png

4、2、2开发插件

  1、配置npm镜像仓库地址。在用户目录下创建或打开.npmrc文件,配置如下信息:

registry=https://repo.huaweicloud.com/repository/npm/
@ohos:registry=https://repo.harmonyos.com/npm/

  2、打开package.json添加devDependencies配置。

"devDependencies": {
    "@ohos/hvigor": "^5.8.9",
    "@ohos/hvigor-ohos-plugin": "^5.8.9"
}

  3、执行如下命令安装依赖。

npm install

  在node_modules目录下能够看到hvigor目录和hvigor-ohos-plugin目录,说明依赖下载成功。

image.png

  4、打开tsconfig.json,将tsconfig.json里面的配置替换成下面的配置。

{
  "compilerOptions": {
    "target": "ES2021",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "sourceMap": false,
    "removeComments": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true
  },
  "include": ["./src/**/*"],
  "exclude": ["./node_modules/", "./dist/", "./tests/"]
}

  5、在plugin目录下新建src目录和index.ts文件,在src目录下新建custom-plugin.ts,custom-plugin.ts的名字可以改,只要是ts文件即可。最终就有下图中这些文件。 image.png

  6、打开custom-plugin.ts文件,添加下面的代码。如下代码,添加customPlugin函数,返回HvigorPlugin。pluginId指定插件的名字,重写apply方法,在apply方法中添加输出语句。

import {HvigorPlugin, HvigorNode} from '@ohos/hvigor'

export function customPlugin(): HvigorPlugin {
    return {
        pluginId: 'customPlugin',
        apply(node: HvigorNode) {
            console.log('这是一个自定义插件')
        }
    }
}

  7、打开index.ts文件,在该文件中声明插件方法的导出。

export {customPlugin} from './src/custom-plugin'

  8、在终端执行npm pack命令,在plugin目录下就生成了plugin-1.0.0.tgz文件。

npm pack

image.png

  9、回到DevEco-Studio,打开hvigor目录下的hvigor-config.json文件,在dependencies添加下面的代码,将上一步生成的tgz文件引入。

{
"modelVersion": "5.0.0",
"dependencies": {
  "@hadss/hmrouter-plugin": "file:../HMRouterPlugin/hadss-hmrouter-plugin-1.0.0.tgz",
  "@shijing/plugin": "file:../plugin/plugin-1.0.0.tgz" /*引入tgz文件*/
},
"execution": {
  // "analyze": "default",                    /* Define the build analyze mode. Value: [ "default" | "verbose" | false ]. Default: "default" */
  // "daemon": true,                          /* Enable daemon compilation. Value: [ true | false ]. Default: true */
  // "incremental": true,                     /* Enable incremental compilation. Value: [ true | false ]. Default: true */
  // "parallel": true,                        /* Enable parallel compilation. Value: [ true | false ]. Default: true */
  // "typeCheck": false,                      /* Enable typeCheck. Value: [ true | false ]. Default: false */
},
"logging": {
  // "level": "info"                          /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
},
"debugging": {
  // "stacktrace": false                      /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
},
"nodeOptions": {
  // "maxOldSpaceSize": 4096                  /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process */
}
}

  10、打开entry目录下hvigorfile.ts,导入customPlugin,将自定义插件添加到export default的plugins中。

import { harTasks } from '@ohos/hvigor-ohos-plugin';
import { customPlugin } from '@shijing/plugin';

export default {
    system: harTasks,  
    plugins:[customPlugin()] /* 应用自定义插件 */
}

  11、打开DevEco-Studio的终端,执行hvigorw --sync命令。

hvigorw --sync

  在第6步中,我们在apply方法中添加了输出语句,执行hvigorw --sync命令,就会调用apply方法。 image.png   12、回到VS Code,我们修改apply方法,再添加两条输出语句。

import {HvigorPlugin, HvigorNode} from '@ohos/hvigor'

export function customPlugin(): HvigorPlugin {
  return {
      pluginId: 'customPlugin',
      apply(node: HvigorNode) {
          console.log('开始执行插件')
          console.log('这是一个自定义插件')
          console.log('结束执行插件')
      }
  }
}

  13、修改完apply方法后,在VS Code的终端执行npm pack,这样就生成了一个新的plugin-1.0.0.tgz文件。

  14、回到DevEco-Studio,打开DevEco-Studio的终端,执行hvigorw --sync命令。

image.png   如上图,明明我们添加了三条输出语句,但还是只输出了一条语句。虽然我们生成了新的plugin-1.0.0.tgz文件,但由于文件的版本号并没有发生改变,DevEco-Studio认为文件没有发生变化,就不会同步新的plugin-1.0.0.tgz文件,仍然使用的是老的plugin-1.0.0.tgz文件。其实我们只要修改版本号,DevEco-Studio就会同步新的tgz文件

  15、回到VS Code,打开package.json,将version字段修改为1.0.1。在VS Code的终端执行npm pack,这样就生成了一个新的plugin-1.0.1.tgz文件。

image.png   16、回到DevEco-Studio,打开hvigor/hvigor-config.json文件,按照下图操作。

image.png   如下图所示,输出了三条语句。 image.png

五、hvigor插件实战

  当打包hap的时候,会在entry/build/default/outputs/default目录下生成entry-default-signed.hap和entry-default-signed.hap,这两个hap的文件名并没有带上版本号,也没有带上项目名称,为此需要修改文件名。在上一节中,我们学会如何自定义插件,现在我们就来实现一个修改文件名的插件。

5、1新建文件

  新建rename.ts,添加下面的代码。

import { HvigorNode, HvigorPlugin, HvigorTaskContext } from "@ohos/hvigor";
import { OhosAppContext, OhosPluginId } from '@ohos/hvigor-ohos-plugin';
import { AppJson } from "@ohos/hvigor-ohos-plugin/src/options/configure/app-json-options";
import fs from 'fs'

export function rename(): HvigorPlugin {
  return {
      // 插件名称
      pluginId: 'renameHapPlugin',
      apply(node: HvigorNode) {
          
      }
  }
}

5、2注册任务

  由于我们需要在打包期间执行插件,那就需要在插件中自定义任务。如下代码,在插件的apply方法中调用HvigorNode的registerTask方法注册任务,指定任务名称,当执行任务的时候,就会调用run方法。

export function rename(): HvigorPlugin {
  return {
      // 插件名称
      pluginId: 'renameHapPlugin',
      apply(node: HvigorNode) {
          // 注册任务,指定任务名称,当执行任务的时候,就会调用run方法
          node.registerTask({
              // 任务名称
              name: 'renameHapTask',
              run: (taskContext: HvigorTaskContext) => {
                  
              }
          })
      }
  }
}

5、3拿到模块名和模块路径

  在run方法里面,通过HvigorTaskContext拿到模块名和模块路径。假设我们在entry目录的hvigorfile.ts文件调用插件,那拿到的模块名就是entry,模块路径就是entry模块的绝对路径。

export function rename(): HvigorPlugin {
  return {
      // 插件名称
      pluginId: 'renameHapPlugin',
      apply(node: HvigorNode) {
          // 注册任务,指定任务名称,当执行任务的时候,就会调用run方法
          node.registerTask({
              // 任务名称
              name: 'renameHapTask',
              run: (taskContext: HvigorTaskContext) => {
                  // 获取模块名
                  const moduleName = taskContext.moduleName
                  // 获取模块路径
                  const modulePath = taskContext.modulePath
                  // 假设我们在entry目录的hvigorfile.ts文件调用插件,那拿到的模块名就是entry,模块路径就是entry模块的绝对路径。
                  console.log(`模块名:${moduleName}`)
                  console.log(`模块路径:${modulePath}`)
              }
          })
      }
  }
}

5、4获取原文件目录,创建新文件目录

  如下图所示,生成的hap会在entry/build/default/outputs/default目录下。既然修改文件名,那就需要拿到原文件。在上一步,我们拿到了模块所在的路径,进而就可以拼接出原文件所在的路径。同时还需要创建新文件所在的目录。 image.png

  export function rename(): HvigorPlugin {
    return {
        // 插件名称
        pluginId: 'renameHapPlugin',
        apply(node: HvigorNode) {
            // 注册任务,指定任务名称,当执行任务的时候,就会调用run方法
            node.registerTask({
                // 任务名称
                name: 'renameHapTask',
                run: (taskContext: HvigorTaskContext) => {
                    // 获取模块名
                    const moduleName = taskContext.moduleName
                    // 获取模块路径
                    const modulePath = taskContext.modulePath
                    // 假设我们在entry目录的hvigorfile.ts文件调用插件,那拿到的模块名就是entry,模块路径就是entry模块的绝对路径。
                    console.log(`模块名:${moduleName}`)
                    console.log(`模块路径:${modulePath}`)
                    // hap所在路径
                    const originSignFilePath = `${modulePath}/build/default/outputs/default/${moduleName}-default-signed.hap`
                    const originUnsignFilePath = `${modulePath}/build/default/outputs/default/${moduleName}-default-unsigned.hap`
                    console.log(`原签名文件路径:${originSignFilePath}`)
                    console.log(`原未签名文件路径:${originUnsignFilePath}`)
                    // 新文件所在的目录
                    const targetFileDir = `${modulePath}/build/default/outputs/default/target`
                    // 创建目录
                    fs.mkdir(targetFileDir, {recursive: true}, (err) => {
                        console.log(`目录创建失败:{err}`)
                    })
                }
            })
        }
    }
}

5、5指定新文件的文件名

  我们希望新文件的文件名带上项目名和版本。拿到父节点和OhosAppContext,就能获取到项目名。版本的话,我们希望读取AppScope目录下app.json文件里面的versionName字段。调用OhosAppContext的getAppJsonOpt,就能获取到app.json5文件中内容的obj对象,拿到versionName字段。拿到项目名和版本,就能确定新文件的文件名了。

export function rename(): HvigorPlugin {
    return {
        // 插件名称
        pluginId: 'renameHapPlugin',
        apply(node: HvigorNode) {
            // 注册任务,指定任务名称,当执行任务的时候,就会调用run方法
            node.registerTask({
                // 任务名称
                name: 'renameHapTask',
                run: (taskContext: HvigorTaskContext) => {
                    // 获取模块名
                    const moduleName = taskContext.moduleName
                    // 获取模块路径
                    const modulePath = taskContext.modulePath
                    // 假设我们在entry目录的hvigorfile.ts文件调用插件,那拿到的模块名就是entry,模块路径就是entry模块的绝对路径。
                    console.log(`模块名:${moduleName}`)
                    console.log(`模块路径:${modulePath}`)
                    // hap所在路径
                    const originSignFilePath = `${modulePath}/build/default/outputs/default/${moduleName}-default-signed.hap`
                    const originUnsignFilePath = `${modulePath}/build/default/outputs/default/${moduleName}-default-unsigned.hap`
                    console.log(`原签名文件路径:${originSignFilePath}`)
                    console.log(`原未签名文件路径:${originUnsignFilePath}`)
                    // 新文件所在的目录
                    const targetFileDir = `${modulePath}/build/default/outputs/default/target`
                    // 创建目录
                    fs.mkdir(targetFileDir, {recursive: true}, (err) => {
                        console.log(`目录创建失败:{err}`)
                    })
                    // 获取父节点
                    const parentNode = node.getParentNode()
                    // 获取OhosAppContext
                    const appContext = parentNode?.getContext(OhosPluginId.OHOS_APP_PLUGIN) as OhosAppContext
                    // 获取项目名
                    const projectName = appContext.getProjectName()
                    console.log(`项目名:${projectName}`)
                    // 获取AppScope目录下app.json文件里面的json
                    const appOptObj: AppJson.AppOptObj = appContext.getAppJsonOpt()
                    // 获取AppScope目录下app.json文件里面的versionName字段
                    const versionName = appOptObj.app.versionName
                    // 新文件路径
                    const targetSignFilePath = `${modulePath}/build/default/outputs/default/target/${projectName}-${versionName}-default-signed.hap`
                    const targetUnsignFilePath = `${modulePath}/build/default/outputs/default/target/${projectName}-${versionName}-default-unsigned.hap`
                }
            })
        }
    }
}

5、6复制文件

  原文件存在的情况下才复制。

export function rename(): HvigorPlugin {
    return {
        // 插件名称
        pluginId: 'renameHapPlugin',
        apply(node: HvigorNode) {
            // 注册任务,指定任务名称,当执行任务的时候,就会调用run方法
            node.registerTask({
                // 任务名称
                name: 'renameHapTask',
                run: (taskContext: HvigorTaskContext) => {
                    console.log(`开始执行重命名任务`)
                    // 获取模块名
                    const moduleName = taskContext.moduleName
                    // 获取模块路径
                    const modulePath = taskContext.modulePath
                    // 假设我们在entry目录的hvigorfile.ts文件调用插件,那拿到的模块名就是entry,模块路径就是entry模块的绝对路径。
                    console.log(`模块名:${moduleName}`)
                    console.log(`模块路径:${modulePath}`)
                    // hap所在路径
                    const originSignFilePath = `${modulePath}/build/default/outputs/default/${moduleName}-default-signed.hap`
                    const originUnsignFilePath = `${modulePath}/build/default/outputs/default/${moduleName}-default-unsigned.hap`
                    console.log(`原签名文件路径:${originSignFilePath}`)
                    console.log(`原未签名文件路径:${originUnsignFilePath}`)
                    // 新文件所在的目录
                    const targetFileDir = `${modulePath}/build/default/outputs/default/target`
                    // 创建目录
                    fs.mkdir(targetFileDir, {recursive: true}, (err) => {
                        console.log(`目录创建失败:{err}`)
                    })
                    // 获取父节点
                    const parentNode = node.getParentNode()
                    // 获取OhosAppContext
                    const appContext = parentNode?.getContext(OhosPluginId.OHOS_APP_PLUGIN) as OhosAppContext
                    // 获取项目名
                    const projectName = appContext.getProjectName()
                    console.log(`项目名:${projectName}`)
                    // 获取AppScope目录下app.json文件里面的json
                    const appOptObj: AppJson.AppOptObj = appContext.getAppJsonOpt()
                    const versionName = appOptObj.app.versionName
                    // 新文件路径
                    const targetSignFilePath = `${modulePath}/build/default/outputs/default/target/${projectName}-${versionName}-default-signed.hap`
                    const targetUnsignFilePath = `${modulePath}/build/default/outputs/default/target/${projectName}-${versionName}-default-unsigned.hap`
                    // 复制文件
                    if (fs.existsSync(originSignFilePath)) {
                        // 原文件存在才复制
                        fs.copyFile(originSignFilePath, targetSignFilePath, (err) => {
                            console.log(`复制文件失败:${err}`)
                        })
                    }
                    if (fs.existsSync(originUnsignFilePath)) {
                        // 原文件存在才复制
                        fs.copyFile(originUnsignFilePath, targetUnsignFilePath, (err) => {
                            console.log(`复制文件失败:${err}`)
                        })
                    }
                    console.log(`重命名任务执行完成`)
                }
            })
        }
    }
}

5、7指定任务的插入位置

  我们自定义了任务,那任务什么时候执行呢?打包就是执行一个一个的任务,所有的任务组成了一个有向无环图。我们既然要重命名文件,那必然得等到原文件生成。查看所有的任务,我们会发现,当执行完default@SignHap任务后,hap就生成了,并且签名也完成,于是我们的重命名任务就需要default@SignHap执行完成后执行。

image.png   当执行完default@assembleHap任务后,打包就结束了。所以,重命名任务就需要在default@SignHap执行完成后执行,在assembleHap任务执行之前执行。 image.png   在注册任务的时候,dependencies用于指定前置依赖的任务,先执行前置依赖,再执行重命名任务。postDependencies用于指定后置依赖的任务,执行后置依赖前,必须先执行重命名任务。三个任务的执行顺序就变成了这样,default@SignHap ——> renameHapTask ——> assembleHap。

// 重命名任务在default@SignHap任务执行完成后执行
dependencies: ['default@SignHap'],
// 重命名任务在default@assembleHap任务执行完成前执行
postDependencies: ['assembleHap'],
// 任务名称
name: 'renameHapTask'

5、8完整代码

import { HvigorNode, HvigorPlugin, HvigorTaskContext } from "@ohos/hvigor";
import { OhosAppContext, OhosPluginId } from '@ohos/hvigor-ohos-plugin';
import { AppJson } from "@ohos/hvigor-ohos-plugin/src/options/configure/app-json-options";
import fs from 'fs'

export function rename(): HvigorPlugin {
    return {
        // 插件名称
        pluginId: 'renameHapPlugin',
        apply(node: HvigorNode) {
            // 注册任务,指定任务名称,当执行任务的时候,就会调用run方法
            node.registerTask({
                // 任务名称
                name: 'renameHapTask',
                // 重命名任务在default@SignHap任务执行完成后执行
                dependencies: ['default@SignHap'],
                // 重命名任务在default@assembleHap任务执行完成前执行
                postDependencies: ['assembleHap'],
                run: (taskContext: HvigorTaskContext) => {
                    console.log(`开始执行重命名任务`)
                    // 获取模块名
                    const moduleName = taskContext.moduleName
                    // 获取模块路径
                    const modulePath = taskContext.modulePath
                    // 假设我们在entry目录的hvigorfile.ts文件调用插件,那拿到的模块名就是entry,模块路径就是entry模块的绝对路径。
                    console.log(`模块名:${moduleName}`)
                    console.log(`模块路径:${modulePath}`)
                    // hap所在路径
                    const originSignFilePath = `${modulePath}/build/default/outputs/default/${moduleName}-default-signed.hap`
                    const originUnsignFilePath = `${modulePath}/build/default/outputs/default/${moduleName}-default-unsigned.hap`
                    console.log(`原签名文件路径:${originSignFilePath}`)
                    console.log(`原未签名文件路径:${originUnsignFilePath}`)
                    // 新文件所在的目录
                    const targetFileDir = `${modulePath}/build/default/outputs/default/target`
                    // 创建目录
                    fs.mkdir(targetFileDir, {recursive: true}, (err) => {
                        console.log(`目录创建失败:{err}`)
                    })
                    // 获取父节点
                    const parentNode = node.getParentNode()
                    // 获取OhosAppContext
                    const appContext = parentNode?.getContext(OhosPluginId.OHOS_APP_PLUGIN) as OhosAppContext
                    // 获取项目名
                    const projectName = appContext.getProjectName()
                    console.log(`项目名:${projectName}`)
                    // 获取AppScope目录下app.json文件里面的json
                    const appOptObj: AppJson.AppOptObj = appContext.getAppJsonOpt()
                    const versionName = appOptObj.app.versionName
                    console.log(`版本:${versionName}`)
                    // 新文件路径
                    const targetSignFilePath = `${modulePath}/build/default/outputs/default/target/${projectName}-${versionName}-default-signed.hap`
                    const targetUnsignFilePath = `${modulePath}/build/default/outputs/default/target/${projectName}-${versionName}-default-unsigned.hap`
                    // 复制文件
                    if (fs.existsSync(originSignFilePath)) {
                        // 原文件存在才复制
                        fs.copyFile(originSignFilePath, targetSignFilePath, (err) => {
                            console.log(`复制文件失败:${err}`)
                        })
                    }
                    if (fs.existsSync(originUnsignFilePath)) {
                        // 原文件存在才复制
                        fs.copyFile(originUnsignFilePath, targetUnsignFilePath, (err) => {
                            console.log(`复制文件失败:${err}`)
                        })
                    }
                    console.log(`重命名任务执行完成`)
                }
            })
        }
    }
}

5、9导出方法

  打开index.ts文件,在该文件中声明插件方法的导出。

export {rename} from './src/rename'

5、10打包

  打开终端,执行npm pack进行打包,生成tgz文件。

npm pack

5、11引入文件

  回到DevEco-Studio,打开hvigor目录下的hvigor-config.json文件,在dependencies添加下面的代码,将上一步生成的tgz文件引入。

{
  "modelVersion": "5.0.0",
  "dependencies": {
    "@shijing/plugin": "file:../plugin/plugin-1.0.0.tgz" /*引入tgz文件*/
  }
}

5、12应用插件

  打开entry目录下的hvigorfile.ts,导入rename,将自定义插件添加到export default的plugins中。

import { rename } from '@shijing/plugin';

export default {
    system: hapTasks,  
    plugins:[rename()] /* 应用重命名任务 */
}

5、13执行插件

  打开DevEco-Studio的终端,执行hvigorw assembleHap命令打包。如下图,default@SignHap任务执行完成后,开始重命名任务,相关的日志页打印出来了。 image.png   build/default/outputs/default/target目录也生成了文件。 image.png

六、hvigor定制构建

  有的时候,仅仅只需要实现一个小小的功能,这时可以不使用插件,可以直接在hvigorfile.ts编码代码。但直接在hvigorfile.ts敲代码没有代码提示,仍然可以在VS Code中编码,然后将代码复制到hvigorfile.ts。

6、1修改app.json5中的配置信息

  在项目的根目录下的hvigorfile.ts中添加如下代码内容:

import { appTasks, OhosAppContext, OhosPluginId } from '@ohos/hvigor-ohos-plugin';
import { hvigor } from '@ohos/hvigor'

// 为根节点添加一个afterNodeEvaluate hook 在hook中修改app.json5的内容并使能
hvigor.getRootNode().afterNodeEvaluate(rootNode => {
  // 获取app插件的上下文对象
  const appContext = rootNode.getContext(OhosPluginId.OHOS_APP_PLUGIN) as OhosAppContext;
  // 通过上下文对象获取从app.json5文件中读出来的obj对象
  const appJsonOpt = appContext.getAppJsonOpt();
  // 修改obj对象为想要的,此处举例修改app中的versionCode
  appJsonOpt['app']['versionCode'] = 1000001;
  // 将obj对象设置回上下文对象以使能到构建的过程与结果中
  appContext.setAppJsonOpt(appJsonOpt);
})
export default {
  system: appTasks,  /* Built-in plugin of Hvigor. It cannot be modified. */
  plugins:[]         /* Custom plugin to extend the functionality of Hvigor. */
}

6、2修改module.json5中的配置信息

  通过hvigor对象的hook能力快捷为所有的node创建hook,此处先举例为单一的node创建一个hook并修改其中的module.json5的配置信息。

  例如此处需要修改entry下的module.json5配置,则在entry下的hvigorfile.ts中添加如下内容:

import { hapTasks, OhosHapContext, OhosPluginId } from '@ohos/hvigor-ohos-plugin';
import { getNode } from '@ohos/hvigor'

const entryNode = getNode(__filename);
// 为此节点添加一个afterNodeEvaluate hook 在hook中修改module.json5的内容并使能
entryNode.afterNodeEvaluate(node => {
    // 获取此节点使用插件的上下文对象 此时为hap插件 获取hap插件上下文对象
    const hapContext = node.getContext(OhosPluginId.OHOS_HAP_PLUGIN) as OhosHapContext;
    // 通过上下文对象获取从module.json5文件中读出来的obj对象
    const moduleJsonOpt = hapContext.getModuleJsonOpt();
    // 修改obj对象为想要的,此处举例修改module中的deviceTypes
    moduleJsonOpt['module']['deviceTypes'] = ["phone", "tablet", "2in1", "car"];
    // 将obj对象设置回上下文对象以使能到构建的过程与结果中
    hapContext.setModuleJsonOpt(moduleJsonOpt);
})
export default {
    system: hapTasks,  /* Built-in plugin of Hvigor. It cannot be modified. */
    plugins:[]         /* Custom plugin to extend the functionality of Hvigor. */
}

6、3修改oh-package.json5中的依赖

  可以通过hvigorfile.ts自定义插件修改工程级、模块级的oh-package.json5的依赖,例如在工程级hvigorfile.ts或模块级hvigorfile.ts分别添加以下内容:

// 工程级hvigorfile.ts
import { appTasks, OhosAppContext, OhosPluginId, Target } from '@ohos/hvigor-ohos-plugin';
import { HvigorNode, HvigorPlugin, TaskInput, TaskOutput } from '@ohos/hvigor';
export function customPlugin(): HvigorPlugin {
  return {
      pluginId: 'customPlugin',
      async apply(currentNode: HvigorNode): Promise<void> {
          const appContext = currentNode.getContext(OhosPluginId.OHOS_APP_PLUGIN) as OhosAppContext;
          const dependency = appContext.getDependenciesOpt({});  //获取dependency依赖
          dependency["library"]="file:library.har"
          console.log(dependency);
          appContext.setDependenciesOpt(dependency );  //修改dependency依赖
      }
  };
}
export default {
  system: appTasks,  /* Built-in plugin of Hvigor. It cannot be modified. */
  plugins:[customPlugin()]         /* Custom plugin to extend the functionality of Hvigor. */
}

// 模块级hvigorfile.ts
import {hapTasks,OhosHapContext,OhosPluginId,Target} from '@ohos/hvigor-ohos-plugin';
import { hvigor, HvigorNode, HvigorPlugin} from '@ohos/hvigor';
import * as fs from 'fs';
export function customPlugin(options: OnlineSignOptions): HvigorPlugin {
  return {
      pluginId: 'customPlugin',
      context() {
          return {
              signConfig: options
          };
      },
      async apply(currentNode: HvigorNode): Promise<void> {
          const hapContext = currentNode.getContext(OhosPluginId.OHOS_HAP_PLUGIN) as OhosHapContext;
          const dependency = hapContext.getDependenciesOpt({});//获取dependency依赖
          dependency["library"]="file:library.har"
          hapContext.setDependenciesOpt(dependency);}
      }
  };
export default {
  system: hapTasks,  /* Built-in plugin of Hvigor. It cannot be modified. */
  plugins:[customPlugin()]         /* Custom plugin to extend the functionality of Hvigor. */
}

七、总结

  • DevEco-Studio不支持ts文件的代码提示,可以使用VS Code。
  • 在VS Code修改完代码后,打开package.json修改版本,执行npm pack命令打包,然后回到DevEco-Studio应用插件。
  • 代码是在打包期间执行的,无法断点调试,所以需要多多的打印日志,通过日志排查问题。