Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- _Upgrade (experimental)_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612))
- _Upgrade (experimental)_: Automatically discover JavaScript config files ([#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))
- _Upgrade (experimental)_: Migrate static JS configurations to CSS ([#14639](https://github.com/tailwindlabs/tailwindcss/pull/14639), [#14650](https://github.com/tailwindlabs/tailwindcss/pull/14650))
- _Upgrade (experimental)_: Migrate static JS configurations to CSS ([#14639](https://github.com/tailwindlabs/tailwindcss/pull/14639), [#14650](https://github.com/tailwindlabs/tailwindcss/pull/14650), [#14648](https://github.com/tailwindlabs/tailwindcss/pull/14648))
- _Upgrade (experimental)_: Migrate `@media screen(…)` when running codemods ([#14603](https://github.com/tailwindlabs/tailwindcss/pull/14603))
- _Upgrade (experimental)_: Inject `@config "…"` when a `tailwind.config.{js,ts,…}` is detected ([#14635](https://github.com/tailwindlabs/tailwindcss/pull/14635))
- _Upgrade (experimental)_: Migrate `aria-*`, `data-*`, and `supports-*` variants from arbitrary values to bare values ([#14644](https://github.com/tailwindlabs/tailwindcss/pull/14644))
Expand Down
99 changes: 47 additions & 52 deletions integrations/upgrade/js-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,29 +103,32 @@ test(
)

test(
'does not upgrade JS config files with functions in the theme config',
'upgrades JS config files with plugins',
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.ts': ts`
import { type Config } from 'tailwindcss'
import typography from '@tailwindcss/typography'
import customPlugin from './custom-plugin'

export default {
theme: {
extend: {
colors: ({ colors }) => ({
gray: colors.neutral,
}),
},
},
plugins: [typography, customPlugin],
} satisfies Config
`,
'custom-plugin.js': ts`
export default function ({ addVariant }) {
addVariant('inverted', '@media (inverted-colors: inverted)')
addVariant('hocus', ['&:focus', '&:hover'])
}
`,
'src/input.css': css`
@tailwind base;
@tailwind components;
Expand All @@ -136,35 +139,26 @@ test(
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade')

expect(await fs.dumpFiles('src/**/*.{css,ts}')).toMatchInlineSnapshot(`
expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
"
--- src/input.css ---
@import 'tailwindcss';
@config '../tailwind.config.ts';

@plugin '@tailwindcss/typography';
@plugin '../custom-plugin';
"
`)

expect(await fs.dumpFiles('tailwind.config.ts')).toMatchInlineSnapshot(`
"
--- tailwind.config.ts ---
import { type Config } from 'tailwindcss'

export default {
theme: {
extend: {
colors: ({ colors }) => ({
gray: colors.neutral,
}),
},
},
} satisfies Config
"
`)
},
)

test(
'does not upgrade JS config files with theme keys contributed to by plugins in the theme config',
'does not upgrade JS config files with functions in the theme config',
{
fs: {
'package.json': json`
Expand All @@ -179,13 +173,10 @@ test(

export default {
theme: {
typography: {
DEFAULT: {
css: {
'--tw-prose-body': 'red',
color: 'var(--tw-prose-body)',
},
},
extend: {
colors: ({ colors }) => ({
gray: colors.neutral,
}),
},
},
} satisfies Config
Expand All @@ -194,14 +185,13 @@ test(
@tailwind base;
@tailwind components;
@tailwind utilities;
@config '../tailwind.config.ts';
`,
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade')

expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
expect(await fs.dumpFiles('src/**/*.{css,ts}')).toMatchInlineSnapshot(`
"
--- src/input.css ---
@import 'tailwindcss';
Expand All @@ -216,13 +206,10 @@ test(

export default {
theme: {
typography: {
DEFAULT: {
css: {
'--tw-prose-body': 'red',
color: 'var(--tw-prose-body)',
},
},
extend: {
colors: ({ colors }) => ({
gray: colors.neutral,
}),
},
},
} satisfies Config
Expand All @@ -232,36 +219,37 @@ test(
)

test(
'does not upgrade JS config files with plugins',
'does not upgrade JS config files with theme keys contributed to by plugins in the theme config',
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.ts': ts`
import { type Config } from 'tailwindcss'
import typography from '@tailwindcss/typography'
import customPlugin from './custom-plugin'

export default {
plugins: [typography, customPlugin],
theme: {
typography: {
DEFAULT: {
css: {
'--tw-prose-body': 'red',
color: 'var(--tw-prose-body)',
},
},
},
},
} satisfies Config
`,
'custom-plugin.js': ts`
export default function ({ addVariant }) {
addVariant('inverted', '@media (inverted-colors: inverted)')
addVariant('hocus', ['&:focus', '&:hover'])
}
`,
'src/input.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@config '../tailwind.config.ts';
`,
},
},
Expand All @@ -280,11 +268,18 @@ test(
"
--- tailwind.config.ts ---
import { type Config } from 'tailwindcss'
import typography from '@tailwindcss/typography'
import customPlugin from './custom-plugin'

export default {
plugins: [typography, customPlugin],
theme: {
typography: {
DEFAULT: {
css: {
'--tw-prose-body': 'red',
color: 'var(--tw-prose-body)',
},
},
},
},
} satisfies Config
"
`)
Expand Down
4 changes: 3 additions & 1 deletion packages/@tailwindcss-upgrade/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
"postcss-selector-parser": "^6.1.2",
"prettier": "^3.3.3",
"string-byte-slice": "^3.0.0",
"tailwindcss": "workspace:^"
"tailwindcss": "workspace:^",
"tree-sitter": "^0.21.1",
"tree-sitter-typescript": "^0.23.0"
},
"devDependencies": {
"@types/node": "catalog:",
Expand Down
12 changes: 11 additions & 1 deletion packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,21 @@ export function migrateConfig(
let absolute = path.resolve(source.base, source.pattern)
css += `@source '${relativeToStylesheet(sheet, absolute)}';\n`
}

if (jsConfigMigration.sources.length > 0) {
css = css + '\n'
}

for (let plugin of jsConfigMigration.plugins) {
let relative =
plugin.path[0] === '.'
? relativeToStylesheet(sheet, path.resolve(plugin.base, plugin.path))
: plugin.path
css += `@plugin '${relative}';\n`
}
if (jsConfigMigration.plugins.length > 0) {
css = css + '\n'
}

cssConfig.append(postcss.parse(css + jsConfigMigration.css))
}

Expand Down
17 changes: 15 additions & 2 deletions packages/@tailwindcss-upgrade/src/migrate-js-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { deepMerge } from '../../tailwindcss/src/compat/config/deep-merge'
import { mergeThemeExtension } from '../../tailwindcss/src/compat/config/resolve-config'
import type { ThemeConfig } from '../../tailwindcss/src/compat/config/types'
import { darkModePlugin } from '../../tailwindcss/src/compat/dark-mode'
import { findStaticPlugins } from './utils/extract-static-plugins'
import { info } from './utils/renderer'

const __filename = fileURLToPath(import.meta.url)
Expand All @@ -21,6 +22,7 @@ export type JSConfigMigration =
// Could not convert the config file, need to inject it as-is in a @config directive
null | {
sources: { base: string; pattern: string }[]
plugins: { base: string; path: string }[]
css: string
}

Expand All @@ -41,6 +43,7 @@ export async function migrateJsConfig(
}

let sources: { base: string; pattern: string }[] = []
let plugins: { base: string; path: string }[] = []
let cssConfigs: string[] = []

if ('darkMode' in unresolvedConfig) {
Expand All @@ -56,8 +59,16 @@ export async function migrateJsConfig(
if (themeConfig) cssConfigs.push(themeConfig)
}

let simplePlugins = findStaticPlugins(source)
if (simplePlugins !== null) {
for (let plugin of simplePlugins) {
plugins.push({ base, path: plugin })
}
}

return {
sources,
plugins,
css: cssConfigs.join('\n'),
}
}
Expand Down Expand Up @@ -168,7 +179,9 @@ function canMigrateConfig(unresolvedConfig: Config, source: string): boolean {
return ['string', 'number', 'boolean', 'undefined'].includes(typeof value)
}

if (!isSimpleValue(unresolvedConfig)) {
// Plugins are more complex, so we have a special heuristics for them.
let { plugins, ...remainder } = unresolvedConfig
if (!isSimpleValue(remainder)) {
return false
}

Expand All @@ -186,7 +199,7 @@ function canMigrateConfig(unresolvedConfig: Config, source: string): boolean {
return false
}

if (unresolvedConfig.plugins && unresolvedConfig.plugins.length > 0) {
if (findStaticPlugins(source) === null) {
return false
}

Expand Down
Loading