npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

3. 新建空 Vue 文件

src/views/userCenter/UserCenter.vue

该文件必须是监听启动后新建的空文件。默认会得到:

src/views/userCenter/
├─ UserCenter.vue   # 填充 Vue 模板
├─ UserCenter.less  # createEffect: less
├─ types.ts         # createEffect: type.ts
└─ api.ts           # createEffect: api

acf2 是持续监听进程,使用 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,它就是本次生成流程的触发文件。

触发文件必须满足以下条件:

  1. 位于 watchDir 中。
  2. 文件类型包含在最终的 watchFileType 中。
  3. 是监听启动后新增的文件;启动前已经存在的文件不会处理。
  4. 文件内容为空;非空文件会跳过,避免修改手写内容。

联动文件

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.ts

registerTemplateGenerator 参数

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 时,会进入组件模式:

  1. 使用 comVue 类型生成 Vue 内容。
  2. 使用 less 类型生成样式文件。
  3. 跳过普通 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 类型:

  • Acf2Config
  • ResolvedAcf2Config
  • Acf2Data
  • BuiltinFileType
  • FileType
  • CreateEffect
  • CreateEffectMap
  • WriteFile
  • WriteFileMap
  • BaseWriteFileFnOptions
  • ReplaceTemplateContentOptions
  • ReplaceTemplateContentReplacerParams
  • ResolveWritePathOptions
  • TemplateVars
  • TemplateGeneratorFile
  • TemplateGeneratorOptions
  • TemplateGeneratorMap

写入与覆盖行为

请在使用前了解以下规则:

  • 监听器只响应新增事件,不响应文件修改。
  • 新增的触发文件非空时,整个生成流程会跳过。
  • 生成目标文件时会直接写入目标路径;同名目标已经存在时会被覆盖。
  • 一个生成器的 files 按顺序执行,指向同一路径时后面的结果会覆盖前面的结果。
  • 模板输出目录不存在时会自动创建。
  • 批量创建、移动文件或执行可能产生大量新增事件的操作前,建议先停止监听。

常见问题

注册了 Vue 模板,为什么没有生效?

确认以下几点:

  1. 注册 key 是 vue,组件目录则应使用 comVue。
  2. registerTemplateGenerator 在 runAcf2 之前执行。
  3. templatePath 指向真实模板文件,推荐使用 fileURLToPath(new URL(...))。
  4. 测试文件是在监听启动后新建的空文件。
  5. 使用的是包含“触发文件优先走模板生成器”逻辑的新版本构建产物。

注册了自定义类型,但新建文件没有反应?

如果显式配置了 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 与源码一致。