Skip to content

Commit 0cfb984

Browse files
Add simple JS config migration (#14639)
This PR implements the first version of JS config file migration to CSS. It is based on the most simple config setups we are using in the Tailwind UI templates Commit, Primer, Radiant, and Studio. The example we use in the integration test is a config that looks like this: ```js import { type Config } from 'tailwindcss' import defaultTheme from 'tailwindcss/defaultTheme' module.exports = { darkMode: 'selector', content: ['./src/**/*.{html,js}'], theme: { boxShadow: { sm: '0 2px 6px rgb(15 23 42 / 0.08)', }, colors: { red: { 500: '#ef4444', }, }, fontSize: { xs: ['0.75rem', { lineHeight: '1rem' }], sm: ['0.875rem', { lineHeight: '1.5rem' }], base: ['1rem', { lineHeight: '2rem' }], }, extend: { colors: { red: { 600: '#dc2626', }, }, fontFamily: { sans: 'Inter, system-ui, sans-serif', display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans], }, borderRadius: { '4xl': '2rem', }, }, }, plugins: [], } satisfies Config ``` As you can see, this file only has a `darkMode` selector, custom `content` globs, a `theme` (with some theme keys being overwriting the default theme and some others extending the defaults). Note that it does not support `plugins` and/or `presets` yet. In the case above, we will find the CSS file containing the existing `@tailwind` directives and are migrating it to the following: ```css @import 'tailwindcss'; @source './**/*.{html,js}'; @variant dark (&:where(.dark, .dark *)); @theme { --box-shadow-*: initial; --box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08); --color-*: initial; --color-red-500: #ef4444; --font-size-*: initial; --font-size-xs: 0.75rem; --font-size-xs--line-height: 1rem; --font-size-sm: 0.875rem; --font-size-sm--line-height: 1.5rem; --font-size-base: 1rem; --font-size-base--line-height: 2rem; --color-red-600: #dc2626; --font-family-sans: Inter, system-ui, sans-serif; --font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --border-radius-4xl: 2rem; } ``` This replicates all features of the JS config so we can even delete the existing JS config in this case. --------- Co-authored-by: Robin Malfait <[email protected]>
1 parent bd3d6bc commit 0cfb984

File tree

14 files changed

+528
-136
lines changed

14 files changed

+528
-136
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- _Upgrade (experimental)_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612))
1515
- _Upgrade (experimental)_: The upgrade tool now automatically discovers your JavaScript config ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597))
1616
- _Upgrade (experimental)_: Migrate legacy classes to the v4 alternative ([#14643](https://github.com/tailwindlabs/tailwindcss/pull/14643))
17+
- _Upgrade (experimental)_: Fully convert simple JS configs to CSS ([#14639](https://github.com/tailwindlabs/tailwindcss/pull/14639))
1718

1819
### Fixed
1920

integrations/upgrade/index.test.ts

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ test(
4040
4141
--- ./src/input.css ---
4242
@import 'tailwindcss';
43-
@config '../tailwind.config.js';
43+
44+
@source './**/*.{html,js}';
4445
"
4546
`)
4647

@@ -71,8 +72,9 @@ test(
7172
}
7273
`,
7374
'src/index.html': html`
74-
<h1>🤠👋</h1>
75-
<div class="!tw__flex sm:!tw__block tw__bg-gradient-to-t flex [color:red]"></div>
75+
<div
76+
class="!tw__flex sm:!tw__block tw__bg-gradient-to-t flex [color:red]"
77+
></div>
7678
`,
7779
'src/input.css': css`
7880
@tailwind base;
@@ -91,13 +93,14 @@ test(
9193
expect(await fs.dumpFiles('./src/**/*.{css,html}')).toMatchInlineSnapshot(`
9294
"
9395
--- ./src/index.html ---
94-
<h1>🤠👋</h1>
95-
<div class="tw:flex! tw:sm:block! tw:bg-linear-to-t flex tw:[color:red]"></div>
96+
<div
97+
class="tw:flex! tw:sm:block! tw:bg-linear-to-t flex tw:[color:red]"
98+
></div>
9699
97100
--- ./src/input.css ---
98101
@import 'tailwindcss' prefix(tw);
99102
100-
@config '../tailwind.config.js';
103+
@source './**/*.{html,js}';
101104
102105
.btn {
103106
@apply tw:rounded-md! tw:px-2 tw:py-1 tw:bg-blue-500 tw:text-white;
@@ -145,8 +148,6 @@ test(
145148
--- ./src/index.css ---
146149
@import 'tailwindcss';
147150
148-
@config '../tailwind.config.js';
149-
150151
.a {
151152
@apply flex;
152153
}
@@ -201,8 +202,6 @@ test(
201202
--- ./src/index.css ---
202203
@import 'tailwindcss';
203204
204-
@config '../tailwind.config.js';
205-
206205
@layer base {
207206
html {
208207
color: #333;
@@ -262,8 +261,6 @@ test(
262261
--- ./src/index.css ---
263262
@import 'tailwindcss';
264263
265-
@config '../tailwind.config.js';
266-
267264
@utility btn {
268265
@apply rounded-md px-2 py-1 bg-blue-500 text-white;
269266
}
@@ -631,7 +628,6 @@ test(
631628
--- ./src/index.css ---
632629
@import 'tailwindcss';
633630
@import './utilities.css';
634-
@config '../tailwind.config.js';
635631
636632
--- ./src/utilities.css ---
637633
@utility no-scrollbar {
@@ -748,7 +744,6 @@ test(
748744
@import './c.1.css' layer(utilities);
749745
@import './c.1.utilities.css';
750746
@import './d.1.css';
751-
@config '../tailwind.config.js';
752747
753748
--- ./src/a.1.css ---
754749
@import './a.1.utilities.css'
@@ -882,17 +877,14 @@ test(
882877
--- ./src/root.1.css ---
883878
@import 'tailwindcss/utilities' layer(utilities);
884879
@import './a.1.css' layer(utilities);
885-
@config '../tailwind.config.js';
886880
887881
--- ./src/root.2.css ---
888882
@import 'tailwindcss/utilities' layer(utilities);
889883
@import './a.1.css' layer(components);
890-
@config '../tailwind.config.js';
891884
892885
--- ./src/root.3.css ---
893886
@import 'tailwindcss/utilities' layer(utilities);
894887
@import './a.1.css' layer(utilities);
895-
@config '../tailwind.config.js';
896888
"
897889
`)
898890
},
@@ -912,11 +904,17 @@ test(
912904
'tailwind.config.ts': js`
913905
export default {
914906
content: ['./src/**/*.{html,js}'],
907+
plugins: [
908+
() => {
909+
// custom stuff which is too complicated to migrate to CSS
910+
},
911+
],
915912
}
916913
`,
917914
'src/index.html': html`
918-
<h1>🤠👋</h1>
919-
<div class="!flex sm:!block bg-gradient-to-t bg-[--my-red]"></div>
915+
<div
916+
class="!flex sm:!block bg-gradient-to-t bg-[--my-red]"
917+
></div>
920918
`,
921919
'src/root.1.css': css`
922920
/* Inject missing @config */
@@ -968,8 +966,9 @@ test(
968966
expect(await fs.dumpFiles('./src/**/*.{html,css}')).toMatchInlineSnapshot(`
969967
"
970968
--- ./src/index.html ---
971-
<h1>🤠👋</h1>
972-
<div class="flex! sm:block! bg-linear-to-t bg-[var(--my-red)]"></div>
969+
<div
970+
class="flex! sm:block! bg-linear-to-t bg-[var(--my-red)]"
971+
></div>
973972
974973
--- ./src/root.1.css ---
975974
/* Inject missing @config */
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { expect } from 'vitest'
2+
import { css, json, test, ts } from '../utils'
3+
4+
test(
5+
`upgrades a simple JS config file to CSS`,
6+
{
7+
fs: {
8+
'package.json': json`
9+
{
10+
"dependencies": {
11+
"@tailwindcss/upgrade": "workspace:^"
12+
}
13+
}
14+
`,
15+
'tailwind.config.ts': ts`
16+
import { type Config } from 'tailwindcss'
17+
import defaultTheme from 'tailwindcss/defaultTheme'
18+
19+
module.exports = {
20+
darkMode: 'selector',
21+
content: ['./src/**/*.{html,js}', './my-app/**/*.{html,js}'],
22+
theme: {
23+
boxShadow: {
24+
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
25+
},
26+
colors: {
27+
red: {
28+
400: '#f87171',
29+
500: 'red',
30+
},
31+
},
32+
fontSize: {
33+
xs: ['0.75rem', { lineHeight: '1rem' }],
34+
sm: ['0.875rem', { lineHeight: '1.5rem' }],
35+
base: ['1rem', { lineHeight: '2rem' }],
36+
},
37+
extend: {
38+
colors: {
39+
red: {
40+
500: '#ef4444',
41+
600: '#dc2626',
42+
},
43+
},
44+
fontFamily: {
45+
sans: 'Inter, system-ui, sans-serif',
46+
display: ['Cabinet Grotesk', ...defaultTheme.fontFamily.sans],
47+
},
48+
borderRadius: {
49+
'4xl': '2rem',
50+
},
51+
},
52+
},
53+
plugins: [],
54+
} satisfies Config
55+
`,
56+
'src/input.css': css`
57+
@tailwind base;
58+
@tailwind components;
59+
@tailwind utilities;
60+
`,
61+
},
62+
},
63+
async ({ exec, fs }) => {
64+
await exec('npx @tailwindcss/upgrade')
65+
66+
expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
67+
"
68+
--- src/input.css ---
69+
@import 'tailwindcss';
70+
71+
@source './**/*.{html,js}';
72+
@source '../my-app/**/*.{html,js}';
73+
74+
@variant dark (&:where(.dark, .dark *));
75+
76+
@theme {
77+
--box-shadow-*: initial;
78+
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);
79+
80+
--color-*: initial;
81+
--color-red-400: #f87171;
82+
--color-red-500: #ef4444;
83+
--color-red-600: #dc2626;
84+
85+
--font-size-*: initial;
86+
--font-size-xs: 0.75rem;
87+
--font-size-xs--line-height: 1rem;
88+
--font-size-sm: 0.875rem;
89+
--font-size-sm--line-height: 1.5rem;
90+
--font-size-base: 1rem;
91+
--font-size-base--line-height: 2rem;
92+
93+
--font-family-sans: Inter, system-ui, sans-serif;
94+
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
95+
96+
--radius-4xl: 2rem;
97+
}
98+
"
99+
`)
100+
101+
expect((await fs.dumpFiles('tailwind.config.ts')).trim()).toBe('')
102+
},
103+
)
104+
105+
test(
106+
`does not upgrade a complex JS config file to CSS`,
107+
{
108+
fs: {
109+
'package.json': json`
110+
{
111+
"dependencies": {
112+
"@tailwindcss/upgrade": "workspace:^"
113+
}
114+
}
115+
`,
116+
'tailwind.config.ts': ts`
117+
import { type Config } from 'tailwindcss'
118+
119+
export default {
120+
plugins: [function complexConfig() {}],
121+
} satisfies Config
122+
`,
123+
'src/input.css': css`
124+
@tailwind base;
125+
@tailwind components;
126+
@tailwind utilities;
127+
`,
128+
},
129+
},
130+
async ({ exec, fs }) => {
131+
await exec('npx @tailwindcss/upgrade')
132+
133+
expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
134+
"
135+
--- src/input.css ---
136+
@import 'tailwindcss';
137+
@config '../tailwind.config.ts';
138+
"
139+
`)
140+
141+
expect(await fs.dumpFiles('tailwind.config.ts')).toMatchInlineSnapshot(`
142+
"
143+
--- tailwind.config.ts ---
144+
import { type Config } from 'tailwindcss'
145+
146+
export default {
147+
plugins: [function complexConfig() {}],
148+
} satisfies Config
149+
"
150+
`)
151+
},
152+
)

integrations/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export function test(
7575
) {
7676
return (only || (!process.env.CI && debug) ? defaultTest.only : defaultTest)(
7777
name,
78-
{ timeout: TEST_TIMEOUT, retry: debug || only ? 0 : 3 },
78+
{ timeout: TEST_TIMEOUT, retry: process.env.CI ? 2 : 0 },
7979
async (options) => {
8080
let rootDir = debug ? path.join(REPO_ROOT, '.debug') : TMP_ROOT
8181
await fs.mkdir(rootDir, { recursive: true })

0 commit comments

Comments
 (0)