@guoxianxin/acf2
v0.0.11
Published
`acf2` 是一个面向 Vue 项目的文件监听与模板生成工具。
Readme
@guoxianxin/acf2
acf2 是一个面向 Vue 项目的文件监听与模板生成工具。
启动监听后,在指定目录中新建空文件,acf2 会填充触发文件,并按配置生成样式、类型、接口、国际化等配套文件。它既提供开箱即用的内置模板,也允许使用项目自己的模板完全覆盖生成结果。
特性
- 监听新建文件并自动生成配套文件
- 内置 Vue、Less、类型、API 和组件 Vue 模板
- 支持覆盖触发文件模板,例如使用项目模板填充新建的
.vue - 支持一个生成类型输出多个文件或子目录
- 支持注册自定义写入函数
- 支持零安装 JSON 配置和 JSON Schema 自动补全
- 支持声明式
templateGenerators,无需导入注册 API - 支持
_acf2_*模板变量和自定义变量替换 - 提供完整的 TypeScript 类型提示和
defineAcf2Config配置辅助函数
快速开始
1. 初始化零安装配置
在项目根目录运行:
npx @guoxianxin/acf2 init默认生成 acf2.config.json。配置通过 JSON Schema 获得字段说明、内置类型补全和校验,不需要在项目中安装 @guoxianxin/acf2:
{
"$schema": "https://unpkg.com/@guoxianxin/acf2@latest/schema/acf2.schema.json",
"watchDir": "src/views",
"watchFileType": ["vue"],
"createEffect": {
"vue": ["less", "type.ts", "api"]
},
"acf2Data": {
"styleLang": "less"
}
}初始化命令实际会把 Schema 地址固定到当前 CLI 版本,避免配置提示与运行版本不一致。
2. 启动监听
npx @guoxianxin/acf23. 新建空 Vue 文件
src/views/userCenter/UserCenter.vue该文件必须是监听启动后新建的空文件。默认会得到:
src/views/userCenter/
├─ UserCenter.vue # 填充 Vue 模板
├─ UserCenter.less # createEffect: less
├─ types.ts # createEffect: type.ts
└─ api.ts # createEffect: apiacf2 是持续监听进程,使用 Ctrl+C 停止。
TypeScript 高级配置
需要函数式配置、自定义写入函数或完整 TypeScript 类型时,再把包安装为开发依赖:
pnpm add -D @guoxianxin/acf2
npx acf2 init --typescript也可以使用 npm 或 yarn 安装。生成的 acf2.config.ts 如下:
import { defineAcf2Config } from "@guoxianxin/acf2";
export default defineAcf2Config({
watchDir: "src/views",
createEffect: {
vue: () => ["less", "type.ts", "api"],
},
acf2Data: {
styleLang: "less",
},
});CLI 会依次查找 acf2.config.ts、acf2.config.mts、acf2.config.cts、acf2.config.js、acf2.config.mjs、acf2.config.cjs 和 acf2.config.json。如需覆盖已有配置,可在初始化命令后添加 --force。
即使项目未安装包,CLI 运行时仍能解析 TS/JS 配置中对 @guoxianxin/acf2 的导入;但编辑器若要提供 TypeScript 类型,仍需要本地开发依赖。
没有找到配置文件时会使用内置默认配置,适合快速体验。
常用选项:
# 使用指定配置文件
npx @guoxianxin/acf2 --config ./config/acf2.config.json
# 以指定目录作为项目工作目录
npx @guoxianxin/acf2 --cwd ./apps/admin
# 查看帮助和版本
npx @guoxianxin/acf2 --help
npx @guoxianxin/acf2 --version--config 的相对路径基于工作目录解析;watchDir 同样相对于工作目录解析。
核心概念
触发文件
监听启动后新增的文件。例如新建 UserCenter.vue,它就是本次生成流程的触发文件。
触发文件必须满足以下条件:
- 位于
watchDir中。 - 文件类型包含在最终的
watchFileType中。 - 是监听启动后新增的文件;启动前已经存在的文件不会处理。
- 文件内容为空;非空文件会跳过,避免修改手写内容。
联动文件
createEffect 返回的类型就是联动文件。例如:
createEffect: {
vue: () => ["less", "type.ts", "api", "i18n"],
}新建空 Vue 文件后,会依次生成 less、type.ts、api 和 i18n 对应的文件。
数组也可以直接作为规则:
createEffect: {
vue: ["less", "type.ts", "api"],
}生成器优先级
触发文件和联动文件使用相同的生成优先级:
registerTemplateGenerator 注册的同类型模板
↓ 未注册
registerWriteFile 注册的函数或 acf2 内置生成函数
↓ 都不存在
输出“没有对应的文件生成函数”警告例如注册 vue 模板后,新建空 .vue 文件会直接使用自定义模板,不会再使用内置 Vue 模板。
默认配置
不传配置时等价于:
{
watchDir: "src/views",
watchFileType: ["vue", "less", "type.ts"],
createEffect: {
vue: () => ["less", "type.ts", "api"],
},
acf2Data: {
styleLang: "less",
stylePath: "",
fileName: "",
ext: "",
},
}用户配置与默认配置采用浅合并。因此如果传入 acf2Data,建议保留需要使用的默认字段,尤其是 styleLang 和必填的 ext。
配置说明
watchDir
监听目录,相对于启动命令的当前工作目录。
watchDir: "src/views"如果在 monorepo 根目录运行脚本,路径也需要从 monorepo 根目录计算。最简单的判断方式是查看启动日志中的“监听路径”。
watchFileType
允许触发生成流程的文件类型:
watchFileType: ["vue", "less", "type.ts", "md"]注意:
types.ts会被识别为特殊类型type.ts。- 不显式传入时,内置类型、
registerWriteFile类型和registerTemplateGenerator类型会自动合并到监听列表。 - 一旦显式传入数组,它会完整覆盖自动生成的监听列表。自定义类型需要手动写进数组。
- 传入空数组时不会执行类型过滤,相当于允许所有新增文件进入后续流程。
通常不需要配置该字段,让 acf2 自动收集类型更省心。
createEffect
定义“哪种触发文件需要生成哪些联动类型”:
createEffect: {
vue: () => ["less", "type.ts", "api", "i18n"],
md: () => ["json"],
}对象的 key 是触发文件扩展名,value 可以是数组或返回数组的函数。
联动数组中的每个值只是“生成类型”,不一定等于最终文件扩展名。比如 i18n 可以一次生成三个 .ts 文件。
acf2Data
生成过程中的运行时数据,也是默认模板变量来源:
| 字段 | 说明 | 示例 |
| --- | --- | --- |
| fileName | 触发文件名,不含扩展名 | UserCenter |
| ext | 触发文件扩展名 | vue |
| snakeName | 文件名转换后的下划线名称 | user_center |
| styleLang | Vue 模板中的样式语言 | less |
| stylePath | 自动生成的样式导入语句 | @import url("./UserCenter.less"); |
| filePath | 触发文件完整路径 | D:/project/src/views/UserCenter.vue |
| dirPath | 触发文件所在目录 | D:/project/src/views/ |
运行时会根据触发文件更新除 styleLang 以外的常用字段。
使用自定义模板
零安装 JSON 配置可以通过 templateGenerators 声明模板。相对 templatePath 会基于配置文件所在目录解析:
{
"watchDir": "src/views",
"createEffect": {
"vue": ["less", "i18n"]
},
"templateGenerators": {
"i18n": {
"files": [
{
"dir": "i18n",
"fileName": "zh-CN",
"ext": "ts",
"templatePath": "./templates/i18n.ts"
},
{
"dir": "i18n",
"fileName": "en-US",
"ext": "ts",
"templatePath": "./templates/i18n.ts"
}
]
}
}
}TypeScript 高级配置则使用 registerTemplateGenerator。它既能覆盖触发文件,也适合动态组织模板路径。
完整示例:覆盖 Vue、API 和 i18n
import { fileURLToPath } from "node:url";
import {
defineAcf2Config,
registerTemplateGenerator,
runAcf2,
} from "@guoxianxin/acf2";
const templatePath = (name) =>
fileURLToPath(new URL(`../template/${name}`, import.meta.url));
registerTemplateGenerator({
vue: {
files: [
{
ext: "vue",
templatePath: templatePath("template.vue"),
},
],
},
api: {
files: [
{
fileName: "api",
ext: "ts",
templatePath: templatePath("template.api.ts"),
},
],
},
i18n: {
files: [
{
dir: "i18n",
fileName: "zh-cn",
ext: "ts",
templatePath: templatePath("template.i18n.ts"),
},
{
dir: "i18n",
fileName: "zh-hk",
ext: "ts",
templatePath: templatePath("template.i18n.ts"),
},
{
dir: "i18n",
fileName: "zh-tw",
ext: "ts",
templatePath: templatePath("template.i18n.ts"),
},
],
},
});
runAcf2(
defineAcf2Config({
watchDir: "src/views",
createEffect: {
vue: () => ["less", "i18n", "type.ts", "api"],
},
acf2Data: {
styleLang: "less",
ext: "",
},
})
);新建 src/views/orderDetail/OrderDetail.vue 后得到:
src/views/orderDetail/
├─ OrderDetail.vue # 自定义 vue 生成器
├─ OrderDetail.less # 未注册,回退内置 less 生成器
├─ types.ts # 未注册,回退内置 type.ts 生成器
├─ api.ts # 自定义 api 生成器
└─ i18n/
├─ zh-cn.ts # 自定义 i18n 生成器
├─ zh-hk.ts
└─ zh-tw.tsregisterTemplateGenerator 参数
registerTemplateGenerator({
[type: string]: {
files: Array<{
templatePath: string;
ext: string;
fileName?: string;
dir?: string;
}>;
};
});| 字段 | 必填 | 说明 |
| --- | --- | --- |
| type | 是 | 注册类型,对应触发类型或 createEffect 中的值 |
| files | 是 | 当前类型需要生成的文件列表,按数组顺序写入 |
| templatePath | 是 | 模板文件路径,推荐传绝对路径 |
| ext | 是 | 输出文件扩展名,不带 . |
| fileName | 否 | 输出文件名,不含扩展名;默认使用触发文件名 |
| dir | 否 | 相对触发文件所在目录的子目录 |
同一个 type 再次注册时,后注册内容会完整覆盖前一次注册。
模板占位符
模板中可以使用 _acf2_字段名:
<template>
<div class="_acf2_snakeName">_acf2_fileName</div>
</template>
<style lang="_acf2_styleLang" scoped>
_acf2_stylePath
</style>默认可用变量:
| 占位符 | 含义 |
| --- | --- |
| _acf2_fileName | 触发文件名 |
| _acf2_ext | 触发文件扩展名 |
| _acf2_snakeName | 下划线文件名 |
| _acf2_styleLang | 样式语言 |
| _acf2_stylePath | 样式导入语句 |
| _acf2_filePath | 触发文件完整路径 |
| _acf2_dirPath | 触发文件所在目录 |
替换规则:
- 匹配格式为
_acf2_加字母、数字或下划线。 - 变量存在时会转换成字符串后替换。
- 变量不存在时保留原始占位符,方便发现拼写错误。
components 目录规则
当新增的 Vue 文件路径包含 components 时,会进入组件模式:
- 使用
comVue类型生成 Vue 内容。 - 使用
less类型生成样式文件。 - 跳过普通
createEffect.vue联动。
覆盖组件模板时应注册 comVue,不是 vue:
registerTemplateGenerator({
comVue: {
files: [
{
ext: "vue",
templatePath: templatePath("template.component.vue"),
},
],
},
});注册自定义写入函数
需要自行读取数据、调用接口或进行复杂转换时,可以使用 registerWriteFile:
import {
baseWriteFileFn,
defineAcf2Config,
registerWriteFile,
runAcf2,
} from "@guoxianxin/acf2";
import { fileURLToPath } from "node:url";
registerWriteFile({
md: async (config) => {
await baseWriteFileFn({
config,
ext: "md",
templatePath: fileURLToPath(
new URL("../template/template.md", import.meta.url)
),
});
},
});
runAcf2(
defineAcf2Config({
watchDir: "src/views",
createEffect: {
vue: () => ["md"],
},
})
);说明:
- key 是生成类型,可以是内置类型或任意自定义类型。
- 注册与内置类型同名的函数会覆盖内置函数。
- 如果同一类型也注册了
registerTemplateGenerator,模板生成器优先。 - 不显式配置
watchFileType时,自定义类型会自动加入监听列表。
模板处理工具
baseWriteFileFn
完成“读取模板 → 构建变量 → 替换占位符 → 写入文件”的完整流程:
await baseWriteFileFn({
config,
templatePath: "/absolute/path/template.md",
ext: "md",
fileName: "README",
dir: "docs",
});最终路径为:
触发文件目录/docs/README.md拆分处理流程
需要自定义变量或替换规则时,可以组合底层工具:
import {
buildTemplateVars,
readTemplate,
registerWriteFile,
replaceTemplateContent,
resolveWritePath,
writeGeneratedFile,
} from "@guoxianxin/acf2";
registerWriteFile({
md: async (config) => {
const template = await readTemplate("/absolute/path/template.md");
const vars = buildTemplateVars(config);
const content = replaceTemplateContent(template, vars, {
replacer: ({ key, value, vars }) => {
if (key === "fileNameUpper") {
return (vars.fileName || "").toUpperCase();
}
return value;
},
});
const writePath = resolveWritePath({
config,
ext: "md",
dir: "docs",
});
await writeGeneratedFile(writePath, content);
},
});底层函数:
| 函数 | 作用 |
| --- | --- |
| readTemplate(templatePath) | 读取 UTF-8 模板文件 |
| buildTemplateVars(config) | 将 acf2Data 转为字符串变量表 |
| replaceTemplateContent(content, vars, options?) | 替换模板占位符 |
| resolveWritePath(options) | 计算输出路径 |
| writeGeneratedFile(path, content) | 创建父目录并写入文件 |
replaceTemplateContent 支持自定义前缀:
replaceTemplateContent("acf2_fileName", vars, {
prefix: "acf2_",
});prefix 会直接参与正则表达式构造,因此应使用不含正则特殊字符的前缀。实际项目建议继续使用默认 _acf2_;复杂模板语法可以在写入前自行处理。
TypeScript 类型提示
defineAcf2Config 会保留配置中的具体字面量类型,同时提供字段说明和内置文件类型补全:
const config = defineAcf2Config({
watchFileType: [
"vue", // 内置类型,有自动补全
"custom-page", // 自定义字符串同样合法
],
createEffect: {
vue: () => ["less", "custom-output"],
},
});公共类型可以直接从包入口导入:
import type {
Acf2Config,
FileType,
ResolvedAcf2Config,
TemplateGeneratorMap,
WriteFileMap,
} from "@guoxianxin/acf2";Acf2Config 用于用户输入;ResolvedAcf2Config 表示合并默认值后传给生成函数的运行时配置。
API 一览
| API | 说明 |
| --- | --- |
| runAcf2(config?) | 合并配置并启动监听 |
| defineAcf2Config(config) | 原样返回配置,用于获得类型提示 |
| registerTemplateGenerator(map) | 注册静态模板生成器,优先级最高 |
| registerWriteFile(map) | 注册自定义写入函数 |
| baseWriteFileFn(options) | 使用模板生成文件 |
| readTemplate(path) | 读取模板 |
| writeGeneratedFile(path, content) | 写入文件并创建父目录 |
| buildTemplateVars(input) | 创建模板变量表 |
| replaceTemplateContent(content, vars, options?) | 替换模板变量 |
| resolveWritePath(options) | 计算目标文件路径 |
公开 TypeScript 类型:
Acf2ConfigResolvedAcf2ConfigAcf2DataBuiltinFileTypeFileTypeCreateEffectCreateEffectMapWriteFileWriteFileMapBaseWriteFileFnOptionsReplaceTemplateContentOptionsReplaceTemplateContentReplacerParamsResolveWritePathOptionsTemplateVarsTemplateGeneratorFileTemplateGeneratorOptionsTemplateGeneratorMap
写入与覆盖行为
请在使用前了解以下规则:
- 监听器只响应新增事件,不响应文件修改。
- 新增的触发文件非空时,整个生成流程会跳过。
- 生成目标文件时会直接写入目标路径;同名目标已经存在时会被覆盖。
- 一个生成器的
files按顺序执行,指向同一路径时后面的结果会覆盖前面的结果。 - 模板输出目录不存在时会自动创建。
- 批量创建、移动文件或执行可能产生大量新增事件的操作前,建议先停止监听。
常见问题
注册了 Vue 模板,为什么没有生效?
确认以下几点:
- 注册 key 是
vue,组件目录则应使用comVue。 registerTemplateGenerator在runAcf2之前执行。templatePath指向真实模板文件,推荐使用fileURLToPath(new URL(...))。- 测试文件是在监听启动后新建的空文件。
- 使用的是包含“触发文件优先走模板生成器”逻辑的新版本构建产物。
注册了自定义类型,但新建文件没有反应?
如果显式配置了 watchFileType,请把自定义类型也加入数组。显式数组不会自动合并注册类型。
为什么修改已有文件没有生成内容?
监听器只处理新增事件。请在监听启动后创建新的空文件。
为什么新建的文件被跳过?
文件中只要存在非空白内容,就会被视为已有内容并跳过。先创建空文件,等待生成完成后再编辑。
为什么模板路径在不同目录运行时失效?
普通相对路径依赖启动命令的当前目录。ESM 项目建议始终这样构造绝对路径:
fileURLToPath(new URL("../template/template.vue", import.meta.url));为什么生成了 name.undefined 样式路径?
传入的 acf2Data 会浅覆盖默认对象。如果自定义了 acf2Data,请同时提供 styleLang:
acf2Data: {
styleLang: "less",
ext: "",
}开发命令
在仓库中开发 acf2 包时:
pnpm --filter @guoxianxin/acf2 run dev
pnpm --filter @guoxianxin/acf2 run build
pnpm --filter @guoxianxin/acf2 run test
pnpm --filter @guoxianxin/acf2 run typecheck:test发布前建议至少执行 build 和 test,确保 dist 与源码一致。
