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

20 阅读5分钟

自定义构建插件

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 提供了一套全面的预配置插件,用于解决常见的构建场景:

PluginPurposeConfiguration OptionApply Mode
envPlugin环境变量管理envPathAlways
versionPlugin生成版本文件version: truebuildStart
loadingPlugin注入加载屏幕 HTMLloading: truetransformIndexHtml
cdnPlugin支持上传的 CDN 导入cdn: CdnPluginOptionsbuild, buildEnd
copyPlugin复制静态资源到输出目录copyStatic: true + staticDirscloseBundle
staticPlugin服务静态目录staticDirs: Array<StaticPluginOption>configureServer
reloadPlugin基于版本的热更新reload: truebuild + transformIndexHtml
babelPluginBabel 转换babel: true + targetsbuild, transform
nodePolyfills浏览器环境的 Node.js polyfillsnode: true | PolyfillOptions

创建自定义插件

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

基础插件模板

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() {
      // 所有文件写入后
    },
  };
}

示例:版本插件实现

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

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

import { createViteConfig } from "@vtj/cli";
import { createDevTools } from "@vtj/local";
import proxy from "./proxy.config";

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

库模式

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

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"],
});

插件/物料模式

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

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,插件支持有限:

import { createUniappViteConfig } from "@vtj/cli";

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

支持上传的 CDN 插件

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

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 函数协调插件注册,确保正确的顺序和条件包含:

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 文件:

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>`);
    },
  };
}

服务器中间件插件

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

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 选项在构建完成后执行自定义逻辑:

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

配置文件模板

最小化应用配置

// vite.config.ts
import { createViteConfig } from "@vtj/cli";

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

带类型的库配置

// 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 的配置

// 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 文件管理环境配置:

// 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

调试插件配置

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

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 → 库集成模式

参考资料