Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate simple PostCSS setup #14612

Merged
merged 14 commits into from
Oct 10, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- 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))
- _Upgrade (experimental)_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612))
- _Upgrade (experimental)_: The upgrade tool now automatically discovers your JavaScript config ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597))

### Fixed
Expand Down
238 changes: 236 additions & 2 deletions integrations/upgrade/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { expect } from 'vitest'
import { css, html, js, json, test } from '../utils'

test(
Expand Down Expand Up @@ -40,6 +41,12 @@ test(
)

await fs.expectFileToContain('src/input.css', css`@import 'tailwindcss';`)

let packageJsonContent = await fs.read('package.json')
let packageJson = JSON.parse(packageJsonContent)
expect(packageJson.dependencies).toMatchObject({
tailwindcss: expect.stringContaining('4.0.0'),
})
},
)

Expand Down Expand Up @@ -265,6 +272,233 @@ test(
},
)

test(
'migrates a simple postcss setup',
{
fs: {
'package.json': json`
{
"dependencies": {
"postcss": "^8",
"postcss-cli": "^10",
"postcss-import": "^16",
"autoprefixer": "^10",
"tailwindcss": "^3",
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.js': js`
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{html,js}'],
}
`,
'postcss.config.js': js`
module.exports = {
plugins: {
'postcss-import': {},
'tailwindcss/nesting': 'postcss-nesting',
tailwindcss: {},
autoprefixer: {},
},
}
`,
'src/index.html': html`
<div class="bg-[--my-red]"></div>
`,
'src/index.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`,
},
},
async ({ fs, exec }) => {
await exec('npx @tailwindcss/upgrade')

await fs.expectFileToContain(
'postcss.config.js',
js`
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
},
}
`,
)
await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`)
await fs.expectFileToContain(
'src/index.html',
// prettier-ignore
js`
<div class="bg-[var(--my-red)]"></div>
`,
)

let packageJsonContent = await fs.read('package.json')
let packageJson = JSON.parse(packageJsonContent)
expect(packageJson.dependencies).toMatchObject({
tailwindcss: expect.stringContaining('4.0.0'),
})
expect(packageJson.dependencies).not.toHaveProperty('autoprefixer')
expect(packageJson.dependencies).not.toHaveProperty('postcss-import')
expect(packageJson.devDependencies).toMatchObject({
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
})
},
)

test(
'migrates a postcss setup using package.json config',
{
fs: {
'package.json': json`
{
"dependencies": {
"postcss": "^8",
"postcss-cli": "^10",
"postcss-import": "^16",
"autoprefixer": "^10",
"tailwindcss": "^3",
"@tailwindcss/upgrade": "workspace:^"
},
"postcss": {
"plugins": {
"postcss-import": {},
"tailwindcss/nesting": "postcss-nesting",
"tailwindcss": {},
"autoprefixer": {}
}
}
}
`,
'tailwind.config.js': js`
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{html,js}'],
}
`,
'src/index.html': html`
<div class="bg-[--my-red]"></div>
`,
'src/index.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`,
},
},
async ({ fs, exec }) => {
await exec('npx @tailwindcss/upgrade')

await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`)
await fs.expectFileToContain(
'src/index.html',
// prettier-ignore
js`
<div class="bg-[var(--my-red)]"></div>
`,
)

let packageJsonContent = await fs.read('package.json')
let packageJson = JSON.parse(packageJsonContent)
expect(packageJson.postcss).toMatchInlineSnapshot(`
{
"plugins": {
"@tailwindcss/postcss": {},
},
}
`)

expect(packageJson.dependencies).toMatchObject({
tailwindcss: expect.stringContaining('4.0.0'),
})
expect(packageJson.dependencies).not.toHaveProperty('autoprefixer')
expect(packageJson.dependencies).not.toHaveProperty('postcss-import')
expect(packageJson.devDependencies).toMatchObject({
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
})
},
)

test(
'migrates a postcss setup using a json based config file',
{
fs: {
'package.json': json`
{
"dependencies": {
"postcss": "^8",
"postcss-cli": "^10",
"postcss-import": "^16",
"autoprefixer": "^10",
"tailwindcss": "^3",
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'.postcssrc.json': json`
{
"plugins": {
"postcss-import": {},
"tailwindcss/nesting": "postcss-nesting",
"tailwindcss": {},
"autoprefixer": {}
}
}
`,
'tailwind.config.js': js`
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{html,js}'],
}
`,
'src/index.html': html`
<div class="bg-[--my-red]"></div>
`,
'src/index.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`,
},
},
async ({ fs, exec }) => {
await exec('npx @tailwindcss/upgrade')

await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`)
await fs.expectFileToContain(
'src/index.html',
// prettier-ignore
js`
<div class="bg-[var(--my-red)]"></div>
`,
)

let jsonConfigContent = await fs.read('.postcssrc.json')
let jsonConfig = JSON.parse(jsonConfigContent)
expect(jsonConfig).toMatchInlineSnapshot(`
{
"plugins": {
"@tailwindcss/postcss": {},
},
}
`)

let packageJsonContent = await fs.read('package.json')
let packageJson = JSON.parse(packageJsonContent)
expect(packageJson.dependencies).toMatchObject({
tailwindcss: expect.stringContaining('4.0.0'),
})
expect(packageJson.dependencies).not.toHaveProperty('autoprefixer')
expect(packageJson.dependencies).not.toHaveProperty('postcss-import')
expect(packageJson.devDependencies).toMatchObject({
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
})
},
)

test(
`migrates prefixes even if other files have unprefixed versions of the candidate`,
{
Expand Down Expand Up @@ -297,7 +531,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', html`
<div class="flex"></div>
Expand Down Expand Up @@ -335,7 +569,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
2 changes: 1 addition & 1 deletion integrations/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function test(
) {
return (only || (!process.env.CI && debug) ? defaultTest.only : defaultTest)(
name,
{ timeout: TEST_TIMEOUT, retry: debug ? 0 : 3 },
{ timeout: TEST_TIMEOUT, retry: debug || only ? 0 : 3 },
async (options) => {
let rootDir = debug ? path.join(REPO_ROOT, '.debug') : TMP_ROOT
await fs.mkdir(rootDir, { recursive: true })
Expand Down
12 changes: 12 additions & 0 deletions packages/@tailwindcss-upgrade/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { globby } from 'globby'
import path from 'node:path'
import { help } from './commands/help'
import { migrate as migrateStylesheet } from './migrate'
import { migratePostCSSConfig } from './migrate-postcss'
import { migrate as migrateTemplate } from './template/migrate'
import { prepareConfig } from './template/prepare-config'
import { args, type Arg } from './utils/args'
import { isRepoDirty } from './utils/git'
import { pkg } from './utils/packages'
import { eprintln, error, header, highlight, info, success } from './utils/renderer'

const options = {
Expand Down Expand Up @@ -98,6 +100,16 @@ async function run() {
success('Stylesheet migration complete.')
}

{
// PostCSS config migration
await migratePostCSSConfig(process.cwd())
}

try {
// Upgrade Tailwind CSS
await pkg('add tailwindcss@next', process.cwd())
} catch {}

// Figure out if we made any changes
if (isRepoDirty()) {
success('Verify the changes and commit them to your repository.')
Expand Down
Loading