前段时间重构博客后台的时候我把整个页面的样式全切成了 Tailwind CSS。刚开始那叫一个爽写个按钮都不用想类名直接px-4 py-2 rounded往上堆。可等页面越来越多问题就来了同一个 tab 导航有人写flex items-center justify-between px-4 py-2有人写px-4 py-2 flex items-center justify-between还有人把 hover 状态放在中间搞得代码 Review 的时候大部分时间都在争论“顺序到底该按什么规范”。直到我找到ponytail这个插件它本质上就是一个针对 Tailwind CSS 的类名整理工具能把散落在 class 里的 utility class 按固定规则排序、分组、去重像把散乱的头发扎成一个马尾辫一样规整。这篇文章我就把从安装、配置到日常使用的完整经验写出来适合正在被 Tailwind 类名顺序折磨的前端开发者参考。1. 为什么需要 ponytail类名失控的痛点与设计思路1.1 类名一多就失控的真实场景我们先还原一个我每天都在犯错的场景。一个卡片组件大概长这样div classbg-white hover:bg-gray-50 shadow rounded-lg p-6 mt-4 flex flex-col md:flex-row items-center justify-between max-w-2xl mx-auto你能一眼看出这个容器的大致结构吗很难。bg-white和hover:bg-gray-50属于背景色应该放一起shadow和rounded-lg是盒模型外观p-6 mt-4是间距flex flex-col md:flex-row是布局items-center justify-between是弹性布局的对齐方式max-w-2xl mx-auto是尺寸和水平居中。如果每次都靠人工理清这十几个类名的优先级迟早会出错而且在新同事加入项目后类名排序的“隐性规则”根本传递不下去。这个问题不只是美观问题。类名顺序不一致会导致 Git diff 变得极其难看你只是改了一个颜色结果整行 class 属性都被调整了位置Code Review 的人必须逐个字符去对比才能发现真正的变更。时间长了大家对代码整洁度就会失去信心。1.2 人工排序 vs 插件强制规范有人可能会说“我们约定一个顺序不就行了”问题是约定是软性的只要有人不遵守就会慢慢变成“新的约定”。在我过往的项目里Try to enforce 类名顺序的方式无非几种代码 Review 时口头建议、用 ESLint 规则提醒、或者干脆使用 Prettier 插件。但这些方案要么依赖个人自觉要么只能提示不能自动修复要么对动态拼接的类名判断力很弱。ponytail 的思路则是完全不同的它在构建流程里介入把class属性当作一个整体解析出内部的 Tailwind utility class然后根据一套可配置的优先组顺序重新排列。这不只是代码风格层面的“偏好”而是把“类名顺序”变成一种可以被机器执行、自动修复的工程标准。一旦接入你不需要再去背“先写布局再写间距还是先写间距再写布局”因为每次保存代码时插件已经帮你把顺序固定了。1.3 ponytail 的核心设计思路ponytail 这个名字本身就有点意思它是在“Tailwind”的 “tail” 上做文章给你项目里的“尾巴”扎个马尾。它的工作流程可以拆成三步扫描读取项目里所有模板文件HTML、Vue、JSX、TSX、astro 等通过正则和 AST 解析找出所有class或className属性分类将每个 class 值按 Tailwind 官方类别拆分比如flex属于 displaypx-4属于 paddingmd:flex-row属于 responsivehover:bg-gray-50属于 pseudo-class重排按预设的组顺序输出新的 class 值同时把重复项合并最后写回原文件。这种设计最大的好处是规则透明。你不需要让插件理解每个类的具体样式含义它只需要知道类名属于哪一组、组的优先级是多少就能在毫秒级完成排序。因为 Tailwind 的类名命名高度规范所以基于前缀和模式来分类是可靠的不需要跑一次浏览器来获取实际计算样式这也让 ponytail 的运行速度非常快。2. 安装与环境准备工作2.1 环境要求Node 版本与 Tailwind 依赖在动手之前你最好先确认自己的项目环境。ponytail 是一个基于 Node.js 和 PostCSS 生态开发的插件所以它需要Node.js 16 及以上版本如果你用的还是 Node 14建议先升级否则某些新语法会直接报错项目中已经安装并配置了 Tailwind CSS正式版 v3 或 v4v4 的使用方式稍微不同但这个插件兼容得很好期望被整理的文件类型需要能被 PostCSS 的解析器识别。纯 HTML 文件其实不经过 PostCSS所以 ponytail 在实际使用时会有两种模式一种是作为 PostCSS 插件直接接在 Tailwind 的构建链里另一种是作为独立的 CLI 工具扫描模板文件。安装时可以一起装上。如果你是在 Vite React 项目里使用那么postcss.config.js和tailwind.config.js应该已经存在了如果是 Next.js 项目也同样支持。老项目用 webpack 的也不太担心只要是 PostCSS 链上的都能接入。2.2 用 npm 安装 ponytail 及配套包我的建议是先把 ponytail 作为开发依赖安装同时安装它需要的插件依赖。打开项目根目录执行npm install -D ponytail postcss postcss-cli如果你打算用命令行模式快速整理整个项目那就需要postcss-cli。装完以后你可以顺手检查一下package.json确认 devDependencies 里出现了这三个包。如果你用的是 pnpm 或 yarn对应命令换成pnpm add -D或yarn add -D即可。这里有一个细节如果你在项目里已经单独配置了 Tailwind 的 PostCSS 插件那么 ponytail 建议放在 Tailwind 插件的后面。因为 ponytail 处理的是已经生成的类名而不是生成类名本身它需要在 Tailwind 处理完tailwind utilities之后再对模板文件做文本层面的整理顺序反了可能会扫描到的是压缩混淆后的代码。2.3 验证安装是否成功装完先别急着配置我们可以先跑一个小命令验证插件是否正常识别。在项目根目录创建一个临时测试文件test.html里面随便写一段样式div classp-4 mt-2 flex shadow-lg rounded bg-white hover:bg-gray-100/div然后在终端执行npx ponytail test.html如果安装成功你会发现test.html里的 class 顺序变成了一个相对统一的结构比如div classflex rounded bg-white p-4 mt-2 shadow-lg hover:bg-gray-100/div不同版本的默认分组顺序可能略有差异但关键点是文件被修改了而且没有破坏类名本身。如果这一步就报错先检查 Node 版本和 PostCSS 版本最常见的坑是 Node 版本太低导致 AST 解析失败。3. 配置文件与自定义规则3.1 在 postcss.config.js 里接入 ponytail大部分 Tailwind 项目都会有一个postcss.config.js我们就在这里接入 ponytail。我的配置长这样module.exports { plugins: { tailwindcss: {}, autoprefixer: {}, ponytail: { // 扫描范围 include: [src/**/*.{html,jsx,tsx,vue,astro}], // 忽略目录 exclude: [dist, node_modules, coverage], // 类名分组顺序可定制 groups: { layout: [flex, grid, block, inline-block, table, hidden], position: [relative, absolute, fixed, sticky, inset, top, right, bottom, left, z], display: [p, m, space, gap, w, h, max-w, min-w, max-h, min-h], // 注意这里只是示意实际默认规则会更细 }, }, }, };其实如果你不做任何自定义ponytail 也会自带一套非常合理的默认分组规则。官方默认的顺序是按照“布局 → 盒模型 → 间距 → 尺寸 → 背景 → 边框 → 文本 → 交互状态 → 其他”来排列基本上符合我们从外到内的阅读习惯。我强烈建议你先用默认规则跑一段时间体验一下再根据团队习惯微调。3.2 自定义分组顺序与优先级为什么分组顺序那么重要因为 Tailwind 的类名是有层级关系的比如flex决定了子元素的排列方式如果被排在p-4后面视觉上并不会变错但人在读代码时就要在多行之间来回跳。我们团队后来约定的核心原则是先结构后外观再状态。具体来说第一优先级布局类display、flex、grid、position第二优先级盒子模型width、height、padding、margin、border第三优先级视觉类background、color、shadow、rounded、font第四优先级交互与状态类hover、focus、active、disabled、dark。这个顺序的好处是符合 CSS 的书写直觉先想好这个元素是什么布局再想它多大、间距多少最后才关心它长什么样、鼠标移上去有什么变化。如果你的团队已经习惯另一种顺序比如“颜色放前面方便做主题切换”那也可以直接修改配置里的 group 顺序。ponytail 的配置基于数组数组项的先后顺序就是最终输出的顺序。你还可以细分前缀比如把flex和grid都归入 layout 组但让flex排在grid前面。组内部还有二级排序默认是字母序但可以通过sort: alpha | custom调整。3.3 忽略文件夹和单行禁用在大型项目里并不是所有文件都希望被 ponytail 处理。比如你的dist目录、node_modules肯定要忽略否则构建产物和依赖包会被改得面目全非。配置里用 exclude 数组就行ponytail: { exclude: [dist/**, .next/**, out/**], }另外还有一种场景某些第三方组件的模板里用了很诡异的类名拼接比如class{condition ? p-4 : p-2} ${extraClass}这种动态拼接的类名有时候会被插件误伤。为了避免这种情况你可以在那一行加一个注释标记让 ponytail 跳过。具体语法通常是!-- ponytail:off --如果是在 JSX 里则用// ponytail:off。不同版本语法略有不同我建议查看一下你安装版本的 README。实测下来单行禁用非常有必要因为你不可能为了满足插件去改第三方库作者的代码习惯。4. 实操在项目里把 ponytail 跑起来4.1 一键整理整个项目的类名配置好之后最直接的使用方式就是通过命令行对整个项目跑一次。我会在package.json里加一个 script{ scripts: { pony:format: ponytail \src/**/*.{html,jsx,tsx,vue,astro}\ --write } }然后执行npm run pony:format首次运行会有一种“看着它扫描几百个文件,逐个重写 class 顺序”的快感。我的建议是第一次先在分支上跑,然后立刻打开 Git diff 检查。你可能会发现两个问题一是有些文件里的类名顺序和你平时习惯不一样二是某些类名因为插件识别问题被合并或拆分。这些都是正常现象你可以根据自己的需要修改 include 或 exclude。我习惯把这条命令加入lint流程里比如在 CI 上跑ponytail --check模式它只会检查顺序不对的文件并输出文件名不会直接修改文件。这样能阻止那些没有格式化过的代码合并进主分支。4.2 接入编辑器保存时自动整理命令行适合批量处理但日常开发最舒服的还是在保存文件时自动触发。ponytail 官方提供了 VS Code 扩展安装后会在语言服务器里绑定保存事件。你只需要在设置里开启{ ponytail.enableOnSave: true, ponytail.include: [src/**/*.{html,jsx,tsx,vue,astro}] }保存的时候扩展会自动执行和命令行一样的格式化但只处理当前文件。这个体验和 Prettier 的 format on save 很像不会有明显的卡顿感。如果你不用 VS Code而是用 Neovim 或 JetBrains 系 IDE也可以在保存时挂钩子调用 CLI 命令只是稍微麻烦一点。我目前是 VS Code 加终端的组合保存后偶尔会看一眼改动基本没有再手动调整过类名顺序。4.3 与 Tailwind 的 class 自动补全插件配合很多前端工程师都装了 Tailwind CSS IntelliSense 扩展来提示类名这和你用 ponytail 并不冲突。IntelliSense 负责输入时的补全和排序建议ponytail 负责保存时的强制整理配合起来非常顺手。不过有个小坑IntelliSense 默认会按照自己的算法给类名排序而你不小心按了 Tab 接受它的顺序保存后 ponytail 又会把你改成另一套顺序。对于这种情况我建议直接在设置里把 IntelliSense 的排序规则也改成和 ponytail 的 group 一致或者干脆把它的tailwindCSS.experimental.classRegex关闭只保留补全功能把排序交给 ponytail。4.4 用 pony lint 检查类名顺序如果你想在代码审查前就发现问题ponytail 还提供了轻量级的 lint 命令。用法很简单npx ponytail lint src/**/*.tsx --quiet它只会列出包含乱序类名的文件和具体行号不会修改文件。这在 CI 里特别有用可以配合--max-warnings 0强制不合格代码不能通过构建。我们团队在接入 ponytail 之后的第三周就把lint命令加到了 CI 里从那以后类名顺序就再也没有被当做过 Review 话题。5. 常见问题与排查技巧5.1 插件没生效文件没有被修改这是接入时遇到最多的问题。如果你执行ponytail之后文件没有任何变化先按这几个顺序排查检查include配置是否覆盖了目标文件路径注意 glob 通配符的写法src/**/*.tsx不会匹配src/index.tsx之外的嵌套路径建议用src/**/*.{tsx,jsx,vue,html}确认是否被exclude排除了比如你习惯把组件放在src/components下但 exclude 里写了src/components的话就会跳过看看文件里是不是用了class:这种 Vue 语法或者 CSS module 的style{styles.foo}这类属性默认不处理需要在配置里加attributes: [class, className]扩展范围。还有一个容易忽略的问题如果你的项目是通过 CDN 引入 Tailwind 而不是用 PostCSS 构建的ponytail 依然可以作为独立 CLI 工具运行。但如果你的 Tailwind 版本太老比如 v1类名的前缀规则差异很大插件解析时可能识别不全导致结果不可预期建议先升级 Tailwind 到 v3/v4。5.2 动态拼接的类名被意外拆分这类问题多出现在 React 中比如div className{p-4 ${condition ? bg-red-500 : bg-blue-500} flex}ponytail 默认会尝试把模板字符串里的内容也当作 class 列表处理但它毕竟不是 JS 解释器无法知道这个变量的最终值。结果就是它可能把静态部分p-4和flex排列好但保留了变量部分的位置最后顺序变得不协调。我的解决办法是把动态条件部分单独抽到一个变量里或者用 ponytail 的单行禁用注释。不过在大多数情况下你写的动态拼接类名都只是少数几个状态类完全可以用clsx或tailwind-merge来管理让 ponytail 只处理静态字符串这样冲突更少。5.3 与 Prettier 冲突怎么办Prettier 的printWidth如果比较小会在 class 属性很长时把属性折成多行。ponytail 默认是保留原有换行的但如果在排序后仍然超过 Prettier 的行宽限制两者会产生“打架”的现象Prettier 换行ponytail 排序下次保存又换行来回改。我的建议是在项目里约定先 p 后 p具体顺序可以这样先用 Prettier 格式化 JS/JSX 结构再让 ponytail 做类名排序最后再跑一次 Prettier 确保换行位置正确。我一般会把这两个步骤串在一个 Hook 里pretty-quick --staged ponytail --write如果 Prettier 和 ponytail 真的因为个别文件发生循环冲突最省心的方式是在.prettierignore里忽略这些文件或者给 ponytail 配置singleLine: false让它保持多行 class 属性的原样只调整顺序减少换行冲突的概率。5.4 类名去重后发现样式异常ponytail 默认会去掉重复类名比如classp-4 p-4会变成classp-4这在大多数情况下是安全的。但有一种情况可能会出问题如果你用了dark p-4 dark:bg-red-500这种写法去重后正常是dark p-4 dark:bg-red-500不会误删。但如果某个类名既有前缀又是另一个类名的前缀比如bg-red-500和bg-red-500/50这两个其实是不同类插件也会区分。我在一个老项目里遇到过的问题是有人在 Tailwind 里又启用了自定义插件生成了形如btn btn-primary这样的语义类名而 ponytail 默认不会识别这类非 utility class结果把btn和btn-primary当成普通文本保留在原位置。这不影响样式但可能会导致排序不彻底。解决方法是在配置里把自定义类名也归入某一个 group比如customGroup: [btn, btn-primary, icon-]这样它们就能被统一放到末尾或指定分组了。5.5 大型项目性能卡顿如果你的项目文件特别多ponytail 在保存时做全量扫描会明显卡顿。我遇到过大几千行的.astro文件保存时几乎卡了三秒。解决办法有两个在 VS Code 设置里缩小include范围比如只处理src/components、src/pages不过度扫描使用增量缓存模式。当前版本支持在项目根目录生成.ponytail-cache文件第二次运行同一目录时只处理有改动的文件。如果你在 CI 上跑全量 lint也可以先把缓存文件提交到仓库避免每次构建都全量跑一遍。6. 使用心得与后续扩展说实话我一开始只把 ponytail 当个玩具觉得类名排序不至于需要专门一个插件。但真正接入后最明显的变化不是代码变好看了而是团队里的“隐性知识”变少了。以前新人会问这个按钮的类名为什么这样排现在没人问了因为所有人保存代码后出来的顺序都是一样的。有几个小心得可以分享第一个建议是引入 ponytail 一定要和 prettier、husky 一起做。只靠编辑器保存还不够总有人喜欢直接命令行提交不带格式化。我们后来在lint-staged里加了ponytail --write现在所有 commit 之前都会自动格式化彻底杜绝了不合规内容进入仓库。第二个建议是把一个规范的可配置ponytail.config.js放进团队模板里。我自己维护了一个基础配置里面固定了 layout、spacing、visual、state 的顺序方便新项目直接复用。如果你用的 UI 组件库有自己的设计系统比如某些组件要求relative必须跟在overflow-hidden后面可以在每行注释里写清楚这些例外效果比口头通知靠谱得多。第三个建议是先拿一个低频改动的目录跑通流程再逐步扩大到全项目。我第一次直接对整个 monorepo 跑ponytail --write结果改了三千多个文件导致一个几十人的团队短时间内出现了大量合并冲突。后来改成“按模块分批接入”就平滑多了。如果你项目中使用了多个 UI 组件库或者团队正在从纯 CSS 过渡到 Tailwindponytail 也可以作为统一类名书写规范的底子。后续还可以考虑把它集成到一个全局的代码生成器里比如根据组件属性拼装好的 class 字符串在生成阶段直接就调用 ponytail连保存格式化都省了。至少在我目前的项目里ponytail 已经成了不可或缺的一部分。我不用再为 class 的先后顺序和同事讨论半天也不用担心重构时某个类名被重复写了两遍。这样的小工具看起来不起眼但长期省下来的时间比我写这篇文章投入的精力多得多。
阅读完成 · 觉得有帮助?