Skip to content

Commit

Permalink
Ensure upgrade tool has access to a JS config (#14597)
Browse files Browse the repository at this point in the history
In order to properly migrate your Tailwind CSS v3 project to v4, we need
access to the JavaScript configuration object. This was previously only
required for template migrations, but in this PR we're making it so that
this is also a prerequisite of the CSS migrations. This is because some
migrations, like `@apply`, also need to convert candidates that to the
v4 syntax and we need the full config in order to properly validate
them.

In addition to requiring a JS config, we also now attempt to
automatically find the right configuration file inside the current
working directory. This is now matching the behavior of the Tailwind CSS
v3 CLI where it will find the config automatically if it's in the
current directory and called `tailwind.conf.js`.

---------

Co-authored-by: Robin Malfait <[email protected]>
  • Loading branch information
philipp-spiess and RobinMalfait authored Oct 7, 2024
1 parent c7a9e9b commit 7be5346
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 131 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add support for `tailwindcss/colors.js`, `tailwindcss/defaultTheme.js`, and `tailwindcss/plugin.js` exports ([#14595](https://github.com/tailwindlabs/tailwindcss/pull/14595))
- Support `keyframes` in JS config file themes ([14594](https://github.com/tailwindlabs/tailwindcss/pull/14594))
- Support `keyframes` in JS config file themes ([#14594](https://github.com/tailwindlabs/tailwindcss/pull/14594))
- _Experimental_: The upgrade tool now automatically discovers your JavaScript config ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597))

### Fixed

Expand Down
7 changes: 5 additions & 2 deletions integrations/upgrade/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ test(
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade -c tailwind.config.js')
await exec('npx @tailwindcss/upgrade')

await fs.expectFileToContain(
'src/index.html',
Expand Down Expand Up @@ -77,7 +77,7 @@ test(
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade -c tailwind.config.js')
await exec('npx @tailwindcss/upgrade')

await fs.expectFileToContain(
'src/index.html',
Expand Down Expand Up @@ -111,6 +111,7 @@ test(
}
}
`,
'tailwind.config.js': js`module.exports = {}`,
'src/index.css': css`
@import 'tailwindcss';
Expand Down Expand Up @@ -162,6 +163,7 @@ test(
}
}
`,
'tailwind.config.js': js`module.exports = {}`,
'src/index.css': css`
@tailwind base;
Expand Down Expand Up @@ -218,6 +220,7 @@ test(
}
}
`,
'tailwind.config.js': js`module.exports = {}`,
'src/index.css': css`
@import 'tailwindcss';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
import dedent from 'dedent'
import postcss from 'postcss'
import type { Config } from 'tailwindcss'
import { expect, it } from 'vitest'
import { migrateAtApply } from './migrate-at-apply'

const css = dedent

function migrateWithoutConfig(input: string) {
async function migrate(input: string, config: Config = {}) {
let designSystem = await __unstable__loadDesignSystem(
css`
@import 'tailwindcss';
`,
{ base: __dirname },
)

return postcss()
.use(migrateAtApply())
.use(
migrateAtApply({
designSystem,
userConfig: config,
}),
)
.process(input, { from: expect.getState().testPath })
.then((result) => result.css)
}

it('should not migrate `@apply`, when there are no issues', async () => {
expect(
await migrateWithoutConfig(css`
await migrate(css`
.foo {
@apply flex flex-col items-center;
}
Expand All @@ -29,7 +42,7 @@ it('should not migrate `@apply`, when there are no issues', async () => {

it('should append `!` to each utility, when using `!important`', async () => {
expect(
await migrateWithoutConfig(css`
await migrate(css`
.foo {
@apply flex flex-col !important;
}
Expand All @@ -44,7 +57,7 @@ it('should append `!` to each utility, when using `!important`', async () => {
// TODO: Handle SCSS syntax
it.skip('should append `!` to each utility, when using `#{!important}`', async () => {
expect(
await migrateWithoutConfig(css`
await migrate(css`
.foo {
@apply flex flex-col #{!important};
}
Expand All @@ -58,7 +71,7 @@ it.skip('should append `!` to each utility, when using `#{!important}`', async (

it('should move the legacy `!` prefix, to the new `!` postfix notation', async () => {
expect(
await migrateWithoutConfig(css`
await migrate(css`
.foo {
@apply !flex flex-col! hover:!items-start items-center;
}
Expand All @@ -71,7 +84,7 @@ it('should move the legacy `!` prefix, to the new `!` postfix notation', async (
})

it('should apply all candidate migration when migrating with a config', async () => {
async function migrateWithConfig(input: string) {
async function migrateWithPrefix(input: string) {
return postcss()
.use(
migrateAtApply({
Expand All @@ -91,7 +104,7 @@ it('should apply all candidate migration when migrating with a config', async ()
}

expect(
await migrateWithConfig(css`
await migrateWithPrefix(css`
.foo {
@apply !tw_flex [color:--my-color] tw_bg-gradient-to-t;
}
Expand Down
14 changes: 5 additions & 9 deletions packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import { migrateCandidate } from '../template/migrate'
export function migrateAtApply({
designSystem,
userConfig,
}: { designSystem?: DesignSystem; userConfig?: Config } = {}): Plugin {
}: {
designSystem: DesignSystem
userConfig: Config
}): Plugin {
function migrate(atRule: AtRule) {
let utilities = atRule.params.split(/(\s+)/)
let important =
Expand All @@ -27,20 +30,13 @@ export function migrateAtApply({
utility += '!'
}

// Migrate the important modifier to the end of the utility
if (utility[0] === '!') {
utility = `${utility.slice(1)}!`
}

// Reconstruct the utility with the variants
return [...variants, utility].join(':')
})

// If we have a valid designSystem and config setup, we can run all
// candidate migrations on each utility
if (designSystem && userConfig) {
params = params.map((param) => migrateCandidate(designSystem, userConfig, param))
}
params = params.map((param) => migrateCandidate(designSystem, userConfig, param))

atRule.params = params.join('').trim()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { migrateTailwindDirectives } from './migrate-tailwind-directives'

const css = dedent

function migrate(input: string, options: { newPrefix?: string } = {}) {
function migrate(input: string, options: { newPrefix: string | null } = { newPrefix: null }) {
return postcss()
.use(migrateTailwindDirectives(options))
.use(formatNodes())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AtRule, type ChildNode, type Plugin, type Root } from 'postcss'

const DEFAULT_LAYER_ORDER = ['theme', 'base', 'components', 'utilities']

export function migrateTailwindDirectives(options: { newPrefix?: string }): Plugin {
export function migrateTailwindDirectives(options: { newPrefix: string | null }): Plugin {
let prefixParams = options.newPrefix ? ` prefix(${options.newPrefix})` : ''

function migrate(root: Root) {
Expand Down
19 changes: 14 additions & 5 deletions packages/@tailwindcss-upgrade/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
import dedent from 'dedent'
import { expect, it } from 'vitest'
import { migrateContents } from './migrate'

const css = dedent

let designSystem = await __unstable__loadDesignSystem(
css`
@import 'tailwindcss';
`,
{ base: __dirname },
)
let config = { designSystem, userConfig: {}, newPrefix: null }

it('should print the input as-is', async () => {
expect(
await migrateContents(
Expand All @@ -15,7 +24,7 @@ it('should print the input as-is', async () => {
/* below */
}
`,
{},
config,
expect.getState().testPath,
),
).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -66,7 +75,7 @@ it('should migrate a stylesheet', async () => {
}
}
`,
{},
config,
),
).toMatchInlineSnapshot(`
"@import 'tailwindcss';
Expand Down Expand Up @@ -116,7 +125,7 @@ it('should migrate a stylesheet (with imports)', async () => {
@import 'tailwindcss/utilities';
@import './my-utilities.css';
`,
{},
config,
),
).toMatchInlineSnapshot(`
"@import 'tailwindcss';
Expand All @@ -140,7 +149,7 @@ it('should migrate a stylesheet (with preceding rules that should be wrapped in
@tailwind components;
@tailwind utilities;
`,
{},
config,
),
).toMatchInlineSnapshot(`
"@charset "UTF-8";
Expand Down Expand Up @@ -169,7 +178,7 @@ it('should keep CSS as-is before existing `@layer` at-rules', async () => {
}
}
`,
{},
config,
),
).toMatchInlineSnapshot(`
".foo {
Expand Down
37 changes: 6 additions & 31 deletions packages/@tailwindcss-upgrade/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

import { globby } from 'globby'
import path from 'node:path'
import type { Config } from 'tailwindcss'
import type { DesignSystem } from '../../tailwindcss/src/design-system'
import { help } from './commands/help'
import { migrate as migrateStylesheet } from './migrate'
import { migrate as migrateTemplate } from './template/migrate'
import { parseConfig } from './template/parseConfig'
import { prepareConfig } from './template/prepare-config'
import { args, type Arg } from './utils/args'
import { isRepoDirty } from './utils/git'
import { eprintln, error, header, highlight, info, success } from './utils/renderer'
Expand Down Expand Up @@ -42,28 +40,15 @@ async function run() {
}
}

let parsedConfig: {
designSystem: DesignSystem
globs: { pattern: string; base: string }[]
userConfig: Config
newPrefix: string | null
} | null = null
if (flags['--config']) {
try {
parsedConfig = await parseConfig(flags['--config'], { base: process.cwd() })
} catch (e: any) {
error(`Failed to parse the configuration file: ${e.message}`)
process.exit(1)
}
}
let config = await prepareConfig(flags['--config'], { base: process.cwd() })

if (parsedConfig) {
{
// Template migrations

info('Migrating templates using the provided configuration file.')

let set = new Set<string>()
for (let { pattern, base } of parsedConfig.globs) {
for (let { pattern, base } of config.globs) {
let files = await globby([pattern], {
absolute: true,
gitignore: true,
Expand All @@ -80,9 +65,7 @@ async function run() {

// Migrate each file
await Promise.allSettled(
files.map((file) =>
migrateTemplate(parsedConfig.designSystem, parsedConfig.userConfig, file),
),
files.map((file) => migrateTemplate(config.designSystem, config.userConfig, file)),
)

success('Template migration complete.')
Expand Down Expand Up @@ -110,15 +93,7 @@ async function run() {
files = files.filter((file) => file.endsWith('.css'))

// Migrate each file
await Promise.allSettled(
files.map((file) =>
migrateStylesheet(file, {
newPrefix: parsedConfig?.newPrefix ?? undefined,
designSystem: parsedConfig?.designSystem,
userConfig: parsedConfig?.userConfig,
}),
),
)
await Promise.allSettled(files.map((file) => migrateStylesheet(file, config)))

success('Stylesheet migration complete.')
}
Expand Down
6 changes: 3 additions & 3 deletions packages/@tailwindcss-upgrade/src/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { migrateMissingLayers } from './codemods/migrate-missing-layers'
import { migrateTailwindDirectives } from './codemods/migrate-tailwind-directives'

export interface MigrateOptions {
newPrefix?: string
designSystem?: DesignSystem
userConfig?: Config
newPrefix: string | null
designSystem: DesignSystem
userConfig: Config
}

export async function migrateContents(contents: string, options: MigrateOptions, file?: string) {
Expand Down
Loading

0 comments on commit 7be5346

Please sign in to comment.