You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Prettier多HTML插件链式调用问题:如何让自定义类名拆分插件在tailwind插件后执行

Prettier多HTML插件链式调用问题:如何让自定义类名拆分插件在tailwind插件后执行

我完全理解你的处境——Prettier的插件链式调用尤其是HTML相关的,官方文档确实写得不够直观,特别是当你需要在特定插件之后执行自定义逻辑的时候。你已经实现了拆分长Tailwind类名的功能,但想把它从单独的后处理脚本整合到Prettier流程中,同时还要保证tailwindcss插件的排序结果不被破坏,又不想fork插件失去更新能力,这个需求非常合理。

核心思路

要实现自定义插件在prettier-plugin-tailwindcss之后执行,关键是利用Prettier的插件钩子执行顺序和AST处理时机:

  1. Tailwind插件会在HTML的AST处理阶段完成类名排序
  2. 我们的自定义插件需要在所有前置插件(包括Tailwind、organize-attributes等)处理完AST后,通过postprocess钩子对最终AST进行修改,只拆分长类名,不破坏其他插件的处理结果

具体实现步骤

1. 调整自定义插件结构,实现postprocess钩子

修改你的自定义插件,让它针对HTML文件的最终AST进行处理,确保只在Tailwind排序后拆分类名:

// ./tools/prettier-split-classes-plugin.js
module.exports = {
  name: 'prettier-plugin-split-tailwind-classes',
  // 只针对HTML文件生效
  overrides: [
    {
      files: '**/*.html',
      options: {},
    },
  ],
  // Postprocess钩子会在所有其他插件的AST处理完成后执行
  postprocess: (ast, options) => {
    // 递归遍历HTML AST,找到所有class属性
    const traverseNode = (node) => {
      // 处理元素节点的class属性
      if (node.type === 'element' && Array.isArray(node.attributes)) {
        node.attributes.forEach(attr => {
          if (attr.name === 'class' && typeof attr.value === 'string' && attr.value.length > 0) {
            // 拆分已被Tailwind排序后的类名数组
            const classList = attr.value.split(' ').filter(cls => cls.trim() !== '');
            if (classList.length <= 1) return; // 短类名无需拆分

            // 自定义拆分逻辑:按每行最大长度拆分(可根据需求调整)
            const maxLineLength = options.printWidth - 10; // 预留缩进空间
            let currentLine = '';
            const newClassLines = [];

            classList.forEach(cls => {
              const proposedLine = currentLine ? `${currentLine} ${cls}` : cls;
              if (proposedLine.length > maxLineLength && currentLine) {
                newClassLines.push(currentLine);
                currentLine = cls;
              } else {
                currentLine = proposedLine;
              }
            });
            if (currentLine) newClassLines.push(currentLine);

            // 更新class属性的value为多行格式
            attr.value = newClassLines.join('\n  ');
          }
        });
      }

      // 递归处理子节点
      if (Array.isArray(node.children)) {
        node.children.forEach(traverseNode);
      }
    };

    traverseNode(ast);
    return ast;
  },
};

2. 调整Prettier配置的插件顺序

在你的Prettier配置中,将自定义插件放在prettier-plugin-tailwindcss的后面——因为Prettier会按插件数组的顺序加载,但postprocess钩子会按加载顺序依次执行,最后加载的插件的钩子会在最末尾运行,正好拿到Tailwind处理后的结果:

{
  "attributeGroups": [
    "$ANGULAR_ELEMENT_REF",
    "$ANGULAR_STRUCTURAL_DIRECTIVE",
    "$ANGULAR_OUTPUT",
    "$ANGULAR_TWO_WAY_BINDING",
    "$ANGULAR_INPUT",
    "$DEFAULT"
  ],
  "attributeSort": "ASC",
  "experimentalOperatorPosition": "start",
  "overrides": [
    {
      "files": "**/*.html",
      "options": {
        "htmlWhitespaceSensitivity": "ignore",
        "parser": "html"
      }
    }
  ],
  "plugins": [
    "prettier-plugin-organize-attributes",
    "prettier-plugin-sort-json",
    "./tools/prettier-stylistic/dist/index.js",
    "prettier-plugin-tailwindcss",
    "./tools/prettier-split-classes-plugin.js" // 自定义插件放在最后
  ],
  "printWidth": 120,
  "singleQuote": true,
  "trailingComma": "es5"
}

3. 验证效果

运行Prettier命令(比如npx prettier --write src/**/*.html),检查结果:

  • Tailwind类名的排序保持不变(由prettier-plugin-tailwindcss处理)
  • 长类名被拆分为多行(由自定义插件处理)
  • HTML属性的排序依然符合prettier-plugin-organize-attributes的规则

关键注意事项

  1. AST结构兼容性:Prettier的HTML AST结构可能会随版本变化,如果你遇到遍历问题,可以临时用console.log(ast)打印节点结构,调整遍历逻辑。
  2. 拆分逻辑自定义:你可以根据团队需求修改拆分规则(比如按Tailwind功能分组拆分,而不是按长度),只要在traverseNode函数中调整classList的处理逻辑即可。
  3. 避免破坏其他插件结果:自定义插件只修改class属性的value,不要触碰节点的其他属性(比如顺序、其他属性值),确保其他插件的处理结果被完整保留。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 13:58:03