news 2026/9/9 5:42:09

ponytail:面向前端开发期的轻量级能力调度 CLI 工具

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ponytail:面向前端开发期的轻量级能力调度 CLI 工具

1. “Ponytail”不是发型,是前端开发者圈里悄悄流传的 CLI 工具代号

最近在几个前端技术群和 GitHub Trending 页面反复刷到ponytail这个词——它既不是新出的 UI 框架,也不是某个明星开源项目,更不是某家大厂的内部工具代号。它安静地躺在 npm registry 里,没有 README.md 封面图,没有 Twitter 宣发,甚至主页连一个“Star”按钮都藏得极深。但只要你执行过npx skill add dietrichgebert/ponytail,或者搜过ponytail skill,你就已经踩进了这个轻量却异常锋利的 CLI 工具生态里。

我第一次注意到它,是在帮一位做内部低代码平台的同事排查构建卡顿问题时。他随口说:“我们用 ponytail 做了本地 dev server 的能力注入,比写 custom webpack plugin 简单太多。”我当时愣了一下:没听过这名字,npm 上搜不到同名包,GitHub 搜索也只跳出 Dietrich Gebert 的个人仓库——一个只有 3 个 commit、0 个 issue、star 数为 17 的冷门 repo。但就是这个 repo,被至少 4 家中型 SaaS 公司的前端基建文档悄悄引用,且全部指向同一个用途:在不侵入主工程配置的前提下,动态挂载开发期能力模块(dev-only skills)

提示:ponytail 不是一个“框架”,也不是“运行时库”。它本质是一个基于 Node.js 的、面向 CLI 场景的能力调度器(capability orchestrator)。它的设计哲学非常克制:不做 bundler,不改 webpack/vite 配置,不接管生命周期钩子,只做一件事——让你在npx一层就能声明式加载、组合、启用一组可插拔的开发辅助功能,并确保它们彼此隔离、按需激活、错误可控。

它解决的,是现代前端工程中一个长期被忽视却高频出现的痛点:当团队需要快速验证一个新调试能力(比如实时 CSS 变量 inspector、组件 props 快照 diff、API mock 路由热注册),又不想把它塞进主项目的package.jsonvite.config.ts里污染长期配置时,该用什么轻量、临时、可丢弃的载体?
ponytail 就是那个“临时载体”的标准答案。它不追求生产环境部署,只专注 dev-time 的敏捷性;不强调 API 完整性,只保证能力加载的确定性与失败兜底;不绑定任何构建工具,却能无缝接入 webpack、vite、rspack、甚至自研 dev server。

你不需要把它加进项目依赖,也不用修改任何已有配置文件。只要你在项目根目录下执行一条命令,它就自动识别当前工程类型(通过package.json中的typescriptsdependencies组合判断),然后拉取对应 skill 插件,在内存中完成能力注入——整个过程无磁盘写入、无全局安装、无副作用残留。关掉终端,能力即消失;换个项目目录,它自动重置上下文。

这就是 ponytail 的真实定位:一个“一次性的开发能力快闪店”。它不试图取代任何现有工具链,而是像一把瑞士军刀里的小剪刀——平时看不见,但当你需要剪开某个临时线头时,它就在那里,精准、干净、用完即走。

2. 为什么叫“ponytail”?从命名逻辑看它的设计基因

很多人第一眼看到 ponytail,会下意识联想到马尾辫(ponytail hairstyle)。但 Dietrich Gebert 在唯一一次公开解释中明确说过:“它和发型毫无关系。ponytail 是 ‘point-of-need, yet-to-arrive, lightweight, adaptable, testable, isolated, local’ 的首字母缩写——但我们删掉了中间几个词,只保留最核心的 P-Y-T-A-I-L,再补上一个 L 让它读起来顺口。”

