You can extend the build capabilities through custom plugins and custom tasks to meet requirements such as automated build steps and customized resource processing. This topic covers the following key points:
You can develop custom plugins by implementing the HvigorPlugin API. The following example defines a plugin in entry/hvigorfile.ts to simulate the scenario where necessary resources are downloaded at the beginning of the HAP build.
// entry/hvigorfile.ts
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
import { HvigorPlugin, HvigorNode } from '@ohos/hvigor';
class MyCustomPlugin implements HvigorPlugin {
pluginId = 'customPlugin';
async apply(node: HvigorNode): Promise<void> {
console.log('download start at', new Date());
await new Promise<void>((resolve) => {
setTimeout(() => {
// Simulate a time-consuming operation using a delay.
console.log('download end at', new Date());
resolve();
}, 5000);
});
}
}
export default {
system: hapTasks,
plugins: [new MyCustomPlugin()]
} Click ReBuild Project. The following logs are displayed in the build window.

Executing Sync also triggers a download operation.

The apply method of HvigorPlugin runs during the configuration phase of the build lifecycle (internal lifecycle functions in this phase are not exposed to developers). In the preceding example, the time-consuming download logic runs directly within the apply method. Since the apply method provides no incremental skip mechanism, the logic runs for every build and blocks the main thread, compromising the development efficiency.
Avoid executing time-consuming service logic directly within the apply method. You are advised to:
The following example refactors the preceding implementation by using an HvigorTask to host the service logic.
// entry/hvigorfile.ts
import { hapTasks, OhosPluginId, OhosHapContext, Target } from '@ohos/hvigor-ohos-plugin';
import { HvigorPlugin, HvigorNode, hvigor } from '@ohos/hvigor';
class MyCustomPlugin implements HvigorPlugin {
pluginId = 'customPlugin';
async apply(node: HvigorNode): Promise<void> {
const hapContext = node?.getContext(OhosPluginId.OHOS_HAP_PLUGIN) as OhosHapContext;
hapContext?.targets((target: Target) => {
const targetName = target.getTargetName();
// Encapsulate the service logic into the task. No download logic runs within the apply method. Instead, the download operation is deferred to the task execution phase.
node?.registerTask({
name: `${targetName}@DownloadTask`,
async run() {
console.log('download start at', new Date());
await new Promise<void>((resolve) => {
setTimeout(() => {
// Simulate a time-consuming operation using a delay.
console.log('download end at', new Date());
resolve();
}, 5000);
});
},
// Set the tasks on which this task depends. The PreBuild task will be executed before this task.
dependencies: [`${targetName}@PreBuild`],
// Set the tasks that depend on this task. This task will be executed before the CreateModuleInfo task.
postDependencies: [`${targetName}@CreateModuleInfo`]
});
});
}
}
export default {
system: hapTasks,
plugins: [new MyCustomPlugin()]
} After refactoring, the Sync flow does not trigger downloads.

The user-defined service logic runs in the task execution phase.

You can also further leverage the task's incremental mechanism to optimize incremental build scenarios, as described in subsequent sections.
You can develop custom tasks by registering HvigorTask via the registerTask API in the hvigorfile.ts file. Multiple tasks can be registered in each module-level hvigorfile.ts file. The dependencies between tasks are declared using dependencies (tasks on which this task depends) and postDependencies (tasks that depend on this task).
In the following example, two HvigorTask instances are registered in the entry/hvigorfile.ts file to simulate the scenario involving operations on the build products.
// entry/hvigorfile.ts
// Register two custom tasks to operate on the output directory.
import { hapTasks, OhosPluginId, OhosHapContext, Target } from '@ohos/hvigor-ohos-plugin';
import { getNode, hvigor, HvigorNode } from '@ohos/hvigor';
hvigor.nodesEvaluated(() => {
const node: HvigorNode | undefined = getNode(__filename);
const hapContext = node?.getContext(OhosPluginId.OHOS_HAP_PLUGIN) as OhosHapContext;
hapContext?.targets((target: Target) => {
const targetName = target.getTargetName();
const buildTargetOutputPath = target.getBuildTargetOutputPath();
// Register the custom task CustomTask0.
node?.registerTask({
name: `${targetName}@CustomTask0`,
async run() {
await new Promise<void>((resolve) => {
setTimeout(() => {
// Simulate a time-consuming operation using a delay.
console.log(`${this.name} Processing files in the directory:`, buildTargetOutputPath);
resolve();
}, 5000);
});
},
// Set the tasks on which this task depends. The SignHap task will be executed before this task.
dependencies: [`${targetName}@SignHap`],
// Set the tasks that depend on this task. This task will be executed before the assembleHap task.
postDependencies: ['assembleHap']
});
// Register the custom task CustomTask1.
node?.registerTask({
name: `${targetName}@CustomTask1`,
async run() {
await new Promise<void>((resolve) => {
setTimeout(() => {
console.log(`${this.name} Processing files in the directory:`, buildTargetOutputPath);
resolve();
}, 5000);
});
},
dependencies: [`${targetName}@SignHap`],
postDependencies: ['assembleHap']
});
});
});
export default {
system: hapTasks,
plugins: []
} Click ReBuild Project. The following logs are displayed in the build window, from which you can see that the end-to-end full build takes approximately 15 seconds.

The preceding sample code has two issues: repeated builds and serialized builds.


