Skip to content

插件

VuReact 提供了一个灵活的插件系统,允许你在编译过程的不同阶段注入自定义逻辑。插件可以用于增强功能、添加元数据、修改输出结果等。

插件系统概述

VuReact 的编译过程分为三个阶段,每个阶段都支持插件:

  1. 解析阶段 (Parse) - 将 Vue SFC 源码解析为结构化 AST
  2. 转换阶段 (Transform) - 将 Vue AST 转换为 React 中间表示 (IR)
  3. 代码生成阶段 (Codegen) - 将 React IR 生成为最终的 JSX/TSX 代码
  4. 编译阶段 (Compilation) - 已经处理好的,带有文件路径的结果

每个阶段都有对应的插件接口,你可以根据需要选择在哪个阶段注入逻辑。

插件类型定义

基础插件接口

typescript
interface PluginRegister<T> {
  [name: string]: (result: T, ctx: ICompilationContext) => void;
}

各阶段插件类型

typescript
// 解析阶段插件
type ParserPlugin = PluginRegister<ParseResult>;

// 转换阶段插件
type TransformerPlugin = PluginRegister<ReactIRDescriptor>;

// 代码生成阶段插件
type CodegenPlugin = PluginRegister<GeneratorResult>;

// 编译完成插件(在所有阶段之后执行)
type CompilationPlugin = PluginRegister<CompilationResult>;

配置插件

vureact.config.js 中配置插件:

javascript
import { defineConfig } from '@vureact/compiler-core';

export default defineConfig({
  plugins: {
    // 解析阶段插件
    parser: {
      extractMetadata: (result, ctx) => {
        // 在解析阶段添加自定义逻辑
      },
    },

    // 转换阶段插件
    transformer: {
      analyzeDependencies: (result, ctx) => {
        // 在转换阶段添加自定义逻辑
      },
    },

    // 代码生成阶段插件
    codegen: {
      formatOutput: (result, ctx) => {
        // 在代码生成阶段添加自定义逻辑
      },
    },

    // 编译完成插件(在所有阶段之后执行)
    customAnalytics: (result, ctx) => {
      // 编译完成后执行
    },
  },
});

插件上下文

每个插件都会接收两个参数:

  1. result - 当前阶段的处理结果
  2. ctx - 编译上下文对象

编译上下文接口

typescript
interface ICompilationContext {
  fileId: string;: string; // 文件 id
  filename: string; // 文件名
  compName: string; // 组件名
  imports: Map<string, ImportItem[]>; // 收集需要导入的 React、VuReact 运行时模块
  source: string; // 源代码
  inputType: 'sfc' | 'script-ts' | 'script-js'; // 输入类型
  route?: boolean; // 是否使用了路由
  // ... 其他上下文信息
}

各阶段插件示例

1. 解析阶段插件

解析阶段插件可以访问 Vue SFC 的完整解析结果:

typescript
// 使用插件
export default defineConfig({
  plugins: {
    parser: {
      // 示例:提取组件元数据
      extractComponentInfo: (result: ParseResult, ctx: ICompilationContext) => {
        const { template, script, style } = result;

        // 提取组件名称
        const componentName = extractNameFromScript(script);

        // 分析模板复杂度
        const templateComplexity = analyzeTemplate(template);

        // 检查样式类型
        const hasScopedStyle = style?.source?.scoped || false;
        const hasCSSModules = style?.source?.module || false;

        // 将元数据添加到结果中
        (result as any).metadata = {
          componentName,
          templateComplexity,
          hasScopedStyle,
          hasCSSModules,
          analyzedAt: new Date().toISOString(),
        };
      },
    },
  },
});

2. 转换阶段插件

转换阶段插件可以访问 React 中间表示:

typescript
export default defineConfig({
  plugins: {
    transformer: {
      // 示例:分析依赖关系
      analyzeDependencies: (result: ReactIRDescriptor, ctx: ICompilationContext) => {
        const { script, template } = result;

        // 从脚本中提取导入语句
        const imports = extractImports(script);

        // 从模板中提取使用的组件
        const usedComponents = extractComponentsFromTemplate(template);

        // 分析运行时依赖
        const runtimeDeps = analyzeRuntimeDependencies(script);

        // 添加依赖分析结果
        (result as any).dependencies = {
          imports,
          usedComponents,
          runtimeDeps,
          totalDeps: imports.length + runtimeDeps.length,
        };
      },

      // 示例:样式预处理
      processStyles: (result: ReactIRDescriptor, ctx: ICompilationContext) => {
        if (result.style) {
          // 添加 CSS 前缀
          result.style = addVendorPrefixes(result.style);

          // 压缩 CSS
          result.style = minifyCSS(result.style);

          // 添加源映射注释
          result.style += `\n/* Source: ${ctx.filename} */`;
        }
      },
    },
  },
});

3. 代码生成阶段插件

代码生成阶段插件可以修改最终的输出代码:

