HarmonyOS5 自定义编译插件开发:扩展ArkCompiler的代码转换能力

136 阅读1分钟

以下为 ​​HarmonyOS 5 ArkCompiler自定义编译插件的完整开发指南​​,包含插件架构、代码转换API及实战示例:


1. 插件系统架构

image.png


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管道

通过本方案可实现:

  1. ​无侵入​​ 代码转换
  2. ​类型安全​​ 的AST操作
  3. ​可配置​​ 的编译策略
  4. ​生产级​​ 插件稳定性