Migrate legacy classes to the v4 alternative (#14643)

This PR adds a mapping from legacy classes to new classes. For example,
the `flex-shrink-0` is still used in our projects, but is deprecated in
v3.

The migration does a tiny bit of parsing because we can't rely on
`designSystem.parseCandidate(…)` because this requires the utility to be
defined which is not the case for legacy classes.

This migration runs _after_ the migration where we handle prefixes, so
we don't have to worry about that. We do have to worry about the `!`
location, because the `important` migration also relies on the
`designSystem`.

| Old                 | New                    |
| ------------------- | ---------------------- |
| `overflow-clip`     | `text-clip`            |
| `overflow-ellipsis` | `text-ellipsis`        |
| `flex-grow-0`       | `grow-0`               |
| `flex-shrink-0`     | `shrink-0`             |
| `decoration-clone`  | `box-decoration-clone` |
| `decoration-slice`  | `box-decoration-slice` |
This commit is contained in:
Robin Malfait 2024-10-11 14:22:55 +02:00 committed by GitHub
parent f0b65e360c
commit bd3d6bc09b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 68 additions and 1 deletions

View File

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support `keyframes` in JS config file themes ([#14594](https://github.com/tailwindlabs/tailwindcss/pull/14594))
- _Upgrade (experimental)_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612))
- _Upgrade (experimental)_: The upgrade tool now automatically discovers your JavaScript config ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597))
- _Upgrade (experimental)_: Migrate legacy classes to the v4 alternative ([#14643](https://github.com/tailwindlabs/tailwindcss/pull/14643))
### Fixed

View File

@ -5,9 +5,10 @@ import type { DesignSystem } from '../../../tailwindcss/src/design-system'
export async function extractRawCandidates(
content: string,
extension: string = 'html',
): Promise<{ rawCandidate: string; start: number; end: number }[]> {
let scanner = new Scanner({})
let result = scanner.getCandidatesWithPositions({ content, extension: 'html' })
let result = scanner.getCandidatesWithPositions({ content, extension })
let candidates: { rawCandidate: string; start: number; end: number }[] = []
for (let { candidate: rawCandidate, position: start } of result) {

View File

@ -0,0 +1,22 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
import { expect, test } from 'vitest'
import { simpleLegacyClasses } from './simple-legacy-classes'
test.each([
['overflow-clip', 'text-clip'],
['overflow-ellipsis', 'text-ellipsis'],
['flex-grow-0', 'grow-0'],
['flex-shrink-0', 'shrink-0'],
['decoration-clone', 'box-decoration-clone'],
['decoration-slice', 'box-decoration-slice'],
['max-lg:hover:decoration-slice', 'max-lg:hover:box-decoration-slice'],
['max-lg:hover:decoration-slice!', 'max-lg:hover:box-decoration-slice!'],
['max-lg:hover:!decoration-slice', 'max-lg:hover:box-decoration-slice!'],
])('%s => %s', async (candidate, result) => {
let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', {
base: __dirname,
})
expect(simpleLegacyClasses(designSystem, {}, candidate)).toEqual(result)
})

View File

@ -0,0 +1,41 @@
import type { Config } from 'tailwindcss'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { printCandidate } from '../candidates'
// Classes that used to exist in Tailwind CSS v3, but do not exist in Tailwind
// CSS v4 anymore.
const LEGACY_CLASS_MAP = {
'overflow-clip': 'text-clip',
'overflow-ellipsis': 'text-ellipsis',
'flex-grow-0': 'grow-0',
'flex-shrink-0': 'shrink-0',
'decoration-clone': 'box-decoration-clone',
'decoration-slice': 'box-decoration-slice',
}
const SEEDED = new WeakSet<DesignSystem>()
export function simpleLegacyClasses(
designSystem: DesignSystem,
_userConfig: Config,
rawCandidate: string,
): string {
// Prepare design system with the legacy classes
if (!SEEDED.has(designSystem)) {
for (let old in LEGACY_CLASS_MAP) {
designSystem.utilities.static(old, () => [])
}
SEEDED.add(designSystem)
}
for (let candidate of designSystem.parseCandidate(rawCandidate)) {
if (candidate.kind === 'static' && Object.hasOwn(LEGACY_CLASS_MAP, candidate.root)) {
return printCandidate(designSystem, {
...candidate,
root: LEGACY_CLASS_MAP[candidate.root as keyof typeof LEGACY_CLASS_MAP],
})
}
}
return rawCandidate
}

View File

@ -7,6 +7,7 @@ import { automaticVarInjection } from './codemods/automatic-var-injection'
import { bgGradient } from './codemods/bg-gradient'
import { important } from './codemods/important'
import { prefix } from './codemods/prefix'
import { simpleLegacyClasses } from './codemods/simple-legacy-classes'
import { variantOrder } from './codemods/variant-order'
export type Migration = (
@ -20,6 +21,7 @@ export const DEFAULT_MIGRATIONS: Migration[] = [
important,
automaticVarInjection,
bgGradient,
simpleLegacyClasses,
variantOrder,
]