typescript
export default defineConfig({
  plugins: {
    codegen: {
      // 示例:代码格式化
      prettify: (result: GeneratorResult, ctx: ICompilationContext) => {
        // 使用 prettier 格式化代码(vureact)
        const formatted = prettier.format(result.code, {
          parser: 'babel',
          semi: true,
          singleQuote: true,
          trailingComma: 'es5',
        });

        result.code = formatted;
      },

      // 示例:添加文件头注释
      addFileHeader: (result: GeneratorResult, ctx: ICompilationContext) => {
        const header = `/** Generated by VuReact */`;
        result.code = header + result.code;
      },

      // 示例:代码质量检查
      lintGeneratedCode: (result: GeneratorResult, ctx: ICompilationContext) => {
        const issues = eslint.verify(result.code, {
          rules: {
            'react/react-in-jsx-scope': 'off',
            'no-unused-vars': 'warn',
            'no-console': 'warn',
          },
        });

        if (issues.length > 0) {
          console.warn(`Found ${issues.length} lint issues in ${ctx.filename}:`);
          issues.forEach((issue) => {
            console.warn(`  ${issue.line}:${issue.column} ${issue.message}`);
          });
        }
      },
    },
  },
});

4. 编译完成插件

编译完成插件在所有阶段之后执行,可以访问完整的编译结果:

typescript
export default defineConfig({
  plugins: {
    // 直接添加插件函数
    // 示例:编译统计
    collectStats: (result: CompilationResult, ctx: ICompilationContext) => {
      const stats = {
        filename: ctx.filename,
        fileSize: ctx.source.length,
        generatedSize: result.code.length,
        hasCSS: !!result.fileInfo.css,
        hasJSX: !!result.fileInfo.jsx,
        hasScript: !!result.fileInfo.script,
      };

      // 保存统计信息
      saveCompilationStats(stats);

      // 添加到结果中
      (result as any).stats = stats;
    },

    // 示例:发送通知
    sendNotification: (result: CompilationResult, ctx: ICompilationContext) => {
      if (process.env.NODE_ENV === 'development') {
        // 开发环境发送桌面通知
        new Notification('VuReact Compilation Complete', {
          body: `Successfully compiled ${ctx.filename}`,
          icon: '/path/to/icon.png',
        });
      }

      // 发送到监控系统
      sendToMonitoringSystem({
        event: 'compilation_complete',
        data: (result as any).stats
        timestamp: new Date().toISOString(),
      });
    },
  },
});

实用插件示例

注意,仅作为参考,请勿直接使用。

1. 性能分析插件

typescript
const performancePlugin = {
  measurePerformance: (result: any, ctx: ICompilationContext) => {
    if (!ctx.metrics) ctx.metrics = {};

    const stage = ctx.currentStage; // 'parse', 'transform', 'codegen'
    const duration = Date.now() - ctx.stageStartTime;

    ctx.metrics[stage] = duration;

    if (stage === 'codegen') {
      // 所有阶段完成,输出性能报告
      console.log(`
VuReact Performance Report for ${ctx.filename}:
  Parse: ${ctx.metrics.parse}ms
  Transform: ${ctx.metrics.transform}ms  
  Codegen: ${ctx.metrics.codegen}ms
  Total: ${Object.values(ctx.metrics).reduce((a, b) => a + b, 0)}ms
      `);
    }
  },
};

// 在所有阶段都使用同一个插件
export default defineConfig({
  plugins: {
    parser: { performancePlugin },
    transformer: { performancePlugin },
    codegen: { performancePlugin },
  },
});

2. 依赖分析插件

typescript
const dependencyGraphPlugin = {
  buildDependencyGraph: (result: CompilationResult, ctx: ICompilationContext) => {
    const graph = {
      nodes: [],
      edges: [],
    };

    // 分析导入语句
    const imports = extractImportsFromCode(result.code);

    imports.forEach((imp) => {
      graph.nodes.push({
        id: imp.source,
        type: 'external',
        label: imp.source,
      });

      graph.edges.push({
        source: ctx.filename,
        target: imp.source,
        type: 'import',
      });
    });

    // 保存依赖图
    saveDependencyGraph(graph);

    // 添加到结果中
    (result as any).dependencyGraph = graph;
  },
};

3. 代码转换插件

typescript
const codeTransformationPlugin = {
  transformImports: (result: GeneratorResult, ctx: ICompilationContext) => {
    // 将 Vue 特定的导入转换为 React 等效
    const transformations = {
      vue: '@vureact/runtime-core',
      '@vue/composition-api': '@vureact/runtime-core',
      'vue-router': 'react-router-dom',
    };

    let code = result.code;

    Object.entries(transformations).forEach(([from, to]) => {
      const regex = new RegExp(`from ['"]${from}['"]`, 'g');
      code = code.replace(regex, `from '${to}'`);
    });

    result.code = code;
  },

  addPolyfills: (result: GeneratorResult, ctx: ICompilationContext) => {
    // 为旧浏览器添加 polyfill 导入
    if (needsPolyfills(ctx.browserTarget)) {
      const polyfillImport = "import 'core-js/stable';\nimport 'regenerator-runtime/runtime';\n\n";
      result.code = polyfillImport + result.code;
    }
  },
};

