mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-05 13:10:24 +00:00
feat: add eslint-plugin-better-tailwindcss for Tailwind v4 linting (#9245)
## Summary Add `eslint-plugin-better-tailwindcss` to the ESLint toolchain for Tailwind CSS v4 class linting. ## Changes - **What**: Integrate `eslint-plugin-better-tailwindcss` (v4.3.1) with the recommended config, pointed at the design-system CSS entry point for v4 theme resolution. Five rules are enabled initially: `enforce-canonical-classes`, `no-deprecated-classes`, `no-conflicting-classes`, `no-duplicate-classes`, `no-unnecessary-whitespace`. Three rules are disabled pending follow-up: `no-unknown-classes` (needs PrimeIcon/custom class whitelisting), `enforce-consistent-line-wrapping` (oxfmt conflict risk), `enforce-consistent-class-order` (large batch change). - **Dependencies**: `eslint-plugin-better-tailwindcss` ^4.3.1 - Fix conflicting `outline outline-1` classes in `FormDropdownMenuActions.vue` (caught by the new `no-conflicting-classes` rule). ## Review Focus - Is the rule severity/enablement strategy appropriate for incremental adoption? - The 700 warnings (mostly `enforce-canonical-classes` and `no-deprecated-classes`) are all auto-fixable via `eslint --fix` — should we batch-fix them in this PR or a follow-up? Fixes COM-15518 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9245-feat-add-eslint-plugin-better-tailwindcss-for-Tailwind-v4-linting-3136d73d365081df8a64dd55962d073f) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
|
||||
import pluginJs from '@eslint/js'
|
||||
import pluginI18n from '@intlify/eslint-plugin-vue-i18n'
|
||||
import betterTailwindcss from 'eslint-plugin-better-tailwindcss'
|
||||
import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript'
|
||||
import { importX } from 'eslint-plugin-import-x'
|
||||
import oxlint from 'eslint-plugin-oxlint'
|
||||
@@ -111,6 +112,28 @@ export default defineConfig([
|
||||
tseslintConfigs.recommended,
|
||||
// Difference in typecheck on CI vs Local
|
||||
pluginVue.configs['flat/recommended'],
|
||||
// Tailwind CSS v4 linting (class ordering, duplicates, conflicts, etc.)
|
||||
betterTailwindcss.configs.recommended,
|
||||
{
|
||||
settings: {
|
||||
'better-tailwindcss': {
|
||||
entryPoint: 'packages/design-system/src/css/style.css'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
// Off: requires whitelisting non-Tailwind classes (PrimeIcons, custom CSS)
|
||||
'better-tailwindcss/no-unknown-classes': 'off',
|
||||
// Off: may conflict with oxfmt formatting
|
||||
'better-tailwindcss/enforce-consistent-line-wrapping': 'off',
|
||||
// Off: large batch change, enable and apply with `eslint --fix`
|
||||
'better-tailwindcss/enforce-consistent-class-order': 'off',
|
||||
// Off: large batch change (v3→v4 renames like rounded→rounded-sm),
|
||||
// enable and apply with `eslint --fix` in a follow-up PR
|
||||
'better-tailwindcss/enforce-canonical-classes': 'off',
|
||||
// Off: large batch change, enable and apply with `eslint --fix`
|
||||
'better-tailwindcss/no-deprecated-classes': 'off'
|
||||
}
|
||||
},
|
||||
// Disables ESLint rules that conflict with formatters
|
||||
eslintConfigPrettier,
|
||||
// @ts-expect-error Type incompatibility between storybook plugin and ESLint config types
|
||||
|
||||
@@ -146,6 +146,7 @@
|
||||
"eslint": "catalog:",
|
||||
"eslint-config-prettier": "catalog:",
|
||||
"eslint-import-resolver-typescript": "catalog:",
|
||||
"eslint-plugin-better-tailwindcss": "catalog:",
|
||||
"eslint-plugin-import-x": "catalog:",
|
||||
"eslint-plugin-oxlint": "catalog:",
|
||||
"eslint-plugin-storybook": "catalog:",
|
||||
|
||||
75
pnpm-lock.yaml
generated
75
pnpm-lock.yaml
generated
@@ -180,6 +180,9 @@ catalogs:
|
||||
eslint-import-resolver-typescript:
|
||||
specifier: ^4.4.4
|
||||
version: 4.4.4
|
||||
eslint-plugin-better-tailwindcss:
|
||||
specifier: ^4.3.1
|
||||
version: 4.3.1
|
||||
eslint-plugin-import-x:
|
||||
specifier: ^4.16.1
|
||||
version: 4.16.1
|
||||
@@ -636,6 +639,9 @@ importers:
|
||||
eslint-import-resolver-typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-better-tailwindcss:
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.1(eslint@9.39.1(jiti@2.6.1))(oxlint@1.49.0(oxlint-tsgolint@0.14.2))(tailwindcss@4.2.0)(typescript@5.9.3)
|
||||
eslint-plugin-import-x:
|
||||
specifier: 'catalog:'
|
||||
version: 4.16.1(@typescript-eslint/utils@8.56.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1))
|
||||
@@ -1916,6 +1922,10 @@ packages:
|
||||
resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@eslint/css-tree@3.6.9':
|
||||
resolution: {integrity: sha512-3D5/OHibNEGk+wKwNwMbz63NMf367EoR4mVNNpxddCHKEb2Nez7z62J2U6YjtErSsZDoY0CsccmoUpdEbkogNA==}
|
||||
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
||||
|
||||
'@eslint/eslintrc@3.3.3':
|
||||
resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -5314,6 +5324,19 @@ packages:
|
||||
eslint-import-resolver-webpack:
|
||||
optional: true
|
||||
|
||||
eslint-plugin-better-tailwindcss@4.3.1:
|
||||
resolution: {integrity: sha512-b6xM31GukKz0WlgMD0tQdY/rLjf/9mWIk8EcA45ngOKJPPQf1C482xZtBlT357jyunQE2mOk4NlPcL4i9Pr85A==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=23.0.0}
|
||||
peerDependencies:
|
||||
eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0
|
||||
oxlint: ^1.35.0
|
||||
tailwindcss: ^3.3.0 || ^4.1.17
|
||||
peerDependenciesMeta:
|
||||
eslint:
|
||||
optional: true
|
||||
oxlint:
|
||||
optional: true
|
||||
|
||||
eslint-plugin-import-x@4.16.1:
|
||||
resolution: {integrity: sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -6558,6 +6581,9 @@ packages:
|
||||
mdn-data@2.12.2:
|
||||
resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
|
||||
|
||||
mdn-data@2.23.0:
|
||||
resolution: {integrity: sha512-786vq1+4079JSeu2XdcDjrhi/Ry7BWtjDl9WtGPWLiIHb2T66GvIVflZTBoSNZ5JqTtJGYEVMuFA/lbQlMOyDQ==}
|
||||
|
||||
mdurl@2.0.0:
|
||||
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
|
||||
|
||||
@@ -7780,6 +7806,10 @@ packages:
|
||||
resolution: {integrity: sha512-2SG1TnJGjMkD4+gblONMGYSrwAzYi+ymOitD+Jb/iMYm57nH20PlkVeMQRah3yDMKEa0QQYUF/QPWpdW7C6zNg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
||||
synckit@0.11.12:
|
||||
resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
||||
table@6.9.0:
|
||||
resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
@@ -7788,6 +7818,10 @@ packages:
|
||||
resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
tailwind-csstree@0.1.4:
|
||||
resolution: {integrity: sha512-FzD187HuFIZEyeR7Xy6sJbJll2d4SybS90satC8SKIuaNRC05CxMvdzN7BUsfDQffcnabckRM5OIcfArjsZ0mg==}
|
||||
engines: {node: '>=18.18'}
|
||||
|
||||
tailwind-merge@2.6.0:
|
||||
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
|
||||
|
||||
@@ -7918,6 +7952,10 @@ packages:
|
||||
ts-map@1.0.3:
|
||||
resolution: {integrity: sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==}
|
||||
|
||||
tsconfig-paths-webpack-plugin@4.2.0:
|
||||
resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
tsconfig-paths@3.15.0:
|
||||
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
|
||||
|
||||
@@ -9708,6 +9746,11 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.15
|
||||
|
||||
'@eslint/css-tree@3.6.9':
|
||||
dependencies:
|
||||
mdn-data: 2.23.0
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@eslint/eslintrc@3.3.3':
|
||||
dependencies:
|
||||
ajv: 6.14.0
|
||||
@@ -13457,6 +13500,23 @@ snapshots:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
eslint-plugin-better-tailwindcss@4.3.1(eslint@9.39.1(jiti@2.6.1))(oxlint@1.49.0(oxlint-tsgolint@0.14.2))(tailwindcss@4.2.0)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@eslint/css-tree': 3.6.9
|
||||
'@valibot/to-json-schema': 1.5.0(valibot@1.2.0(typescript@5.9.3))
|
||||
enhanced-resolve: 5.19.0
|
||||
jiti: 2.6.1
|
||||
synckit: 0.11.12
|
||||
tailwind-csstree: 0.1.4
|
||||
tailwindcss: 4.2.0
|
||||
tsconfig-paths-webpack-plugin: 4.2.0
|
||||
valibot: 1.2.0(typescript@5.9.3)
|
||||
optionalDependencies:
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
oxlint: 1.49.0(oxlint-tsgolint@0.14.2)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.56.0
|
||||
@@ -14893,6 +14953,8 @@ snapshots:
|
||||
|
||||
mdn-data@2.12.2: {}
|
||||
|
||||
mdn-data@2.23.0: {}
|
||||
|
||||
mdurl@2.0.0: {}
|
||||
|
||||
media-encoder-host-broker@8.0.19:
|
||||
@@ -16571,6 +16633,10 @@ snapshots:
|
||||
'@pkgr/core': 0.2.9
|
||||
tslib: 2.8.1
|
||||
|
||||
synckit@0.11.12:
|
||||
dependencies:
|
||||
'@pkgr/core': 0.2.9
|
||||
|
||||
table@6.9.0:
|
||||
dependencies:
|
||||
ajv: 8.18.0
|
||||
@@ -16581,6 +16647,8 @@ snapshots:
|
||||
|
||||
tagged-tag@1.0.0: {}
|
||||
|
||||
tailwind-csstree@0.1.4: {}
|
||||
|
||||
tailwind-merge@2.6.0: {}
|
||||
|
||||
tailwindcss-primeui@0.6.1(tailwindcss@4.2.0):
|
||||
@@ -16691,6 +16759,13 @@ snapshots:
|
||||
|
||||
ts-map@1.0.3: {}
|
||||
|
||||
tsconfig-paths-webpack-plugin@4.2.0:
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
enhanced-resolve: 5.19.0
|
||||
tapable: 2.3.0
|
||||
tsconfig-paths: 4.2.0
|
||||
|
||||
tsconfig-paths@3.15.0:
|
||||
dependencies:
|
||||
'@types/json5': 0.0.29
|
||||
|
||||
@@ -61,6 +61,7 @@ catalog:
|
||||
eslint: ^9.39.1
|
||||
eslint-config-prettier: ^10.1.8
|
||||
eslint-import-resolver-typescript: ^4.4.4
|
||||
eslint-plugin-better-tailwindcss: ^4.3.1
|
||||
eslint-plugin-import-x: ^4.16.1
|
||||
eslint-plugin-oxlint: 1.25.0
|
||||
eslint-plugin-storybook: ^10.2.10
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
layout="vertical"
|
||||
:pt:gutter="
|
||||
cn(
|
||||
'rounded-tl-lg rounded-tr-lg ',
|
||||
'rounded-tl-lg rounded-tr-lg',
|
||||
!(bottomPanelVisible && !focusMode) && 'hidden'
|
||||
)
|
||||
"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
:class="
|
||||
cn(
|
||||
'flex items-center gap-1 rounded-full hover:bg-interface-button-hover-surface justify-center',
|
||||
compact && 'size-full '
|
||||
compact && 'size-full'
|
||||
)
|
||||
"
|
||||
>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
v-if="collapsible"
|
||||
:class="
|
||||
cn(
|
||||
'pi transition-transform duration-200 text-xs text-text-secondary ',
|
||||
'pi transition-transform duration-200 text-xs text-text-secondary',
|
||||
isCollapsed ? 'pi-chevron-right' : 'pi-chevron-down'
|
||||
)
|
||||
"
|
||||
|
||||
@@ -175,7 +175,7 @@ export function useNodeTooltips(nodeType: MaybeRef<string>) {
|
||||
class: cn(
|
||||
context.top && 'border-t-node-component-tooltip-border',
|
||||
context.bottom && 'border-b-node-component-tooltip-border',
|
||||
context.left && 'border-l-node-component-tooltip-border ',
|
||||
context.left && 'border-l-node-component-tooltip-border',
|
||||
context.right && 'border-r-node-component-tooltip-border'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -42,7 +42,7 @@ const baseModelSelected = defineModel<Set<string>>('baseModelSelected', {
|
||||
})
|
||||
|
||||
const actionButtonStyle = cn(
|
||||
'h-8 bg-zinc-500/20 rounded-lg outline outline-1 outline-offset-[-1px] outline-node-component-border transition-all duration-150'
|
||||
'h-8 bg-zinc-500/20 rounded-lg outline-1 -outline-offset-1 outline-node-component-border transition-all duration-150'
|
||||
)
|
||||
|
||||
const layoutSwitchItemStyle =
|
||||
@@ -157,7 +157,7 @@ function toggleBaseModelSelection(item: FilterOption) {
|
||||
cn(
|
||||
'flex flex-col gap-2 p-2 min-w-32',
|
||||
'bg-component-node-background',
|
||||
'rounded-lg outline outline-offset-[-1px] outline-component-node-border'
|
||||
'rounded-lg outline -outline-offset-1 outline-component-node-border'
|
||||
)
|
||||
"
|
||||
>
|
||||
@@ -219,7 +219,7 @@ function toggleBaseModelSelection(item: FilterOption) {
|
||||
cn(
|
||||
'flex flex-col gap-2 p-2 min-w-32',
|
||||
'bg-component-node-background',
|
||||
'rounded-lg outline outline-offset-[-1px] outline-component-node-border'
|
||||
'rounded-lg outline -outline-offset-1 outline-component-node-border'
|
||||
)
|
||||
"
|
||||
>
|
||||
@@ -281,7 +281,7 @@ function toggleBaseModelSelection(item: FilterOption) {
|
||||
cn(
|
||||
'flex flex-col gap-2 p-2 min-w-32',
|
||||
'bg-component-node-background',
|
||||
'rounded-lg outline outline-offset-[-1px] outline-component-node-border'
|
||||
'rounded-lg outline -outline-offset-1 outline-component-node-border'
|
||||
)
|
||||
"
|
||||
>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cn } from '@comfyorg/tailwind-utils'
|
||||
|
||||
const sharedButtonClasses = cn(
|
||||
'inline-flex items-center justify-center border-0 bg-transparent text-inherit transition-colors duration-150 ease-in-out ',
|
||||
'inline-flex items-center justify-center border-0 bg-transparent text-inherit transition-colors duration-150 ease-in-out',
|
||||
'hover:bg-node-component-surface-hovered active:bg-node-component-surface-selected',
|
||||
'disabled:bg-node-component-disabled disabled:text-node-icon-disabled disabled:cursor-not-allowed'
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user