Vite插件开发实战AI提交前审查代码坏味道

01 为什么要做提交前AI审查

团队Code Review永远是稀缺资源。Senior忙、Junior看不出深层问题,最后Review流于形式。ESLint管语法一致性,管不了"这段逻辑绕不绕"。大模型恰恰擅长这一层。

Vite插件机制给了我们一个绝佳拦截点:transform钩子在每个模块加载时触发。我们能拿到源码、异步调API、把结果塞回构建日志,不需要重写CI。

这篇文章从0到1写一个能跑的Vite插件,在vite build时把代码喂给大模型,让它在控制台标出坏味道。完整源码可复制运行,依赖版本清晰标注。

02 环境准备+依赖清单

json 复制代码
// package.json
{
  "dependencies": {
    "vite": "^5.4.0",
    "openai": "^4.67.0"
  },
  "devDependencies": {
    "typescript": "^5.4.0",
    "@types/node": "^20.14.0"
  }
}

环境变量配置:

ini 复制代码
// .env
AI_REVIEW_API_KEY=sk-xxx
AI_REVIEW_BASE_URL=https://api.deepseek.com/v1

兼容OpenAI接口的厂商均可:OpenAI、DeepSeek、Moonshot、智谱、通义千问,换个baseURL就能切。

03 Vite插件最小骨架

Vite插件本质是带name的对象。关键字段:

  • name:插件唯一标识
  • enforce: 'pre':排在esbuild之前,拿到原始TS/JSX源码
  • apply: 'build':只在构建时生效
  • transform(code, id):核心钩子,每个模块都会触发
typescript 复制代码
// vite-plugin-ai-review.ts
import type { Plugin } from 'vite';

export interface AiReviewOptions {
  apiKey: string;
  baseURL?: string;
  model?: string;
  failOnError?: boolean;
}

export default function aiReview(options: AiReviewOptions): Plugin {
  return {
    name: 'vite-plugin-ai-review',
    enforce: 'pre',
    apply: 'build',
    async transform(code, id) {
      const cleanId = id.split('?')[0];
      if (!/.(ts|tsx|js|jsx)$/.test(cleanId)) return null;
      if (/node_modules|/dist/|.d.ts$|.test./.test(cleanId)) return null;

      const issues = await reviewCode(code, cleanId, options);
      for (const issue of issues) {
        const label = `[ai-review] ${cleanId}:${issue.line} [${issue.severity}] ${issue.message}`;
        if (issue.severity === 'error' && options.failOnError) {
          this.error(label);
        } else {
          this.warn(label);
        }
      }
      return null;
    },
  };
}

this.error会让构建失败,this.warn只输出警告。两者都被Vite集成进构建日志和CI退出码,不要用console.log

04 大模型API接入

用官方openaiSDK,支持任意兼容接口:

php 复制代码
// llm.ts
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey=***!,
  baseURL: process.env.AI_REVIEW_BASE_URL ?? 'https://api.openai.com/v1',
});

const SYSTEM_PROMPT = `You are a senior code reviewer. Identify code smells: long functions, deep nesting, duplicated logic, swallowed errors, magic numbers.
Return STRICT JSON: {"issues":[{"line":number,"severity":"error"|"warning"|"info","message":string,"suggestion":string}]}
If no issues, return {"issues":[]}`;