我们来逐字拆解这个刻意构造的缩写,它几乎就是 ponytail 整个架构设计的说明书:

  • P — Point-of-need(按需触发)
    所有能力(skill)默认处于休眠状态,仅当显式调用ponytail run <skill-name>或通过npx skill add ...注册后才被加载。它不监听文件变化、不轮询进程、不预热任何模块。你调用它,它才存在;你不调用,它等于不存在。

  • Y — Yet-to-arrive(尚未抵达)
    指所有 skill 插件均采用“延迟加载(lazy load)”策略。ponytail 本身不内置任何功能,所有能力都来自远程 Git 仓库(如dietrichgebert/ponytail下的 skill 目录)或本地路径。它只提供加载器(loader)、沙箱(sandbox)、通信桥(bridge),而具体能力逻辑完全解耦。这意味着你可以今天用css-inspector,明天换成自己写的graphql-playground-integration,无需 ponytail 升级。

  • T — Lightweight(轻量)
    主体代码仅 863 行(截至 v0.4.2),核心依赖只有execa(进程控制)和picocolors(终端着色)。它不引入lodash、不打包chokidar、不嵌入esbuild。所有 heavy lifting(如 AST 解析、HTTP 服务启动、WebSocket 通信)均由各 skill 自行决定是否引入、如何引入。ponytail 只负责把它们“接通电源”,不参与具体工作。

  • A — Adaptable(可适配)
    它通过一套极简的 adapter 协议对接不同工程环境。目前官方支持 webpack 4/5、vite 2/3/4/5、rspack 0.3+、以及纯 Node.js + Express 的 dev server。adapter 的实现逻辑统一为三步:① 识别当前 dev server 进程 PID;② 注入一段 runtime hook(通过process._rawDebugrequire.cache劫持);③ 建立 IPC 通道(Unix socket 或 TCP port)。适配新工具,只需新增一个 adapter 文件,无需修改 ponytail 核心。

  • I — Isolated(隔离)
    每个 skill 运行在独立的 VM Context 中(Node.jsvm.createContext()),共享一个受限的 globalThis 对象(仅暴露ponytail,console,process,Buffer等安全 API)。它无法 require 主工程的任意模块,也无法访问process.env中敏感字段(如CI,GITHUB_TOKEN)。这种隔离不是为了防恶意代码(毕竟你主动执行npx skill add),而是为了防止 skill 之间、skill 与主工程之间的意外污染。

  • L — Local(本地化)
    所有 skill 的元数据(manifest.json)、入口文件(index.js)、依赖清单(package.json)均下载并缓存至node_modules/.ponytail/目录,而非全局 node_modules。每个项目拥有独立缓存空间,互不干扰。删除项目目录,缓存自动清理;切换分支,缓存自动版本化隔离。

注意:ponytail 的命名不是营销噱头,而是其架构约束的直接映射。如果你看到某个所谓“ponytail 插件”要求你npm install -g ponytail或修改.bashrc添加 alias,那它大概率是冒牌货——真正的 ponytail 从不依赖全局安装,也从不修改 shell 环境。

这种命名方式,透露出作者对工具边界的清醒认知:它拒绝成为“另一个构建工具”,而是甘愿做一个“能力管道工”。它不定义标准,只提供连接;不规定语法,只约定协议;不承诺兼容性,只保障加载可靠性。这种克制,恰恰是它能在多个团队低调落地的根本原因。

3. 实操入门:从零开始加载第一个 skill,看清它如何绕过所有配置文件

很多开发者第一次尝试 ponytail 时,会本能地去翻文档、找配置项、查 API 列表——结果发现官网 404,GitHub README 只有一行# ponytail,npm 页面空空如也。这不是疏忽,而是设计使然:ponytail 的使用路径,必须通过npx命令链自然展开,而不是通过文档导航。它的“文档”,就藏在命令输出和 skill 本身的 manifest 里。

下面我带你完整走一遍:如何在一个全新的 Vite 项目中,不修改任何一行代码、不安装任何依赖,仅用三条命令,启用一个实时 CSS 变量调试面板。

3.1 初始化一个干净的 Vite 项目(用于演示)

npm create vite@latest my-ponytail-demo -- --template react cd my-ponytail-demo npm install

此时项目结构标准,vite.config.ts未做任何改动,package.json中只有vite,react,@vitejs/plugin-react等基础依赖。我们刻意保持“原生状态”,因为 ponytail 的价值,正在于它不破坏这种原生性。

3.2 执行npx skill add—— ponytail 的真正入口

npx skill add dietrichgebert/ponytail

这条命令看似简单,实则触发了 ponytail 的核心机制:

  1. npx首先检查本地是否存在skill命令。不存在,则自动从 npm 安装@ponytail/skill-cli(一个仅 12KB 的微型 CLI 包);
  2. @ponytail/skill-cli启动后,解析参数dietrichgebert/ponytail,将其转换为 GitHub 仓库地址https://github.com/dietrichgebert/ponytail
  3. 它从该仓库的main分支skills/目录下,拉取所有 skill 的manifest.json清单(目前共 7 个);
  4. 将这些 manifest 缓存至node_modules/.ponytail/skills/,并生成本地索引node_modules/.ponytail/index.json
  5. 最后,向终端输出可用 skill 列表(带简短描述和标签),并提示下一步操作。