To address the issues above, the following optimization solutions are recommended.
Use the incremental build mechanism by configuring the input or output function to declare the files or directories on which the task depends. Hvigor records the metadata hash values of these files or directories and executes the task only when they change.
// entry/hvigorfile.ts
// Register two custom tasks to operate on the output directory.
import fs from 'node:fs';
import { hapTasks, OhosPluginId, OhosHapContext, Target } from '@ohos/hvigor-ohos-plugin';
import { getNode, hvigor, HvigorNode, TaskInput } from '@ohos/hvigor';
hvigor.nodesEvaluated(() => {
const node: HvigorNode | undefined = getNode(__filename);
const hapContext = node?.getContext(OhosPluginId.OHOS_HAP_PLUGIN) as OhosHapContext;
hapContext?.targets((target: Target) => {
const targetName = target.getTargetName();
const buildTargetOutputPath = target.getBuildTargetOutputPath();
// Register the custom task CustomTask0.
node?.registerTask({
name: `${targetName}@CustomTask0`,
async run() {
await new Promise<void>((resolve) => {
setTimeout(() => {
// Simulate a time-consuming operation using a delay.
console.log(`${this.name} Processing files in the directory:`, buildTargetOutputPath);
resolve();
}, 5000);
});
},
// Add the input dependency configuration.
input(input: TaskInput) {
if (fs.existsSync(buildTargetOutputPath)) {
// The input.file method declares the files or directories that the task depends on. Here, buildTargetOutputPath is added to the incremental check set.
// When the contents of the directory specified by buildTargetOutputPath remain unchanged, the task is skipped.
input.file(buildTargetOutputPath);
}
},
// Set the tasks on which this task depends. The SignHap task will be executed before this task.
dependencies: [`${targetName}@SignHap`],
// Set the tasks that depend on this task. This task will be executed before the assembleHap task.
postDependencies: ['assembleHap']
});
// Register the custom task CustomTask1.
node?.registerTask({
name: `${targetName}@CustomTask1`,
async run() {
await new Promise<void>((resolve) => {
setTimeout(() => {
console.log(`${this.name} Processing files in the directory:`, buildTargetOutputPath);
resolve();
}, 5000);
});
},
// Add the input dependency configuration.
input(input: TaskInput) {
if (fs.existsSync(buildTargetOutputPath)) {
input.file(buildTargetOutputPath);
}
},
dependencies: [`${targetName}@SignHap`],
postDependencies: ['assembleHap']
});
});
});
export default {
system: hapTasks,
plugins: []
} When no code changes are made, the custom tasks show the UP-TO-DATE status in a repeated build, indicating that they have been skipped.

For time-consuming tasks, you can use the submitWorker API to distribute tasks to worker threads for parallel execution.
When to use:
The optimized sample code is as follows.
// Create the custom-job.js file in the entry/scripts directory.
async function runJob(workInput) {
await new Promise((resolve) => {
setTimeout(() => {
console.log(`${workInput.taskName} Processing files in the directory:`, workInput.outputPath);
resolve();
}, 5000);
})
}
exports.runJob = runJob; // entry/hvigorfile.ts
// Register two custom tasks to operate on the output directory.
import fs from 'node:fs';
import path from "node:path";
import { hapTasks, OhosPluginId, OhosHapContext, Target } from '@ohos/hvigor-ohos-plugin';
import { getNode, hvigor, HvigorNode, TaskInput, submitWorker } from '@ohos/hvigor';
hvigor.nodesEvaluated(() => {
const node: HvigorNode | undefined = getNode(__filename);
const hapContext = node?.getContext(OhosPluginId.OHOS_HAP_PLUGIN) as OhosHapContext;
hapContext?.targets((target: Target) => {
const targetName = target.getTargetName();
const buildTargetOutputPath = target.getBuildTargetOutputPath();
// Register the custom task CustomTask0.
node?.registerTask({
name: `${targetName}@CustomTask0`,
async run() {
// Submit the custom-job.js script to the worker thread for execution. Ensure that the file source is trusted.
submitWorker(node, this.name, path.join(__dirname, 'scripts', 'custom-job.js', 'runJob'), {
workInput: {
taskName: this.name,
outputPath: buildTargetOutputPath,
}
});
},
// Add the input dependency configuration.
input(input: TaskInput) {
if (fs.existsSync(buildTargetOutputPath)) {
input.file(buildTargetOutputPath);
}
},
// Set the tasks on which this task depends. The SignHap task will be executed before this task.
dependencies: [`${targetName}@SignHap`],
// Set the tasks that depend on this task. This task will be executed before the assembleHap task.
postDependencies: ['assembleHap']
});
// Register the custom task CustomTask1.
node?.registerTask({
name: `${targetName}@CustomTask1`,
async run() {
// Submit the custom-job.js script to the worker thread for execution. Ensure that the file source is trusted.
submitWorker(node, this.name, path.join(__dirname, 'scripts', 'custom-job.js', 'runJob'), {
workInput: {
taskName: this.name,
outputPath: buildTargetOutputPath,
}
});
},
// Add the input dependency configuration.
input(input: TaskInput) {
if (fs.existsSync(buildTargetOutputPath)) {
input.file(buildTargetOutputPath);
}
},
dependencies: [`${targetName}@SignHap`],
postDependencies: ['assembleHap']
});
});
});
export default {
system: hapTasks,
plugins: []
} Parallel effect verification:


When developing custom plugins and tasks, you can apply and verify the following optimization practices: