feat: migrate to Tailwind CSS v4 (#7614)

* chore: update deps

* feat: use jsonc/x language

* chore: update eslint 10.0

* fix: no-useless-assignment

* feat: add CLAUDE.md

* chore: ignore

* feat: claude

* fix: lint

* chore: suppot eslint v10

* fix: lint

* fix: lint

* fix: type check

* fix: unit test

* fix: Suggested fix

* fix: unit test

* chore: update stylelint v17

* chore: update all major deps

* fix:  echarts console warn

* chore: update vitest v4

* feat: add skills ignores

* chore: update deps

* chore: update deps

* fix: cspell

* chore: update deps

* chore: update tailwindcss v4

* chore: remove postcss config

* fix: no use catalog

* chore: tailwind v4 config

* fix: tailwindcss v4 sort

* feat: use eslint-plugin-better-tailwindcss

* fix: Interference between enforce-consistent-line-wrapping, jsx-curly-brace-presence and Prettier

* fix: Interference between enforce-consistent-line-wrapping, jsx-curly-brace-presence and Prettier

* fix(lint): resolve prettier and better-tailwindcss formatting conflicts

* fix(tailwind): update theme references and lint sources

* style(format): normalize apps docs and playground vue files

* style(format): normalize core ui-kit components

* style(format): normalize effects ui and layout components
This commit is contained in:
xingyu
2026-03-10 05:08:45 +08:00
committed by GitHub
parent aa7d8630b5
commit a4736a49f8
289 changed files with 5286 additions and 6331 deletions

View File

@@ -32,12 +32,13 @@
"eslint-plugin-import-x": "catalog:"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "catalog:",
"@eslint/js": "catalog:",
"@types/eslint": "catalog:",
"@typescript-eslint/eslint-plugin": "catalog:",
"@typescript-eslint/parser": "catalog:",
"@vitest/eslint-plugin": "catalog:",
"eslint": "catalog:",
"eslint-plugin-eslint-comments": "catalog:",
"eslint-plugin-better-tailwindcss": "catalog:",
"eslint-plugin-jsdoc": "catalog:",
"eslint-plugin-jsonc": "catalog:",
"eslint-plugin-n": "catalog:",
@@ -48,11 +49,9 @@
"eslint-plugin-regexp": "catalog:",
"eslint-plugin-unicorn": "catalog:",
"eslint-plugin-unused-imports": "catalog:",
"eslint-plugin-vitest": "catalog:",
"eslint-plugin-vue": "catalog:",
"eslint-plugin-yml": "catalog:",
"globals": "catalog:",
"jsonc-eslint-parser": "catalog:",
"vue-eslint-parser": "catalog:",
"yaml-eslint-parser": "catalog:"
}

View File

@@ -4,8 +4,7 @@ import { interopDefault } from '../util';
export async function comments(): Promise<Linter.Config[]> {
const [pluginComments] = await Promise.all([
// @ts-expect-error - no types
interopDefault(import('eslint-plugin-eslint-comments')),
interopDefault(import('@eslint-community/eslint-plugin-eslint-comments')),
] as const);
return [

View File

@@ -48,6 +48,12 @@ export async function ignores(): Promise<Linter.Config[]> {
'**/*.woff',
'**/.github',
'**/lefthook.yml',
'**/.agent/**',
'**/.agents/**',
'**/.codex/**',
'**/.claude/**',
'**/.cursor/**',
],
},
];

View File

@@ -11,6 +11,7 @@ export * from './perfectionist';
export * from './pnpm';
export * from './prettier';
export * from './regexp';
export * from './tailwindcss';
export * from './test';
export * from './turbo';
export * from './typescript';

View File

@@ -3,17 +3,12 @@ import type { Linter } from 'eslint';
import { interopDefault } from '../util';
export async function jsonc(): Promise<Linter.Config[]> {
const [pluginJsonc, parserJsonc] = await Promise.all([
interopDefault(import('eslint-plugin-jsonc')),
interopDefault(import('jsonc-eslint-parser')),
] as const);
const pluginJsonc = await interopDefault(import('eslint-plugin-jsonc'));
return [
{
files: ['**/*.json', '**/*.json5', '**/*.jsonc', '*.code-workspace'],
languageOptions: {
parser: parserJsonc as any,
},
language: 'jsonc/x',
plugins: {
jsonc: pluginJsonc as any,
},

View File

@@ -23,7 +23,6 @@ export async function node(): Promise<Linter.Config[]> {
'vitest',
'vite',
'@vue/test-utils',
'@vben/tailwind-config',
'@playwright/test',
],
},

View File

@@ -21,41 +21,58 @@ export async function perfectionist(): Promise<Linter.Config[]> {
'perfectionist/sort-imports': [
'error',
{
customGroups: {
type: {
'vben-core-type': ['^@vben-core/.+'],
'vben-type': ['^@vben/.+'],
'vue-type': ['^vue$', '^vue-.+', '^@vue/.+'],
customGroups: [
{
selector: 'type',
groupName: 'vben-core-type',
elementNamePattern: '^@vben-core/.+',
},
value: {
vben: ['^@vben/.+'],
'vben-core': ['^@vben-core/.+'],
vue: ['^vue$', '^vue-.+', '^@vue/.+'],
{
selector: 'type',
groupName: 'vben-type',
elementNamePattern: '^@vben/.+',
},
},
{
selector: 'type',
groupName: 'vue-type',
elementNamePattern: ['^vue$', '^vue-.+', '^@vue/.+'],
},
{
groupName: 'vben',
elementNamePattern: '^@vben/.+',
},
{
groupName: 'vben-core',
elementNamePattern: '^@vben-core/.+',
},
{
groupName: 'vue',
elementNamePattern: ['^vue$', '^vue-.+', '^@vue/.+'],
},
],
environment: 'node',
groups: [
['external-type', 'builtin-type', 'type'],
['type-external', 'type-builtin', 'type-import'],
'vue-type',
'vben-type',
'vben-core-type',
['parent-type', 'sibling-type', 'index-type'],
['internal-type'],
'builtin',
['type-parent', 'type-sibling', 'type-index'],
['type-internal'],
'value-builtin',
'vue',
'vben',
'vben-core',
'external',
'internal',
['parent', 'sibling', 'index'],
'value-external',
'value-internal',
['value-parent', 'value-sibling', 'value-index'],
'side-effect',
'side-effect-style',
'style',
'object',
'ts-equals-import',
'unknown',
],
internalPattern: ['^#/.+'],
newlinesBetween: 'always',
newlinesBetween: 1,
order: 'asc',
type: 'natural',
},

View File

@@ -3,18 +3,15 @@ import type { Linter } from 'eslint';
import { interopDefault } from '../util';
export async function pnpm(): Promise<Linter.Config[]> {
const [pluginPnpm, parserPnpm, parserJsonc] = await Promise.all([
const [pluginPnpm, parserPnpm] = await Promise.all([
interopDefault(import('eslint-plugin-pnpm')),
interopDefault(import('yaml-eslint-parser')),
interopDefault(import('jsonc-eslint-parser')),
] as const);
return [
{
files: ['package.json', '**/package.json'],
languageOptions: {
parser: parserJsonc,
},
language: 'jsonc/x',
plugins: {
pnpm: pluginPnpm,
},

View File

@@ -0,0 +1,49 @@
import type { Linter } from 'eslint';
import { getDefaultSelectors } from 'eslint-plugin-better-tailwindcss/defaults';
import { SelectorKind } from 'eslint-plugin-better-tailwindcss/types';
import { interopDefault } from '../util';
export async function tailwindcss(): Promise<Linter.Config[]> {
const [pluginBetterTailwindcss] = await Promise.all([
interopDefault(import('eslint-plugin-better-tailwindcss')),
] as const);
return [
{
plugins: {
'better-tailwindcss': pluginBetterTailwindcss,
},
// shadcn-ui 内部组件是自动生成的,不做太多限制
ignores: ['packages/@core/ui-kit/shadcn-ui/**/**'],
settings: {
'better-tailwindcss': {
entryPoint: 'packages/@core/base/design/src/css/global.css',
selectors: [
...getDefaultSelectors(), // preserve default selectors
{
kind: SelectorKind.Attribute,
match: [{ type: 'objectValues' }],
name: '^classNames$',
},
],
},
},
rules: {
...pluginBetterTailwindcss.configs.recommended.rules,
'better-tailwindcss/enforce-consistent-class-order': [
'error',
{
detectComponentClasses: true,
unknownClassOrder: 'asc',
unknownClassPosition: 'start',
},
],
// Let Prettier own wrapping decisions to avoid ping-pong formatting.
'better-tailwindcss/enforce-consistent-line-wrapping': 'off',
'better-tailwindcss/no-unknown-classes': 'off',
},
},
];
}

View File

@@ -4,7 +4,7 @@ import { interopDefault } from '../util';
export async function test(): Promise<Linter.Config[]> {
const [pluginTest, pluginNoOnlyTests] = await Promise.all([
interopDefault(import('eslint-plugin-vitest')),
interopDefault(import('@vitest/eslint-plugin')),
// @ts-expect-error - no types
interopDefault(import('eslint-plugin-no-only-tests')),
] as const);

View File

@@ -12,7 +12,7 @@ export async function yaml(): Promise<Linter.Config[]> {
{
files: ['**/*.y?(a)ml'],
plugins: {
yaml: pluginYaml as any,
yaml: pluginYaml,
},
languageOptions: {
parser: parserYaml,

View File

@@ -1,10 +1,6 @@
import type { Linter } from 'eslint';
const restrictedImportIgnores = [
'**/vite.config.mts',
'**/tailwind.config.mjs',
'**/postcss.config.mjs',
];
const restrictedImportIgnores = ['**/vite.config.mts'];
const customConfig: Linter.Config[] = [
// shadcn-ui 内部组件是自动生成的,不做太多限制

View File

@@ -14,6 +14,7 @@ import {
pnpm,
prettier,
regexp,
tailwindcss,
test,
turbo,
typescript,
@@ -45,6 +46,7 @@ async function defineConfig(config: FlatConfig[] = []) {
perfectionist(),
comments(),
jsdoc(),
tailwindcss(),
unicorn(),
test(),
regexp(),

View File

@@ -9,7 +9,6 @@ export default {
},
},
],
plugins: ['prettier-plugin-tailwindcss'],
printWidth: 80,
proseWrap: 'never',
semi: true,

View File

@@ -22,7 +22,6 @@
}
},
"dependencies": {
"prettier": "catalog:",
"prettier-plugin-tailwindcss": "catalog:"
"prettier": "catalog:"
}
}

View File

@@ -67,6 +67,12 @@ export default {
'use',
'forward',
'return',
'reference',
'plugin',
'source',
'theme',
'utility',
'custom-variant',
],
},
],
@@ -130,12 +136,18 @@ export default {
'use',
'forward',
'return',
'reference',
'plugin',
'source',
'theme',
'utility',
'custom-variant',
],
},
],
'scss/operator-no-newline-after': null,
'selector-class-pattern':
'^(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:[.+])?$',
'^-?(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:[.+])?$',
'selector-not-notation': null,
},