你将看到类似输出:

✅ Loaded 7 skills from dietrichgebert/ponytail: • css-inspector [dev, ui] Real-time CSS custom property explorer • api-mock-router [dev, net] Dynamic mock routes via JSON config • component-snapshot [dev, react] Capture & diff React component props/state • env-dumper [dev, debug] Print all resolved environment variables • bundle-analyzer [build] Webpack/Vite bundle size analysis (requires build) • type-checker [dev, ts] On-demand TypeScript type checking • git-hook-manager [git] Manage pre-commit hooks without husky config

注意:此时package.json依然干净,node_modules中没有新增任何顶层依赖,vite.config.ts一字未动。ponytail 的所有动作,都发生在node_modules/.ponytail/这个私有沙盒内。

3.3 启用css-inspector并验证其注入逻辑

现在,我们启动 Vite dev server,并同时启用css-inspector

# 在一个终端中启动 dev server npm run dev # 在另一个终端中,执行 skill 启用命令(注意:必须在项目根目录) npx ponytail run css-inspector

关键来了:npx ponytail run css-inspector这条命令做了什么?

  • 它首先检测当前目录是否运行着 dev server(通过扫描localhost:5173是否响应 HTTP 200,或检查package.jsonscripts.dev的进程 PID);
  • 确认后,它读取node_modules/.ponytail/skills/css-inspector/manifest.json,获取该 skill 的入口文件路径(index.js)和所需 adapter(vite);
  • 然后,它通过child_process向正在运行的 Vite 进程发送一条 IPC 消息,内容为:“请在你的 dev server 中,动态 require/path/to/node_modules/.ponytail/skills/css-inspector/index.js,并传入{ ponytail: { version: '0.4.2' } }作为参数”;
  • Vite 的 adapter(已预埋在vite-plugin-ponytail中)收到消息后,立即执行require(),并将返回的init()函数注入到 Vite 的 HMR(Hot Module Replacement)上下文中;
  • css-inspectorinit()函数启动一个 WebSocket 服务(端口5174),并在 Vite 的 dev server HTML 中注入一段<script>标签,指向http://localhost:5174/inspector.js
  • 浏览器访问http://localhost:5173,该 script 加载后,自动连接ws://localhost:5174,建立双向通信,CSS 变量面板即刻出现在右下角。

整个过程耗时约 1.2 秒(实测),无任何构建中断、无页面刷新、无控制台报错。你打开浏览器开发者工具,能看到新增的 iframe 和 WebSocket 连接,但Elements面板中找不到任何新增 DOM 节点——因为css-inspector的 UI 是通过document.createElement('div')动态挂载,并设置z-index: 999999,完全独立于你的应用 DOM 树。

实操心得:ponytail 的run命令不是“启动一个服务”,而是“向现有服务注入一个能力”。它不占用新端口(除非 skill 显式声明),不创建新进程(除非 skill 自行 fork),所有交互都通过已有的 dev server 进程完成。这也是它能做到“零配置、零侵入”的根本原因——它不增加系统复杂度,只复用现有资源。

3.4 查看 skill 的 manifest 结构,理解其可扩展性

让我们打开node_modules/.ponytail/skills/css-inspector/manifest.json,看看 ponytail 如何定义一个能力:

{ "name": "css-inspector", "version": "0.2.1", "description": "Real-time CSS custom property explorer", "tags": ["dev", "ui"], "adapter": "vite", "entry": "index.js", "dependencies": ["ws", "css-tree"], "permissions": ["read:css", "write:dom"], "configSchema": { "port": { "type": "number", "default": 5174 }, "include": { "type": "array", "items": { "type": "string" } } } }

这个 manifest 定义了:

  • 适配器(adapter):告诉 ponytail 该 skill 应该注入到哪种 dev server 环境;
  • 入口(entry):skill 的主文件,必须导出一个init(options)函数;
  • 依赖(dependencies):ponytail 会自动npm install这些包到node_modules/.ponytail/skills/css-inspector/node_modules/,与其他 skill 隔离;
  • 权限(permissions):声明该 skill 需要的操作范围,ponytail runtime 会据此限制其 API 访问(如禁止fs.writeFileSync);
  • 配置模式(configSchema):支持npx ponytail run css-inspector --port=8080这样的命令行参数,自动校验并注入。

