AI 驱动的 Vue3 应用开发平台 深入探究(二十):CLI与工具链之自定义构建插件

自定义构建插件

VTJ 中的自定义构建插件系统提供了一个灵活的、基于插件的架构,用于跨不同项目类型(应用、库、插件和物料)扩展 Vite 构建流程。该系统使开发者能够注入自定义转换、管理构建时操作,并将第三方工具无缝集成到 VTJ 构建管道中。

插件架构概览

构建插件系统基于 Vite 的插件架构构建,为开发和生产环境提供了统一的接口。核心配置函数接受插件选项,并通过 mergePlugins 函数将其与内置插件合并,创建一个综合的插件管道。

flowchart TD A[createViteConfig] --> B[mergePlugins] B --> C[Built-in Plugins] B --> D[Custom Plugins] B --> E[Final Vite Config] C --> C1[envPlugin] C --> C2[vue, vueJsx] C --> C3[versionPlugin] C --> C4[loadingPlugin] C --> C5[cdnPlugin] C --> C6[copyPlugin] C --> C7[staticPlugin] C --> C8[reloadPlugin] C --> C9[babelPlugin] C --> C10[nodePolyfills] D --> D1[User Custom Plugins] D --> D2[Third-party Plugins] E --> F[Vite Build Pipeline]

内置插件套件

VTJ CLI 提供了一套全面的预配置插件,用于解决常见的构建场景:

Plugin Purpose Configuration Option Apply Mode
envPlugin 环境变量管理 envPath Always
versionPlugin 生成版本文件 version: true buildStart
loadingPlugin 注入加载屏幕 HTML loading: true transformIndexHtml
cdnPlugin 支持上传的 CDN 导入 cdn: CdnPluginOptions build, buildEnd
copyPlugin 复制静态资源到输出目录 copyStatic: true + staticDirs closeBundle
staticPlugin 服务静态目录 staticDirs: Array<StaticPluginOption> configureServer
reloadPlugin 基于版本的热更新 reload: true build + transformIndexHtml
babelPlugin Babel 转换 babel: true + targets build, transform
nodePolyfills 浏览器环境的 Node.js polyfills `node: true PolyfillOptions`

创建自定义插件

自定义插件遵循 Vite 的标准插件接口,可以通过 createViteConfig 中的 plugins 选项进行集成。插件系统同时支持 Vite 的 Rollup 风格插件和自定义钩子。

基础插件模板

typescript 复制代码
import type { Plugin } from "vite";

export function customPlugin(options?: any): Plugin {
  return {
    name: "vtj-custom-plugin",
    apply: "build", // 'serve' | 'build' | undefined
    config(config) {
      // 修改 Vite 配置
      return config;
    },
    transform(code, id) {
      // 转换代码
      return { code, map };
    },
    buildStart() {
      // 构建生命周期钩子
    },
    buildEnd(error) {
      // 构建完成
    },
    closeBundle() {
      // 所有文件写入后
    },
  };
}

示例:版本插件实现

版本插件演示了如何生成构建时产物:

typescript 复制代码
function writeVersion(file: string) {
  const pkg = readJsonSync(resolve("package.json"));
  const date = new Date();
  const year = date.getFullYear();
  const banner = `/**!
 * Copyright (c) ${year}, VTJ.PRO All rights reserved.
 * @name ${pkg.name} 
 * @author CHC chenhuachun1549@dingtalk.com 
 * @version ${pkg.version}
 * @license <a href="https://vtj.pro/license.html">MIT License</a>
 */\n`;
  const code = `export const version = '${pkg.version}';`;
  const content = `${banner}${code}`;
  fs.writeFileSync(resolve(file), content, "utf-8");
}

export function versionPlugin(output: string = "src/version.ts") {
  return {
    name: "write-version",
    buildStart() {
      writeVersion(output);
    },
  };
}

插件配置选项

CreateViteConfigOptions 接口定义了完整的插件配置 API:

flowchart TD A[CreateViteConfigOptions] --> B[Core Plugins] A --> C[Build Plugins] A --> D[Development Plugins] B --> B1[version: boolean] B --> B2[loading: boolean] B --> B3[reload: boolean] B --> B4[babel: boolean] C --> C1[cdn: CdnPluginOptions] C --> C2[copyStatic: boolean] C --> C3[visualizer: boolean] C --> C4[staticDirs: string[]] D --> D1[https: boolean] D --> D2[watchModules: string[]] D --> D3[plugins: PluginOption[]]