插件开发最佳实践

1. 错误处理

typescript
const safePlugin = {
  myPlugin: (result: any, ctx: ICompilationContext) => {
    try {
      // 插件逻辑
      performComplexOperation(result);
    } catch (error) {
      // 优雅地处理错误,不影响编译过程
      console.warn(`Plugin myPlugin failed: ${error.message}`);

      // 可以添加错误信息到结果中
      if (!result.errors) result.errors = [];
      result.errors.push({
        plugin: 'myPlugin',
        message: error.message,
        timestamp: new Date().toISOString(),
      });
    }
  },
};

2. 性能优化

typescript
const optimizedPlugin = {
  efficientPlugin: (result: any, ctx: ICompilationContext) => {
    // 使用缓存避免重复计算
    if (!ctx.cache) ctx.cache = new Map();

    const cacheKey = `plugin-${ctx.filename}`;
    if (ctx.cache.has(cacheKey)) {
      return ctx.cache.get(cacheKey);
    }

    // 昂贵的计算
    const computedValue = expensiveComputation(result);

    ctx.cache.set(cacheKey, computedValue);
    return computedValue;
  },
};

3. 配置化插件

typescript
// 创建可配置的插件工厂
function createConfigurablePlugin(options = {}) {
  return {
    configurablePlugin: (result: any, ctx: ICompilationContext) => {
      const { enabled = true, logLevel = 'info', transformRules = {} } = options;

      if (!enabled) return;

      // 使用配置
      if (logLevel === 'debug') {
        console.debug('Plugin executing for:', ctx.filename);
      }

      // 应用转换规则
      applyTransformRules(result, transformRules);
    },
  };
}

// 使用配置化插件
export default defineConfig({
  plugins: {
    ...createConfigurablePlugin({
      enabled: process.env.NODE_ENV === 'development',
      logLevel: 'debug',
      transformRules: {
        vue: '@vureact/runtime-core',
      },
    }),
  },
});

插件执行顺序

插件按照配置的顺序执行:

  1. 解析阶段插件(parser
  2. 转换阶段插件(transformer
  3. 代码生成阶段插件(codegen
  4. 编译完成插件(直接配置在 plugins 下的插件)
javascript
export default defineConfig({
  plugins: {
    // 这些插件在所有阶段之后执行
    pluginA: () => console.log('最后执行'),
    pluginB: () => console.log('最后执行'),

    parser: {
      // 解析阶段插件
      plugin1: () => console.log('第一阶段执行'),
      plugin2: () => console.log('第一阶段执行'),
    },

    transformer: {
      // 转换阶段插件
      plugin3: () => console.log('第二阶段执行'),
    },

    codegen: {
      // 代码生成阶段插件
      plugin4: () => console.log('第三阶段执行'),
    },
  },
});

调试插件

1. 使用日志

typescript
const debugPlugin = {
  debugPlugin: (result: any, ctx: ICompilationContext) => {
    console.log('=== Plugin Debug Info ===');
    console.log('Filename:', ctx.filename);
    console.log('Stage:', ctx.currentStage);
    console.log('Result type:', result.constructor.name);
    console.log('Result keys:', Object.keys(result));
    console.log('========================');
  },
};

2. 开发工具

typescript
// 插件开发助手
const pluginDevTools = {
  inspectResult: (result: any, ctx: ICompilationContext) => {
    if (process.env.VUREACT_PLUGIN_DEBUG) {
      // 将结果保存到文件以便分析
      const fs = require('fs');
      const path = require('path');

      const debugDir = path.join(ctx.root, '.vureact-debug');
      if (!fs.existsSync(debugDir)) {
        fs.mkdirSync(debugDir, { recursive: true });
      }

      const debugFile = path.join(
        debugDir,
        `${path.basename(ctx.filename)}-${ctx.currentStage}.json`,
      );

      fs.writeFileSync(debugFile, JSON.stringify(result, null, 2));

      console.log(`Debug info saved to: ${debugFile}`);
    }
  },
};

注意事项

  1. 不要修改原始对象:尽量创建新对象或添加新属性,避免修改插件系统的内部状态
  2. 保持插件轻量:插件会在每个文件编译时执行,避免昂贵的操作
  3. 处理异步操作:插件目前是同步的,如果需要异步操作,需要在插件内部处理
  4. 错误边界:确保插件错误不会导致整个编译过程失败
  5. 兼容性:考虑不同版本 VuReact 的兼容性,避免使用内部 API

下一步

  • 参考 API 了解完整的类型定义

Released under the MIT License