export async function callLLM(code: string, filePath: string, model = 'gpt-4o-mini') {
  const resp = await client.chat.completions.create({
    model,
    messages: [
      { role: 'system', content: SYSTEM_PROMPT },
      { role: 'user', content: `File: ${filePath}${code}},
    ],
    temperature: 0.2,
    max_tokens: 1500,
  });
  const content = resp.choices[0]?.message?.content ?? '{}';
  try {
    const parsed = JSON.parse(content);
    return Array.isArray(parsed.issues) ? parsed.issues.slice(0, 20) : [];
  } catch {
    return [];
  }
}

注意三点:temperature: 0.2比0更稳定;max_tokens: 1500防止账单爆炸;JSON解析失败时降级为空数组,不让构建挂掉。

05 坏味道Prompt设计

Prompt是这套插件最值钱的部分。五个关键点:

强制JSON输出。早期写"请审查代码",模型返回大白话无法解析。改成Return STRICT JSON+schema贴prompt里,解析成功率从60%拉到98%。

明确severity语义。error=必须改(安全/逻辑bug)、warning=建议改(长函数/嵌套)、info=提示。在prompt里给出例子,模型才知道分类。

支持空返回。模型有"必须找出问题"的倾向,干净代码也会被编出毛病。强制{"issues":[]}非常必要。

设max_tokens。不设模型会"滔滔不绝"解释每个小问题,账单爆炸。1500 tokens够审查中等文件。

temperature用0.2而非0。0在某些模型上会出现贪心采样退化,两次审查结果不一致。0.2带一点随机性,反而让模型更敢说真话。

06 vite.config.ts完整接入

javascript 复制代码
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import aiReview from './vite-plugin-ai-review';

export default defineConfig({
  plugins: [
    aiReview({
      apiKey=***!,
      baseURL: 'https://api.deepseek.com/v1',
      model: 'deepseek-chat',
      failOnError: process.env.CI === 'true',
    }),
    react(),
  ],
});

成本数据(实战参考):

  • 300个TS文件全量审查:deepseek-chat约0.04美元/次,gpt-4o-mini约0.15美元/次
  • 加hash缓存后二次构建成本降至5%以下
  • CI建议挂vite build --mode production,作为PR硬门禁

性能优化建议:用git diff --cached --name-only拿暂存区文件列表,在include里精确匹配,只审查PR实际改动的部分。dev模式默认关闭,避免HMR触发频繁API调用。

07 跑通验证+常见报错

实战两周数据(15万行中后台项目):

  • 平均CI审查耗时:47秒
  • 误报率:8%
  • 漏报率:12%
  • 阻断PR:23个,其中19个确实是坏味道

常见报错与解决方案:

  1. JSON.parse失败。某些兼容接口偶尔返回非JSON前缀。解决方案:用正则提取{...}段再解析,或直接降级为空issues。

  2. rate limit 429。Vite build全量并发打爆API。解决方案:加并发限流器,限到5并发。

  3. HMR触发频繁调用。dev模式每次保存都调API。解决方案:enableInServe: false,dev默认关。

  4. 生成代码被审查。GraphQL codegen、openapi-typescript生成的文件动辄几千行,模型编出无数"问题"。解决方案:exclude里加掉所有codegen目录。

  5. 测试文件被审查。测试代码里的magic number和长函数是合理的,AI不懂这个上下文。解决方案:exclude.test..spec.

  6. CI超时。LLM调用层没设超时会卡死整个构建。解决方案:自己加AbortController,30秒超时降级。

08 互动

你的团队目前Code Review靠人还是靠规则?

如果误报率10%是当前上限,你会怎么调prompt让它更贴合你的业务?

相关推荐
Code额1 小时前
Python 连接 DeepSeek API,OpenAI 对话方式总结
后端·python·ai·ai编程
星火10241 小时前
【LangChain4j系列10】Guardrails 安全护栏
人工智能·后端
颜进强1 小时前
14 - OpenSpec 老页面改造骨架:定位 + 增量 + 回归三件套
前端·后端·ai编程
PFFstronger1 小时前
AI到底如何生成测试用例
人工智能
LHX sir1 小时前
医疗设备外设多、协议杂?HubPort 让出厂设备自带 AI
人工智能·物联网·医疗设备·设备智能化
chuan.bai1 小时前
Java RAG 实战附录:qwen3 与 bge-m3 模型切换指南
java·人工智能·算法
武子康2 小时前
Pi Extension 写完不等于可用:从类型检查到真实 Runtime 的证据阶梯
人工智能·llm·agent
润乾软件2 小时前
如何给已有报表系统增加 AI 语言查询能力——AI 赋能数据分析技术探讨与实践
人工智能·chatbi
微硬创新2 小时前
不用换设备,也能接入智能系统:耐达讯自动化NY-B801网关的神奇功效
人工智能·网络协议·自动化·信息与通信