专用构建配置

应用模式

标准 Web 应用使用带有默认插件的基础 createViteConfig

typescript 复制代码
import { createViteConfig } from "@vtj/cli";
import { createDevTools } from "@vtj/local";
import proxy from "./proxy.config";

export default createViteConfig({
  proxy,
  plugins: [createDevTools()],
});

库模式

库启用类型生成并禁用特定于浏览器的功能:

typescript 复制代码
export default createViteConfig({
  lib: true,
  dts: true,
  dtsOutputDir: "types",
  entry: "src/index.ts",
  external: ["vue", "@vtj/ui"],
  externalGlobals: {
    vue: "Vue",
    "@vtj/ui": "VtjUI",
  },
  formats: ["es", "cjs", "umd"],
});

插件/物料模式

插件项目支持开发和生产构建,采用不同的配置:

typescript 复制代码
import { createViteConfig, createPluginViteConfig } from "@vtj/cli";
const isUmd = !!process.env.UMD;
const isDev = !!process.env.DEV;

export default isDev
  ? createViteConfig({ plugins: [createDevTools()] })
  : createPluginViteConfig({
      libFileName: pkg.name,
      isUmd,
    });

UniApp 模式

UniApp 项目使用专用的 createUniappViteConfig,插件支持有限:

typescript 复制代码
import { createUniappViteConfig } from "@vtj/cli";

export default createUniappViteConfig({
  envPath: "./",
  node: true,
  plugins: [
    /* custom plugins */
  ],
});

支持上传的 CDN 插件

CDN 插件提供了复杂的依赖管理,并支持可选的上传功能:

typescript 复制代码
export interface CdnPluginOptions {
  modules: CdnPluginModule[];
  basePath: string;
  nodeModulesDir?: string;
  uploader?: (modules: CdnPluginModuleParsed[]) => Promise<void>;
}

export interface CdnPluginModule {
  name: string; // 包名称
  var: string; // 全局变量名称
  path: string; // 包中的文件路径
}

该插件读取包版本,生成 CDN URL,并可选择通过自定义上传器函数上传文件。

构建生命周期钩子

自定义插件可以在构建过程的多个阶段进行钩子操作:

sequenceDiagram participant Build participant Plugin participant Output Build->>Plugin: buildStart() Plugin->>Output: Write artifacts Build->>Plugin: transform(code, id) Plugin-->>Build: Transformed code Build->>Plugin: buildEnd(error) Plugin->>Plugin: Cleanup/Post-processing Build->>Plugin: closeBundle() Plugin->>Output: Final operations

对于构建时文件操作(如复制资源),请使用 closeBundle 钩子以确保所有输出文件都已先写入。有关实现,请参阅 copy plugin

插件注册与合并策略

mergePlugins 函数协调插件注册,确保正确的顺序和条件包含:

typescript 复制代码
export const mergePlugins = (opts: CreateViteConfigOptions) => {
  const plugins: PluginOption[] = [
    envPlugin({ dir: opts.envPath }),
    vue(),
    vueJsx(),
  ];

  // 条件插件
  if (opts.reload) plugins.push(reloadPlugin());
  if (opts.version) plugins.push(versionPlugin());
  if (opts.cdn) plugins.push(cdnPlugin(opts.cdn));

  // 用户插件放在最后
  if (opts.plugins) plugins.push(...opts.plugins);

  return plugins;
};

用户定义的插件附加在内置插件之后,允许它们覆盖或扩展行为。将你的插件放在 plugins 数组中,以确保它们在管道中最后运行。

高级插件模式

HTML 转换插件

使用 transformIndexHtml 钩子转换 HTML 文件:

typescript 复制代码
export function loadingPlugin(): Plugin {
  return {
    name: "vtj-loading-plugin",
    transformIndexHtml(html: string) {
      return html
        .replace("</head>", `${style}</head>`)
        .replace("<body>", `<body>${mask}`)
        .replace("</body>", `${script}</body>`);
    },
  };
}