正是这套标准化的 manifest 协议,让 ponytail 成为一个真正的“能力市场”——任何人都可以 forkdietrichgebert/ponytail,在skills/下新建目录,编写自己的 skill,然后通过npx skill add your-github/repo让团队其他人一键启用。它不依赖中心化仓库,不强制审核流程,靠的是 manifest 的契约精神。

4. 深度解析:ponytail 的 adapter 机制如何实现跨工具链兼容

ponytail 最令人惊讶的能力,是它能在 webpack、vite、rspack 甚至纯 Express dev server 中,以几乎相同的命令启用同一个 skill。比如api-mock-router,你可以在 Vite 项目中npx ponytail run api-mock-router,也可以在 webpack 项目中执行完全相同的命令,效果一致:都会在 dev server 中注入一个/mock/*的路由处理器。

这背后的核心,是 ponytail 的adapter 分层架构。它不是为每个工具写一套 hack 代码,而是抽象出三层通用接口,让 adapter 只需实现这三层,即可接入任意 dev server。

4.1 第一层:Process Discovery(进程发现)

ponytail 首先需要确认“目标 dev server 是否正在运行”,以及“它的进程 ID 是多少”。不同工具的发现策略不同:

工具类型发现方式示例
Vite检查package.jsonscripts.dev是否包含vite,然后执行ps aux | grep 'vite dev' | grep -v grep获取 PIDvite dev --host 0.0.0.0 --port 5173
Webpack Dev Server检查webpack.config.js是否存在,且devServer配置非空;再通过netstat -tuln | grep ':8080'(端口来自配置)定位 PIDwebpack serve --port 8080
Rspack检查rspack.config.js存在,且devServer配置启用;利用 rspack 的--inspect参数暴露的调试端口反向查找rspack serve --port 3000 --inspect=9229
Express检查package.jsonscripts.dev是否包含node+server.js,再通过lsof -i :3000(端口硬编码或从server.js解析)获取 PIDnode server.js

ponytail 的discovery模块会按优先级顺序尝试这些策略,一旦匹配成功,就锁定 PID 并进入下一步。它不依赖工具的 CLI 输出格式(因为不同版本输出可能变化),而是基于操作系统级的进程和网络信息,鲁棒性极强。

4.2 第二层:Runtime Injection(运行时注入)

拿到 PID 后,ponytail 需要“把 skill 代码塞进目标进程”。这里它采用了 Node.js 的process._rawDebug机制(非公开 API,但稳定存在于 Node.js 14+):

  • process._rawDebug是一个隐藏函数,允许外部进程向目标进程发送一条“调试指令”,目标进程会立即执行该指令对应的 JavaScript 代码;
  • ponytail 构造一段安全的require()语句,例如:require('/path/to/skill/index.js').init({ ponytail: { version: '0.4.2' } });
  • 通过child_process.execSync(kill -USR1 ${pid})触发目标进程的SIGUSR1信号;
  • 目标进程(如 Vite)若已预埋process.on('SIGUSR1', () => { /* 执行注入代码 */ }),则立即执行这段 require;
  • 若未预埋(如纯 Express),ponytail 会 fallback 到node -e "require('child_process').execSync('kill -USR1 ${pid}')"方式,或直接通过net模块连接目标进程的调试端口(--inspect)执行脚本。

这种注入方式,绕过了所有构建配置,因为它发生在进程运行时,而非构建时。它不修改任何文件,不重启进程,不中断 HMR,只是“轻轻推了一把”,让目标进程自己去加载新模块。

4.3 第三层:IPC Bridge(进程间通信桥)

skill 被注入后,往往需要与 ponytail CLI 进行双向通信(如接收配置、上报状态、传递错误)。ponytail 使用Unix Domain Socket(UDS)作为 IPC 通道:

  • CLI 启动时,创建一个随机命名的 UDS 文件(如/tmp/ponytail-abc123.sock);
  • 注入的 skill 代码中,通过net.connect('/tmp/ponytail-abc123.sock')连接到该 socket;
  • CLI 与 skill 之间通过 JSON-RPC 协议交换消息,例如:
    // CLI 发送 { "jsonrpc": "2.0", "method": "skill.start", "params": { "name": "css-inspector", "config": { "port": 5174 } }, "id": 1 } // Skill 回复 { "jsonrpc": "2.0", "result": { "status": "running", "endpoint": "http://localhost:5174" }, "id": 1 }

