以下为 HarmonyOS 5 ArkCompiler自定义编译插件的完整开发指南,包含插件架构、代码转换API及实战示例:
1. 插件系统架构
2. 插件基础模板
2.1 插件骨架代码
// my-plugin.ets
import { CompilerPlugin, ASTVisitor } from '@ark/compiler';
export default class MyPlugin implements CompilerPlugin {
name = 'MyCustomPlugin';
version = '1.0';
apply(compiler: Compiler) {
compiler.hooks.beforeCompile.tap(this.name, (ast) => {
return new MyVisitor().visit(ast);
});
}
}
2.2 注册插件
// arkconfig.json
{
"plugins": [
"./plugins/my-plugin.ets",
"@ohos/logger-plugin"
]
}
3. AST转换核心API
3.1 节点访问器
// visitor.ets
class MyVisitor extends ASTVisitor {
visitFunctionDeclaration(node: FunctionNode) {
// 转换函数名大写
node.name = node.name.toUpperCase();
// 递归访问子节点
super.visitFunctionDeclaration(node);
}
visitBinaryExpression(node: BinaryNode) {
// 替换所有加法为乘法
if (node.operator === '+') {
return new BinaryExpression('*', node.left, node.right);
}
return super.visitBinaryExpression(node);
}
}
3.2 源码映射保持
// source-map.ets
compiler.hooks.emit.tap('SourceMapPlugin', (code) => {
SourceMapConsumer.with(code, (map) => {
const transformed = transform(code);
return new SourceMapGenerator()
.applySourceMap(map)
.toComment(transformed);
});
});
4. 典型转换场景
4.1 日志注入
// logger-plugin.ets
visitCallExpression(node: CallNode) {
if (node.callee.name === 'fetch') {
return new BlockStatement([
new CallExpression('console.log', [
new Literal(`Calling fetch: ${node.loc}`)
]),
node
]);
}
return super.visitCallExpression(node);
}
4.2 性能分析包装
// perf-plugin.ets
visitFunctionBody(node: BlockNode) {
const start = new CallExpression('performance.mark', ['start']);
const end = new CallExpression('performance.measure', [
'func_duration', 'start'
]);
return new BlockStatement([
start,
...node.body,
end
]);
}
5. 高级转换技巧
5.1 作用域感知转换
// scope-plugin.ets
visitVariableDeclaration(node: VarNode) {
const scope = this.currentScope;
if (scope.isGlobal && node.kind === 'var') {
throw new Error('全局作用域禁止使用var');
}
return super.visitVariableDeclaration(node);
}
5.2 类型驱动重写
// type-plugin.ets
visitMemberExpression(node: MemberNode) {
const type = this.getType(node.object);
if (type === 'number') {
return new CallExpression('Math.floor', [node]);
}
return super.visitMemberExpression(node);
}
6. 插件配置系统
6.1 可配置插件
// configurable-plugin.ets
interface Config {
rules: Record<string, Rule>;
}
export default (config: Config) => {
return class ConfigurablePlugin implements CompilerPlugin {
apply(compiler) {
compiler.hooks.transform.tap(this.name, (ast) => {
return new ConfigurableVisitor(config).visit(ast);
});
}
}
}
6.2 配置文件示例
// my-plugin.config.json
{
"rules": {
"no-var": { "level": "error" },
"log-fetch": { "enabled": true }
}
}
7. 调试与测试
7.1 单元测试工具
// plugin-test.ets
test('BinaryExpression转换', () => {
const plugin = new MyPlugin();
const input = parse('a + b');
const output = plugin.apply(input);
expect(output).toEqual(parse('a * b'));
});
7.2 AST可视化调试
# 生成AST图形
arkc --dump-ast --plugin=my-plugin input.ets -o ast.html
8. 性能优化建议
8.1 增量转换
// incremental-plugin.ets
let cache = new WeakMap<ASTNode, ASTNode>();
visitNode(node: ASTNode) {
if (cache.has(node)) {
return cache.get(node);
}
const transformed = super.visitNode(node);
cache.set(node, transformed);
return transformed;
}
8.2 并行处理
// parallel-plugin.ets
async visitProgram(node: ProgramNode) {
const transformed = await Promise.all(
node.body.map(stmt =>
threadPool.run(() => this.visit(stmt))
)
);
return new ProgramNode(transformed);
}
9. 完整示例:国际化插件
9.1 转换代码
// i18n-plugin.ets
visitLiteral(node: LiteralNode) {
if (typeof node.value === 'string') {
return new CallExpression('i18n.t', [
new Literal(hashString(node.value))
]);
}
return super.visitLiteral(node);
}
9.2 输入/输出对比
// 输入代码
const msg = "Hello World";
// 输出代码
const msg = i18n.t("7d793037"); // hash("Hello World")
10. 插件发布与共享
10.1 打包配置
// package.json
{
"name": "@ohos/ark-i18n-plugin",
"ark-plugin": {
"entry": "./dist/plugin.js",
"schema": "./schema.json"
}
}
10.2 安装使用
ohpm install @ohos/ark-i18n-plugin
// arkconfig.json
{
"plugins": ["@ohos/ark-i18n-plugin"]
}
11. 安全注意事项
11.1 沙箱执行
// safe-plugin.ets
compiler.hooks.pluginInit.tap('Sandbox', () => {
if (!isWhitelisted(this.name)) {
throw new Error(`未授权的插件: ${this.name}`);
}
});
11.2 输入验证
// validation-plugin.ets
visitImportDeclaration(node) {
if (node.source.value.includes('..')) {
throw new Error(`禁止相对路径导入: ${node.source.value}`);
}
return super.visitImportDeclaration(node);
}
12. 性能影响评估
| 插件类型 | 编译耗时增加 | 内存开销 | 适用场景 |
|---|---|---|---|
| 简单语法转换 | <5% | 0.1MB | 全量编译 |
| 复杂类型推导 | 15-20% | 2MB | 开发阶段 |
| 全局分析插件 | 30%+ | 10MB+ | CI/CD管道 |
通过本方案可实现:
- 无侵入 代码转换
- 类型安全 的AST操作
- 可配置 的编译策略
- 生产级 插件稳定性