服务器中间件插件

向开发服务器添加自定义中间件:

typescript 复制代码
export function staticPlugin(
  options: Array<string | StaticPluginOption>,
): Plugin {
  return {
    name: "vtj-static-server",
    configureServer(server) {
      for (let option of opts) {
        server.middlewares.use(
          option.path,
          serveStatic(resolve(option.dir), { setHeaders }),
        );
      }
    },
    configurePreviewServer(server) {
      // 预览时使用相同的中间件
    },
  };
}

构建完成回调

使用 buildEnd 选项在构建完成后执行自定义逻辑:

typescript 复制代码
export default createViteConfig({
  buildEnd: (error?: any) => {
    if (!error) {
      console.log("Build completed successfully!");
      // Deploy, notify, etc.
    }
  },
});

配置文件模板

最小化应用配置

typescript 复制代码
// vite.config.ts
import { createViteConfig } from "@vtj/cli";

export default createViteConfig({
  base: "/",
  outDir: "dist",
  proxy: {
    "/api": "http://localhost:3000",
  },
  plugins: [
    // Your custom plugins here
  ],
});

带类型的库配置

typescript 复制代码
// vite.config.ts
import { createViteConfig } from "@vtj/cli";

export default createViteConfig({
  lib: true,
  entry: "src/index.ts",
  dts: true,
  dtsOutputDir: "types",
  external: ["vue"],
  externalGlobals: {
    vue: "Vue",
  },
  formats: ["es", "cjs"],
});

启用 CDN 的配置

typescript 复制代码
// vite.config.ts
import { createViteConfig } from "@vtj/cli";

export default createViteConfig({
  cdn: {
    basePath: "https://cdn.example.com/",
    modules: [
      { name: "vue", var: "Vue", path: "dist/vue.global.js" },
      { name: "element-plus", var: "ElementPlus", path: "dist/index.full.js" },
    ],
    uploader: async (modules) => {
      // Custom upload implementation
      await uploadToCDN(modules);
    },
  },
});

特定环境的插件

env 插件通过 JSON 文件管理环境配置:

typescript 复制代码
// env.json (defaults)
{
  "API_URL": "http://localhost:3000"
}

// env.production.json
{
  "API_URL": "https://api.example.com"
}

// Access in code
console.log(process.env.API_URL); // Resolved based on ENV_TYPE

调试插件配置

启用调试模式以检查最终合并的配置:

typescript 复制代码
export default createViteConfig({
  debug: true, // Prints final config to console
  // ... other options
});

这将显示完整的 Vite 配置,包括所有合并的插件,有助于排查插件冲突或配置错误。

后续步骤

为了全面了解 VTJ 的构建工具生态系统:

  • Build Configuration and Vite Integration → 深入研究 Vite 配置模式和构建优化
  • Create VTJ CLI Reference → 完整的 CLI 命令和选项参考
  • Development and Production Workflows → 端到端的构建和部署管道

如需在构建插件之外扩展 VTJ:

  • Plugin System Development → 框架级插件架构
  • Integrating Third-Party Libraries → 库集成模式

参考资料

相关推荐
代码搬运媛8 小时前
Jest 测试框架详解与实现指南
前端
counterxing8 小时前
Agent 跑起来之后,难的是复用、观测和评测
node.js·agent·ai编程
uccs8 小时前
大模型底层机制与Agent开发
agent·ai编程·claude
counterxing9 小时前
我把 Codex 里的 Skills 做成了一个 MCP,还支持分享
前端·agent·ai编程
wangqiaowq9 小时前
windows下nginx的安装
linux·服务器·前端
夜雪闻竹9 小时前
vectra 向量索引文件损坏怎么办
ai编程·向量·vectra
ZzT9 小时前
Harness 到底指什么
openai·ai编程·claude
之歆10 小时前
DAY_12JavaScript DOM 完全指南(二):实战与性能篇
开发语言·前端·javascript·ecmascript
宅小年10 小时前
AI 创业最危险的地方:太容易做出来
openai·ai编程·claude
发现一只大呆瓜10 小时前
Vite凭什么这么快?3分钟带你彻底搞懂 Vite 热更新的幕后黑手
前端·面试·vite