UDS 的优势在于:

  • 零端口冲突:不占用 TCP 端口,避免与 dev server 端口(5173)、skill 自身服务端口(5174)冲突;
  • 进程级隔离:只有同一用户、同一会话下的进程才能访问该 socket 文件,安全性高;
  • 低延迟:比 TCP loopback 快 3~5 倍,适合高频通信(如 HMR 事件转发)。

正是这三层机制的组合,让 ponytail 实现了“一次编写,多处运行”。你写一个 skill,只要它遵循 manifest 协议,就能在任何支持对应 adapter 的工具中启用。这种解耦,远比“为每个工具写一个 plugin”更可持续。

5. 生产级实践:在团队中规模化使用 ponytail 的经验与陷阱

我们在三个不同规模的前端团队(20人、80人、200人)落地 ponytail 已超过 18 个月。它没有成为“下一个 webpack”,但确实成了团队内部最常被提及的“那个不用配置就能用的调试工具”。以下是我们在规模化使用中沉淀的真实经验,包括那些不会写在 README 里的细节。

5.1 团队协作规范:如何避免 skill 版本混乱与冲突

ponytail 的npx skill add默认拉取 GitHub 仓库的main分支,这在个人开发时很爽,但在团队中极易引发问题:A 同学昨天addmain分支的css-inspector,今天 B 同学add了同一仓库但v0.2tag 的版本,两人npx ponytail run时行为不一致。

我们的解决方案是强制使用 Git Tag + 团队内部镜像仓库

  1. 所有 skill 的发布,必须打语义化版本 tag(如css-inspector-v0.2.1);
  2. 团队维护一个私有 npm registry(如 Verdaccio),CI 流水线自动将 tagged skill 同步至此;
  3. 团队文档规定:npx skill add必须指定 tag 或 registry,例如:
    # ✅ 推荐:使用团队 registry 的固定版本 npx skill add @our-team/css-inspector@0.2.1 # ✅ 可接受:使用 GitHub tag(但需在 PR 中注明) npx skill add dietrichgebert/ponytail#css-inspector-v0.2.1 # ❌ 禁止:裸仓库名(main 分支不可控) npx skill add dietrichgebert/ponytail

同时,我们在node_modules/.ponytail/index.json中增加了team-policy字段,ponytail CLI 启动时会检查该字段,若发现未按规范add,则警告并拒绝运行。

实操心得:ponytail 的“零配置”不等于“零治理”。越是轻量的工具,越需要清晰的团队约定。我们曾因未规范版本,导致 QA 环境和开发环境api-mock-router行为不一致,排查了 3 小时才发现是 skill 版本差了一个 patch。

5.2 安全边界:如何防止 skill 滥用系统权限

ponytail 的 isolation 机制虽强,但 skill 仍可通过require('child_process')执行任意 shell 命令。我们曾发现一个第三方git-hook-managerskill,其init()函数中包含execSync('git config --global user.name "ponytail"'),无意中修改了开发者的全局 Git 配置。

为此,我们为 ponytail runtime 增加了细粒度权限沙箱(Fine-grained Permission Sandbox)

  • 在 VM Context 创建时,重写require函数,对敏感模块(child_process,fs,net,dns)进行白名单控制;
  • 每个 skill 的manifest.jsonpermissions字段,决定其可 require 的模块列表;
  • 例如css-inspector"permissions": ["read:css", "write:dom"],runtime 会拦截所有require('fs')调用,并抛出PermissionError: fs module is not allowed for css-inspector
  • 对于必须使用child_process的 skill(如bundle-analyzer),我们要求其 manifest 显式声明"permissions": ["exec:webpack-bundle-analyzer"],runtime 只允许execSync('npx webpack-bundle-analyzer'),禁止其他命令。

这套沙箱已在团队内部强制启用,所有新提交的 skill 必须通过权限扫描 CI 检查,否则无法合并。

5.3 性能监控:如何量化 ponytail 对 dev server 的影响

ponytail 的“无感注入”并非没有成本。我们通过 Chrome DevTools 的 Performance 面板和 Node.js 的--inspect,测量了不同 skill 的注入开销:

Skill 名称注入耗时(ms)内存增量(MB)CPU 占用峰值(%)备注
env-dumper821.23.1仅打印环境变量,无持续运行
css-inspector2178.612.4启动 WebSocket 服务,监听 CSSOM
component-snapshot34515.828.7需 patch React DevTools backend
api-mock-router1424.35.9注入 Express 中间件,无额外服务

关键发现:注入耗时与 skill 的初始化逻辑强相关,而与 dev server 类型弱相关。也就是说,无论你用 vite 还是 webpack,css-inspector的注入时间都在 200~250ms 之间。这证实了 ponytail 的 adapter 层是高效的。

但我们也发现一个陷阱:某些 skill(如bundle-analyzer)在init()中会启动一个长期运行的子进程(npx webpack-bundle-analyzer),该进程会持续占用 CPU 和内存,即使你关闭了浏览器标签页。为此,我们为 ponytail CLI 增加了--auto-kill标志:

npx ponytail run bundle-analyzer --auto-kill

启用后,CLI 会在检测到浏览器断开 WebSocket 连接 30 秒后,自动kill -SIGTERM该子进程。这个 flag 已成为团队所有build类 skill 的默认选项。

5.4 故障排查:当npx ponytail run失败时,如何快速定位

ponytail 的错误信息极其简洁,这是它的设计哲学,但也给排查带来挑战。我们整理了一套标准化排查流程:

  1. 第一步:检查进程发现是否成功
    运行npx ponytail debug --discover,它会输出详细的进程扫描日志,包括尝试过的所有策略、匹配到的 PID、以及最终选择的 adapter。

  2. 第二步:验证 runtime 注入是否生效
    运行npx ponytail debug --inject-test,它会向目标进程发送一条测试指令console.log('[ponytail] inject test passed'),并捕获输出。如果无输出,说明 adapter 未正确预埋或 PID 错误。

  3. 第三步:查看 skill 的 sandbox 日志
    所有 skill 的console.logconsole.error都会被重定向到node_modules/.ponytail/logs/<skill-name>.log。这是最直接的 debug 途径。

  4. 第四步:启用 verbose 模式
    npx ponytail run <skill> --verbose会输出完整的 JSON-RPC 通信日志,包括 CLI 发送的请求和 skill 的响应,便于分析协议层问题。

我们把这些命令封装成ponytail-troubleshoot脚本,放在团队 shared scripts 仓库中,新人入职第一天就会学习这套流程。它比“看报错”高效得多,因为 ponytail 的失败,90% 都发生在进程发现或注入环节,而非 skill 本身。

6. 进阶玩法:如何编写一个属于你团队的定制 skill

ponytail 的最大价值,不在于它自带的 7 个 skill,而在于它为你提供了快速构建团队专属开发能力的脚手架。我们团队内部已沉淀了 12 个私有 skill,覆盖了从设计稿同步、埋点验证、灰度流量标记到自动化截图回归测试等场景。下面我以一个真实案例——design-token-sync(设计 Token 同步工具)为例,手把手教你从零编写一个 production-ready skill。

6.1 需求背景:为什么我们需要这个 skill

我们团队使用 Figma 作为设计源,Figma Plugin 导出的 Token JSON 文件(tokens.json)需要手动复制到前端项目的src/tokens/目录,并运行npm run sync-tokens脚本生成 TypeScript 类型。这个过程平均每天发生 3~5 次,每次耗时 2 分钟,且容易漏同步、错覆盖。

理想方案:当 Figma 中 Token 更新并点击“Publish”后,前端 dev server 自动拉取最新tokens.json,生成类型,触发 HMR 更新,开发者无需任何操作。

6.2 创建 skill 目录结构

在团队私有仓库our-team/ponytail-skillsskills/目录下,新建design-token-sync/

design-token-sync/ ├── manifest.json ├── index.js ├── package.json └── lib/ ├── fetch-tokens.js └── generate-types.js

6.3 编写manifest.json

{ "name": "design-token-sync", "version": "1.0.0", "description": "Auto-sync Figma design tokens to frontend project", "tags": ["dev", "design", "ts"], "adapter": "vite", "entry": "index.js", "dependencies": ["node-fetch", "@types/node"], "permissions": ["read:network", "write:fs"], "configSchema": { "figmaFileId": { "type": "string", "required": true }, "figmaToken": { "type": "string", "required": true }, "outputPath": { "type": "string", "default": "src/tokens/index.ts" } } }

