Skip to content

Commit 75b9066

Browse files
Migrate simple PostCSS setup (#14612)
This PR attempts to detect simple postcss setups: These are setups that do not load dynamic modules and are based off the examples we are [recommending in our docs](https://tailwindcss.com/docs/installation/using-postcss). We detect wether a config is appropriate by having it use the object plugin config and by not requiring any other modules: ```js module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } ``` When we find such a config file, we will go over it line-by-line and attempt to: - Upgrade `tailwindcss:` to `'@tailwindcss/postcss':` - Remove `autoprefixer` if used We then attempt to install and remove the respective npm packages based on the package manger we detect. And since we now have logic to upgrade packages, this also makes sure to install `tailwindcss@next` at the end of a sucessful migration. --------- Co-authored-by: Robin Malfait <[email protected]>
1 parent 3ae22f1 commit 75b9066

File tree

6 files changed

+648
-3
lines changed

6 files changed

+648
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Add support for `tailwindcss/colors.js`, `tailwindcss/defaultTheme.js`, and `tailwindcss/plugin.js` exports ([#14595](https://github.com/tailwindlabs/tailwindcss/pull/14595))
1313
- Support `keyframes` in JS config file themes ([#14594](https://github.com/tailwindlabs/tailwindcss/pull/14594))
14+
- _Upgrade (experimental)_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612))
1415
- _Upgrade (experimental)_: The upgrade tool now automatically discovers your JavaScript config ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597))
1516

1617
### Fixed

integrations/upgrade/index.test.ts

Lines changed: 236 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { expect } from 'vitest'
12
import { css, html, js, json, test } from '../utils'
23

34
test(
@@ -40,6 +41,12 @@ test(
4041
)
4142

4243
await fs.expectFileToContain('src/input.css', css`@import 'tailwindcss';`)
44+
45+
let packageJsonContent = await fs.read('package.json')
46+
let packageJson = JSON.parse(packageJsonContent)
47+
expect(packageJson.dependencies).toMatchObject({
48+
tailwindcss: expect.stringContaining('4.0.0'),
49+
})
4350
},
4451
)
4552

@@ -265,6 +272,233 @@ test(
265272
},
266273
)
267274

