自定义构建插件
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 风格插件和自定义钩子。
基础插件模板
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 → 库集成模式
参考资料
- 官方文档:vtj.pro/
- 在线平台:app.vtj.pro/
- 开源仓库:gitee.com/newgateway/…