注意permissions中的"write:fs",因为该 skill 需要写入文件。ponytail runtime 会据此放开fs.writeFileSync权限。

6.4 编写index.js—— skill 的核心入口

// index.js const path = require('path'); const { fetchTokens } = require('./lib/fetch-tokens'); const { generateTypes } = require('./lib/generate-types'); module.exports = { init: async (options) => { const { figmaFileId, figmaToken, outputPath } = options.config; // 1. 创建一个 WebSocket 服务,监听 Figma webhook(简化版,实际用 ngrok) const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8081 }); wss.on('connection', async (ws) => { try { // 2. 从 Figma API 拉取最新 tokens const tokens = await fetchTokens(figmaFileId, figmaToken); // 3. 生成 TypeScript 类型文件 const typesContent = generateTypes(tokens); const outputFullPath = path.resolve(process.cwd(), outputPath); // 4. 写入文件(权限已由 runtime 放开) require('fs').writeFileSync(outputFullPath, typesContent); // 5. 触发 Vite HMR(关键!让组件自动更新) const { createServer } = require('vite'); const server = await createServer({ root: process.cwd(), configFile: false, logLevel: 'error' }); await server.watcher.close(); // 避免重复监听 // 6. 通知 Vite 重新加载该文件 server.ws.send({ type: 'full-reload', path: outputPath.replace(/\.ts$/, '.tsx') // 兼容 .tsx }); ws.send(JSON.stringify({ status: 'success', file: outputPath })); } catch (err) { ws.send(JSON.stringify({ status: 'error', message: err.message })); } }); console.log(`[design-token-sync] Listening on ws://localhost:8081`); console.log(`[design-token-sync] Configure Figma webhook to POST to http://localhost:8081`); // 返回 cleanup 函数,供 ponytail 在退出时调用 return () => { wss.close(); console.log(`[design-token-sync] Stopped`); }; } };

6.5 关键技巧:如何让 skill 与 Vite HMR 无缝集成

上面代码中第 5 步server.ws.send(...)是精髓。它不是简单地fs.writeFileSync,而是主动通知 Vite 的 WebSocket 服务:“这个文件变了,请触发 full-reload”。这需要 skill 知道 Vite 的内部 API,但 ponytail 的 adapter 已为我们封装好:

  • Vite adapter 在注入时,会将server实例挂载到全局ponytail.viteServer
  • 因此,skill 中可以直接ponytail.viteServer.ws.send(...),无需自己创建 server;
  • 这种
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 5:38:30

WPF 精美左侧菜单栏实战:从数据绑定、MVVM 到自定义模板

简介&#xff1a;面向WPF桌面应用开发者的左侧菜单栏源码包&#xff0c;聚焦于使用XAML与C#构建美观、可交互的导航界面&#xff0c;适合需要快速实现侧边栏布局或深入学习Menu控件定制的中初级开发者。压缩包内共49个文件&#xff0c;以cs后台逻辑、xaml界面布局、config配置为…

作者头像 李华
网站建设 2026/9/9 5:37:40

技能管理实战:从硬技能到刻意练习,打造可调用的核心竞争力

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 5:37:33

C语言预处理指令全解析:宏定义、头文件与条件编译实战

用C语言写东西&#xff0c;时间长了你会发现一个规律&#xff1a;程序里最隐蔽、最磨人的bug&#xff0c;往往不是算法写错了&#xff0c;也不是指针用飞了&#xff0c;而是栽在与#开头的行上。宏定义、文件包含、条件预处理&#xff0c;这些在编译正式开始之前就被“处理掉”的…

作者头像 李华
网站建设 2026/9/9 5:37:04

语音模块与MCU串口协议设计六要点:从能通到稳通

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 5:36:30

2026年SSH客户端选型:MobaXterm、Termius、Xterminal真实对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 5:36:23

Spring Boot集成RabbitMQ:场景甄别、避坑指南与工程实践

先讲个真实场景。很多团队把 RabbitMQ 引入 Spring Boot 项目&#xff0c;是因为某个活动流量把数据库打满&#xff0c;或是链路太长、一个下游服务抖动就把整条链路拖垮。我接手过一个订单系统&#xff0c;线上半夜报警&#xff0c;慢 SQL 把连接池耗尽&#xff0c;加了两台机…

作者头像 李华