275+
test(
276+
'migrates a simple postcss setup',
277+
{
278+
fs: {
279+
'package.json': json`
280+
{
281+
"dependencies": {
282+
"postcss": "^8",
283+
"postcss-cli": "^10",
284+
"postcss-import": "^16",
285+
"autoprefixer": "^10",
286+
"tailwindcss": "^3",
287+
"@tailwindcss/upgrade": "workspace:^"
288+
}
289+
}
290+
`,
291+
'tailwind.config.js': js`
292+
/** @type {import('tailwindcss').Config} */
293+
module.exports = {
294+
content: ['./src/**/*.{html,js}'],
295+
}
296+
`,
297+
'postcss.config.js': js`
298+
module.exports = {
299+
plugins: {
300+
'postcss-import': {},
301+
'tailwindcss/nesting': 'postcss-nesting',
302+
tailwindcss: {},
303+
autoprefixer: {},
304+
},
305+
}
306+
`,
307+
'src/index.html': html`
308+
<div class="bg-[--my-red]"></div>
309+
`,
310+
'src/index.css': css`
311+
@tailwind base;
312+
@tailwind components;
313+
@tailwind utilities;
314+
`,
315+
},
316+
},
317+
async ({ fs, exec }) => {
318+
await exec('npx @tailwindcss/upgrade')
319+
320+
await fs.expectFileToContain(
321+
'postcss.config.js',
322+
js`
323+
module.exports = {
324+
plugins: {
325+
'@tailwindcss/postcss': {},
326+
},
327+
}
328+
`,
329+
)
330+
await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`)
331+
await fs.expectFileToContain(
332+
'src/index.html',
333+
// prettier-ignore
334+
js`
335+
<div class="bg-[var(--my-red)]"></div>
336+
`,
337+
)
338+
339+
let packageJsonContent = await fs.read('package.json')
340+
let packageJson = JSON.parse(packageJsonContent)
341+
expect(packageJson.dependencies).toMatchObject({
342+
tailwindcss: expect.stringContaining('4.0.0'),
343+
})
344+
expect(packageJson.dependencies).not.toHaveProperty('autoprefixer')
345+
expect(packageJson.dependencies).not.toHaveProperty('postcss-import')
346+
expect(packageJson.devDependencies).toMatchObject({
347+
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
348+
})
349+
},
350+
)
351+
352+
test(
353+
'migrates a postcss setup using package.json config',
354+
{
355+
fs: {
356+
'package.json': json`
357+
{
358+
"dependencies": {
359+
"postcss": "^8",
360+
"postcss-cli": "^10",
361+
"postcss-import": "^16",
362+
"autoprefixer": "^10",
363+
"tailwindcss": "^3",
364+
"@tailwindcss/upgrade": "workspace:^"
365+
},
366+
"postcss": {
367+
"plugins": {
368+
"postcss-import": {},
369+
"tailwindcss/nesting": "postcss-nesting",
370+
"tailwindcss": {},
371+
"autoprefixer": {}
372+
}
373+
}
374+
}
375+
`,
376+
'tailwind.config.js': js`
377+
/** @type {import('tailwindcss').Config} */
378+
module.exports = {
379+
content: ['./src/**/*.{html,js}'],
380+
}
381+
`,
382+
'src/index.html': html`
383+
<div class="bg-[--my-red]"></div>
384+
`,
385+
'src/index.css': css`
386+
@tailwind base;
387+
@tailwind components;
388+
@tailwind utilities;
389+
`,
390+
},
391+
},
392+
async ({ fs, exec }) => {
393+
await exec('npx @tailwindcss/upgrade')
394+
395+
await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`)
396+
await fs.expectFileToContain(
397+
'src/index.html',
398+
// prettier-ignore
399+
js`
400+
<div class="bg-[var(--my-red)]"></div>
401+
`,
402+
)
403+
404+
let packageJsonContent = await fs.read('package.json')
405+
let packageJson = JSON.parse(packageJsonContent)
406+
expect(packageJson.postcss).toMatchInlineSnapshot(`
407+
{
408+
"plugins": {
409+
"@tailwindcss/postcss": {},
410+
},
411+
}
412+
`)
413+
414+
expect(packageJson.dependencies).toMatchObject({
415+
tailwindcss: expect.stringContaining('4.0.0'),
416+
})
417+
expect(packageJson.dependencies).not.toHaveProperty('autoprefixer')
418+
expect(packageJson.dependencies).not.toHaveProperty('postcss-import')
419+
expect(packageJson.devDependencies).toMatchObject({
420+
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
421+
})
422+
},
423+
)
424+
425+
test(
426+
'migrates a postcss setup using a json based config file',
427+
{
428+
fs: {
429+
'package.json': json`
430+
{
431+
"dependencies": {
432+
"postcss": "^8",
433+
"postcss-cli": "^10",
434+
"postcss-import": "^16",
435+
"autoprefixer": "^10",
436+
"tailwindcss": "^3",
437+
"@tailwindcss/upgrade": "workspace:^"
438+
}
439+
}
440+
`,
441+
'.postcssrc.json': json`
442+
{
443+
"plugins": {
444+
"postcss-import": {},
445+
"tailwindcss/nesting": "postcss-nesting",
446+
"tailwindcss": {},
447+
"autoprefixer": {}
448+
}
449+
}
450+
`,
451+
'tailwind.config.js': js`
452+
/** @type {import('tailwindcss').Config} */
453+
module.exports = {
454+
content: ['./src/**/*.{html,js}'],
455+
}
456+
`,
457+
'src/index.html': html`
458+
<div class="bg-[--my-red]"></div>
459+
`,
460+
'src/index.css': css`
461+
@tailwind base;
462+
@tailwind components;
463+
@tailwind utilities;
464+
`,
465+
},
466+
},
467+
async ({ fs, exec }) => {
468+
await exec('npx @tailwindcss/upgrade')
469+
470+
await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`)
471+
await fs.expectFileToContain(
472+
'src/index.html',
473+
// prettier-ignore
474+
js`
475+
<div class="bg-[var(--my-red)]"></div>
476+
`,
477+
)
478+
479+
let jsonConfigContent = await fs.read('.postcssrc.json')
480+
let jsonConfig = JSON.parse(jsonConfigContent)
481+
expect(jsonConfig).toMatchInlineSnapshot(`
482+
{
483+
"plugins": {
484+
"@tailwindcss/postcss": {},
485+
},
486+
}
487+
`)
488+
489+
let packageJsonContent = await fs.read('package.json')
490+
let packageJson = JSON.parse(packageJsonContent)
491+
expect(packageJson.dependencies).toMatchObject({
492+
tailwindcss: expect.stringContaining('4.0.0'),
493+
})
494+
expect(packageJson.dependencies).not.toHaveProperty('autoprefixer')
495+
expect(packageJson.dependencies).not.toHaveProperty('postcss-import')
496+
expect(packageJson.devDependencies).toMatchObject({
497+
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
498+
})
499+
},
500+
)
501+
268502
test(
269503
`migrates prefixes even if other files have unprefixed versions of the candidate`,
270504
{
@@ -297,7 +531,7 @@ test(
297531
},
298532
},
299533
async ({ exec, fs }) => {
300-
await exec('npx @tailwindcss/upgrade -c tailwind.config.js')
534+
await exec('npx @tailwindcss/upgrade')
301535

302536
await fs.expectFileToContain('src/index.html', html`
303537
<div class="flex"></div>
@@ -335,7 +569,7 @@ test(
335569
},
336570
},
337571
async ({ exec, fs }) => {
338-
await exec('npx @tailwindcss/upgrade -c tailwind.config.js')
572+
await exec('npx @tailwindcss/upgrade')
339573

340574
await fs.expectFileToContain(
341575
'src/index.html',

integrations/utils.ts

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

packages/@tailwindcss-upgrade/src/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import { globby } from 'globby'
44
import path from 'node:path'
55
import { help } from './commands/help'
66
import { migrate as migrateStylesheet } from './migrate'
7+
import { migratePostCSSConfig } from './migrate-postcss'
78
import { migrate as migrateTemplate } from './template/migrate'
89
import { prepareConfig } from './template/prepare-config'
910
import { args, type Arg } from './utils/args'
1011
import { isRepoDirty } from './utils/git'
12+
import { pkg } from './utils/packages'
1113
import { eprintln, error, header, highlight, info, success } from './utils/renderer'
1214

1315
const options = {
@@ -98,6 +100,16 @@ async function run() {
98100
success('Stylesheet migration complete.')
99101
}
100102

103+
{
104+
// PostCSS config migration
105+
await migratePostCSSConfig(process.cwd())
106+
}
107+
108+
try {
109+
// Upgrade Tailwind CSS
110+
await pkg('add tailwindcss@next', process.cwd())
111+
} catch {}
112+
101113
// Figure out if we made any changes
102114
if (isRepoDirty()) {
103115
success('Verify the changes and commit them to your repository.')

0 commit comments

Comments
 (0)