tailwindcss/packages/@tailwindcss-upgrade/src/template/migrate.ts
Robin Malfait bd3d6bc09b
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` |
2024-10-11 14:22:55 +02:00

70 lines
2.1 KiB
TypeScript

import fs from 'node:fs/promises'
import path, { extname } from 'node:path'
import type { Config } from 'tailwindcss'
import type { DesignSystem } from '../../../tailwindcss/src/design-system'
import { extractRawCandidates, replaceCandidateInContent } from './candidates'
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 = (
designSystem: DesignSystem,
userConfig: Config,
rawCandidate: string,
) => string
export const DEFAULT_MIGRATIONS: Migration[] = [
prefix,
important,
automaticVarInjection,
bgGradient,
simpleLegacyClasses,
variantOrder,
]
export function migrateCandidate(
designSystem: DesignSystem,
userConfig: Config,
rawCandidate: string,
): string {
for (let migration of DEFAULT_MIGRATIONS) {
rawCandidate = migration(designSystem, userConfig, rawCandidate)
}
return rawCandidate
}
export default async function migrateContents(
designSystem: DesignSystem,
userConfig: Config,
contents: string,
extension: string,
): Promise<string> {
let candidates = await extractRawCandidates(contents, extension)
// Sort candidates by starting position desc
candidates.sort((a, z) => z.start - a.start)
let output = contents
for (let { rawCandidate, start, end } of candidates) {
let migratedCandidate = migrateCandidate(designSystem, userConfig, rawCandidate)
if (migratedCandidate !== rawCandidate) {
output = replaceCandidateInContent(output, migratedCandidate, start, end)
}
}
return output
}
export async function migrate(designSystem: DesignSystem, userConfig: Config, file: string) {
let fullPath = path.resolve(process.cwd(), file)
let contents = await fs.readFile(fullPath, 'utf-8')
await fs.writeFile(
fullPath,
await migrateContents(designSystem, userConfig, contents, extname(file)),
)
}