From 3f38b8bdd4e679824e1fa6d2dce049f149bd57b9 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Fri, 4 Oct 2024 16:41:35 +0200 Subject: [PATCH 01/12] WIP: add test --- integrations/upgrade/index.test.ts | 67 +++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index c348dc4c4a04..5aae1cedd61e 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -1,4 +1,4 @@ -import { css, html, js, json, test } from '../utils' +import { candidate, css, html, js, json, test } from '../utils' test( `upgrades a v3 project to v4`, @@ -264,3 +264,68 @@ test( ) }, ) + +test.only( + 'migrate a simple postcss setup', + { + fs: { + 'package.json': json` + { + "dependencies": { + "postcss": "^8", + "postcss-cli": "^10", + "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: { + tailwindcss: {}, + autoprefixer: {}, + }, + } + `, + 'src/index.html': html` +
+ `, + 'src/index.css': css` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + }, + }, + async ({ fs, exec }) => { + // Assert that the v3 project works as expected + await exec('pnpm postcss src/index.css --output dist/out.css') + await fs.expectFileToContain('dist/out.css', [candidate`bg-[--my-red]`]) + + await exec('npx @tailwindcss/upgrade') + + await fs.expectFileToContain( + 'src/index.css', + css` + @utility btn { + @apply rounded-md px-2 py-1 bg-blue-500 text-white; + } + + @utility no-scrollbar { + &::-webkit-scrollbar { + display: none; + } + -ms-overflow-style: none; + scrollbar-width: none; + } + `, + ) + }, +) From 16686c232bbe8395cca6bbe32966dea3827d6016 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Mon, 7 Oct 2024 14:36:19 +0200 Subject: [PATCH 02/12] Migrate simple PostCSS setup --- integrations/upgrade/index.test.ts | 49 ++++++---- integrations/utils.ts | 2 +- packages/@tailwindcss-upgrade/src/index.ts | 12 +++ .../src/migrate-postcss.ts | 95 +++++++++++++++++++ .../src/utils/packages.ts | 61 ++++++++++++ 5 files changed, 197 insertions(+), 22 deletions(-) create mode 100644 packages/@tailwindcss-upgrade/src/migrate-postcss.ts create mode 100644 packages/@tailwindcss-upgrade/src/utils/packages.ts diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index 5aae1cedd61e..98610fc2c215 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -1,4 +1,5 @@ -import { candidate, css, html, js, json, test } from '../utils' +import { expect } from 'vitest' +import { css, html, js, json, test } from '../utils' test( `upgrades a v3 project to v4`, @@ -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'), + }) }, ) @@ -265,8 +272,8 @@ test( }, ) -test.only( - 'migrate a simple postcss setup', +test( + 'fully migrate a simple postcss setup', { fs: { 'package.json': json` @@ -305,27 +312,27 @@ test.only( }, }, async ({ fs, exec }) => { - // Assert that the v3 project works as expected - await exec('pnpm postcss src/index.css --output dist/out.css') - await fs.expectFileToContain('dist/out.css', [candidate`bg-[--my-red]`]) - - await exec('npx @tailwindcss/upgrade') - + console.log(await exec('npx @tailwindcss/upgrade -c tailwind.config.js')) await fs.expectFileToContain( - 'src/index.css', - css` - @utility btn { - @apply rounded-md px-2 py-1 bg-blue-500 text-white; - } - - @utility no-scrollbar { - &::-webkit-scrollbar { - display: none; - } - -ms-overflow-style: none; - scrollbar-width: none; + 'postcss.config.js', + js` + module.exports = { + 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.devDependencies).toMatchObject({ + '@tailwindcss/postcss': expect.stringContaining('4.0.0'), + }) }, ) diff --git a/integrations/utils.ts b/integrations/utils.ts index d524d68f2378..7c18e845222f 100644 --- a/integrations/utils.ts +++ b/integrations/utils.ts @@ -74,7 +74,7 @@ export function test( ) { return (only || (!process.env.CI && debug) ? defaultTest.only : defaultTest)( name, - { timeout: TEST_TIMEOUT, retry: 3 }, + { timeout: TEST_TIMEOUT }, async (options) => { let rootDir = debug ? path.join(REPO_ROOT, '.debug') : TMP_ROOT await fs.mkdir(rootDir, { recursive: true }) diff --git a/packages/@tailwindcss-upgrade/src/index.ts b/packages/@tailwindcss-upgrade/src/index.ts index 2d323a24de0b..b6d58083bf10 100644 --- a/packages/@tailwindcss-upgrade/src/index.ts +++ b/packages/@tailwindcss-upgrade/src/index.ts @@ -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 = { @@ -98,6 +100,16 @@ async function run() { success('Stylesheet migration complete.') } + if (parsedConfig) { + // 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.') diff --git a/packages/@tailwindcss-upgrade/src/migrate-postcss.ts b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts new file mode 100644 index 000000000000..a03e159c72db --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts @@ -0,0 +1,95 @@ +import fs from 'node:fs/promises' +import path from 'node:path' +import { pkg } from './utils/packages' +import { info, success, warn } from './utils/renderer' + +// Migrates simple PostCSS setups. This is to cover non-dynamic config files +// similar to the ones we have all over our docs: +// +// ```js +// module.exports = { +// plugins: { +// tailwindcss: {}, +// autoprefixer: {}, +// } +// } +export async function migratePostCSSConfig(base: string) { + let configPath = await detectConfigPath(base) + if (configPath === null) { + // TODO: We can look for an eventual config inside package.json + return + } + + info(`Attempt to upgrade the PostCSS config in file: ${configPath}`) + + let isSimpleConfig = await isSimplePostCSSConfig(base, configPath) + if (!isSimpleConfig) { + warn(`The PostCSS config contains dynamic JavaScript and can not be automatically migrated.`) + return + } + + let didAddPostcssClient = false + let didRemoveAutoprefixer = false + + let fullPath = path.resolve(base, configPath) + let content = await fs.readFile(fullPath, 'utf-8') + let lines = content.split('\n') + let newLines: string[] = [] + for (let line of lines) { + if (line.includes('tailwindcss:')) { + didAddPostcssClient = true + newLines.push(line.replace('tailwindcss:', `'@tailwindcss/postcss':`)) + } else if (line.includes('autoprefixer:')) { + didRemoveAutoprefixer = true + } else { + newLines.push(line) + } + } + await fs.writeFile(fullPath, newLines.join('\n')) + + if (didAddPostcssClient) { + try { + await pkg('add -D @tailwindcss/postcss@next', base) + } catch {} + } + if (didRemoveAutoprefixer) { + try { + await pkg('remove autoprefixer', base) + } catch {} + } + + success(`PostCSS config in file ${configPath} has been upgraded.`) +} + +const CONFIG_FILE_LOCATIONS = [ + '.postcssrc.js', + '.postcssrc.mjs', + '.postcssrc.cjs', + '.postcssrc.ts', + '.postcssrc.mts', + '.postcssrc.cts', + 'postcss.config.js', + 'postcss.config.mjs', + 'postcss.config.cjs', + 'postcss.config.ts', + 'postcss.config.mts', + 'postcss.config.cts', +] +async function detectConfigPath(base: string): Promise { + for (let file of CONFIG_FILE_LOCATIONS) { + let fullPath = path.resolve(base, file) + try { + await fs.access(fullPath) + return file + } catch {} + } + return null +} + +async function isSimplePostCSSConfig(base: string, configPath: string): Promise { + let fullPath = path.resolve(base, configPath) + let content = await fs.readFile(fullPath, 'utf-8') + return ( + content.includes('tailwindcss:') && !(content.includes('require') || content.includes('import')) + ) +} diff --git a/packages/@tailwindcss-upgrade/src/utils/packages.ts b/packages/@tailwindcss-upgrade/src/utils/packages.ts new file mode 100644 index 000000000000..668a83a505ee --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/utils/packages.ts @@ -0,0 +1,61 @@ +import { execSync } from 'node:child_process' +import fs from 'node:fs/promises' +import { dirname, resolve } from 'node:path' +import { warn } from './renderer' + +let didWarnAboutPackageManager = false + +export async function pkg(command: string, base: string): Promise { + let packageManager = await detectPackageManager(base) + if (!packageManager) { + if (!didWarnAboutPackageManager) { + didWarnAboutPackageManager = true + warn('Could not detect a package manager. Please manually update `tailwindcss` to v4.') + } + return + } + return execSync(`${packageManager} ${command}`, { + cwd: base, + }) +} + +async function detectPackageManager(base: string): Promise { + do { + // 1. Check package.json for a `packageManager` field + let packageJsonPath = resolve(base, 'package.json') + try { + let packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8') + let packageJson = JSON.parse(packageJsonContent) + if (packageJson.packageManager) { + if (packageJson.packageManager.includes('yarn')) { + return 'yarn' + } + if (packageJson.packageManager.includes('pnpm')) { + return 'pnpm' + } + if (packageJson.packageManager.includes('npm')) { + return 'npm' + } + } + } catch {} + + // 2. Check for common lockfiles + try { + await fs.access(resolve(base, 'pnpm-lock.yaml')) + return 'pnpm' + } catch {} + + try { + await fs.access(resolve(base, 'yarn.lock')) + return 'yarn' + } catch {} + + try { + await fs.access(resolve(base, 'package-lock.json')) + return 'npm' + } catch {} + + // 3. If no lockfile is found, we might be in a monorepo + base = dirname(base) + } while (true) +} From 3d556dfb146e6ea35a285a6a7bd1b012b226c526 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Mon, 7 Oct 2024 14:54:02 +0200 Subject: [PATCH 03/12] Add change log --- CHANGELOG.md | 2 ++ integrations/upgrade/index.test.ts | 12 ++++++++++-- integrations/utils.ts | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6074ecce90b3..7f255f3c5148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ 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)) - _Experimental_: The upgrade tool now automatically discovers your JavaScript config ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597)) +- Support `keyframes` in JS config file themes ([14594](https://github.com/tailwindlabs/tailwindcss/pull/14594)) +- _Experimental_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612)) ### Fixed diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index 98610fc2c215..f5ff9c4dcdb9 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -312,7 +312,8 @@ test( }, }, async ({ fs, exec }) => { - console.log(await exec('npx @tailwindcss/upgrade -c tailwind.config.js')) + await exec('npx @tailwindcss/upgrade -c tailwind.config.js') + await fs.expectFileToContain( 'postcss.config.js', js` @@ -323,10 +324,17 @@ test( } `, ) + await fs.expectFileToContain('src/index.css', css` @import 'tailwindcss' prefix(tw); `) + await fs.expectFileToContain( + 'src/index.html', + // prettier-ignore + js` +
+ `, + ) let packageJsonContent = await fs.read('package.json') let packageJson = JSON.parse(packageJsonContent) - expect(packageJson.dependencies).toMatchObject({ tailwindcss: expect.stringContaining('4.0.0'), }) diff --git a/integrations/utils.ts b/integrations/utils.ts index 7c18e845222f..d524d68f2378 100644 --- a/integrations/utils.ts +++ b/integrations/utils.ts @@ -74,7 +74,7 @@ export function test( ) { return (only || (!process.env.CI && debug) ? defaultTest.only : defaultTest)( name, - { timeout: TEST_TIMEOUT }, + { timeout: TEST_TIMEOUT, retry: 3 }, async (options) => { let rootDir = debug ? path.join(REPO_ROOT, '.debug') : TMP_ROOT await fs.mkdir(rootDir, { recursive: true }) From 21030c0cc35edfaa27c4567c6b0b57db909675cf Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Mon, 7 Oct 2024 18:06:59 +0200 Subject: [PATCH 04/12] Add bun support Co-authored-by: Robin Malfait --- packages/@tailwindcss-upgrade/src/utils/packages.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/@tailwindcss-upgrade/src/utils/packages.ts b/packages/@tailwindcss-upgrade/src/utils/packages.ts index 668a83a505ee..837816b4ccc8 100644 --- a/packages/@tailwindcss-upgrade/src/utils/packages.ts +++ b/packages/@tailwindcss-upgrade/src/utils/packages.ts @@ -27,6 +27,9 @@ async function detectPackageManager(base: string): Promise { let packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8') let packageJson = JSON.parse(packageJsonContent) if (packageJson.packageManager) { + if (packageJson.packageManager.includes('bun')) { + return 'bun' + } if (packageJson.packageManager.includes('yarn')) { return 'yarn' } @@ -40,6 +43,14 @@ async function detectPackageManager(base: string): Promise { } catch {} // 2. Check for common lockfiles + try { + await fs.access(resolve(base, 'bun.lockb')) + return 'bun' + } catch {} + try { + await fs.access(resolve(base, 'bun.lock')) + return 'bun' + } catch {} try { await fs.access(resolve(base, 'pnpm-lock.yaml')) return 'pnpm' From 970622917c72fe15d0470513d66a4eec4b40a1b5 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Mon, 7 Oct 2024 18:08:07 +0200 Subject: [PATCH 05/12] Update snapshot --- integrations/upgrade/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index f5ff9c4dcdb9..1841e8b8e212 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -324,7 +324,7 @@ test( } `, ) - await fs.expectFileToContain('src/index.css', css` @import 'tailwindcss' prefix(tw); `) + await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`) await fs.expectFileToContain( 'src/index.html', // prettier-ignore From 7494dd8d3832bdcbf6b016f562f58735841bf7c8 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Mon, 7 Oct 2024 18:09:45 +0200 Subject: [PATCH 06/12] Update change log --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f255f3c5148..408142883753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ 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)) - _Experimental_: The upgrade tool now automatically discovers your JavaScript config ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597)) -- Support `keyframes` in JS config file themes ([14594](https://github.com/tailwindlabs/tailwindcss/pull/14594)) - _Experimental_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612)) ### Fixed From 6ddab3ff159d1e804aa124d27e6a0de7a05fda24 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Tue, 8 Oct 2024 12:03:19 +0200 Subject: [PATCH 07/12] Fix typescript issue --- packages/@tailwindcss-upgrade/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@tailwindcss-upgrade/src/index.ts b/packages/@tailwindcss-upgrade/src/index.ts index b6d58083bf10..eeae17edf8d0 100644 --- a/packages/@tailwindcss-upgrade/src/index.ts +++ b/packages/@tailwindcss-upgrade/src/index.ts @@ -100,7 +100,7 @@ async function run() { success('Stylesheet migration complete.') } - if (parsedConfig) { + { // PostCSS config migration await migratePostCSSConfig(process.cwd()) } From acdb351b6e2bf2aa3ec41ee3248637095e0cf306 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Wed, 9 Oct 2024 15:57:03 +0200 Subject: [PATCH 08/12] Handle postcss-import and tailwindcss/nesting removal --- integrations/upgrade/index.test.ts | 12 ++- integrations/utils.ts | 2 +- .../src/migrate-postcss.ts | 76 +++++++++++++++++-- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index cfa42b6f2401..e5e30b1f3313 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -273,7 +273,7 @@ test( ) test( - 'fully migrate a simple postcss setup', + 'migrates a simple postcss setup', { fs: { 'package.json': json` @@ -281,6 +281,7 @@ test( "dependencies": { "postcss": "^8", "postcss-cli": "^10", + "postcss-import": "^16", "autoprefixer": "^10", "tailwindcss": "^3", "@tailwindcss/upgrade": "workspace:^" @@ -296,6 +297,8 @@ test( 'postcss.config.js': js` module.exports = { plugins: { + 'postcss-import': {}, + 'tailwindcss/nesting': 'postcss-nesting', tailwindcss: {}, autoprefixer: {}, }, @@ -312,7 +315,7 @@ test( }, }, async ({ fs, exec }) => { - await exec('npx @tailwindcss/upgrade -c tailwind.config.js') + await exec('npx @tailwindcss/upgrade') await fs.expectFileToContain( 'postcss.config.js', @@ -339,6 +342,7 @@ test( 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'), }) @@ -377,7 +381,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`
@@ -415,7 +419,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', diff --git a/integrations/utils.ts b/integrations/utils.ts index e5f3311580fb..fcea2a0ff235 100644 --- a/integrations/utils.ts +++ b/integrations/utils.ts @@ -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 }) diff --git a/packages/@tailwindcss-upgrade/src/migrate-postcss.ts b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts index a03e159c72db..71640de9b587 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-postcss.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts @@ -8,10 +8,12 @@ import { info, success, warn } from './utils/renderer' // // ```js // module.exports = { -// plugins: { -// tailwindcss: {}, -// autoprefixer: {}, -// } +// plugins: { +// 'postcss-import': {}, +// 'tailwindcss/nesting': 'postcss-nesting', +// tailwindcss: {}, +// autoprefixer: {}, +// } // } export async function migratePostCSSConfig(base: string) { let configPath = await detectConfigPath(base) @@ -30,17 +32,49 @@ export async function migratePostCSSConfig(base: string) { let didAddPostcssClient = false let didRemoveAutoprefixer = false + let didRemovePostCSSImport = false let fullPath = path.resolve(base, configPath) let content = await fs.readFile(fullPath, 'utf-8') let lines = content.split('\n') let newLines: string[] = [] - for (let line of lines) { - if (line.includes('tailwindcss:')) { + for (let i = 0; i < lines.length; i++) { + let line = lines[i] + + if (isTailwindCSSPlugin(line)) { didAddPostcssClient = true newLines.push(line.replace('tailwindcss:', `'@tailwindcss/postcss':`)) - } else if (line.includes('autoprefixer:')) { + } else if (isAutoprefixerPlugin(line)) { didRemoveAutoprefixer = true + } else if (isPostCSSImportPlugin(line)) { + // Check that there are no unknown plugins before the tailwindcss plugin + let hasUnknownPluginsBeforeTailwindCSS = false + for (let j = i + 1; j < lines.length; j++) { + let nextLine = lines[j] + if (isTailwindCSSPlugin(nextLine)) { + break + } + if (isTailwindCSSNestingPlugin(nextLine)) { + continue + } + hasUnknownPluginsBeforeTailwindCSS = true + break + } + + if (!hasUnknownPluginsBeforeTailwindCSS) { + didRemovePostCSSImport = true + } else { + newLines.push(line) + } + } else if (isTailwindCSSNestingPlugin(line)) { + // Check if the following rule is the tailwindcss plugin + let nextLine = lines[i + 1] + if (isTailwindCSSPlugin(nextLine)) { + // Since this plugin is bundled with `tailwindcss`, we don't need to + // clean up a package when deleting this line. + } else { + newLines.push(line) + } } else { newLines.push(line) } @@ -57,6 +91,11 @@ export async function migratePostCSSConfig(base: string) { await pkg('remove autoprefixer', base) } catch {} } + if (didRemovePostCSSImport) { + try { + await pkg('remove postcss-import', base) + } catch {} + } success(`PostCSS config in file ${configPath} has been upgraded.`) } @@ -90,6 +129,27 @@ async function isSimplePostCSSConfig(base: string, configPath: string): Promise< let fullPath = path.resolve(base, configPath) let content = await fs.readFile(fullPath, 'utf-8') return ( - content.includes('tailwindcss:') && !(content.includes('require') || content.includes('import')) + content.includes('tailwindcss:') && + !( + content.includes('require') || + // Adding a space at the end to not match `'postcss-import'` + content.includes('import ') + ) ) } + +function isTailwindCSSPlugin(line: string) { + return /['"]?tailwindcss['"]?\: ?\{\}/.test(line) +} + +function isPostCSSImportPlugin(line: string) { + return /['"]?postcss-import['"]?\: ?\{\}/.test(line) +} + +function isAutoprefixerPlugin(line: string) { + return /['"]?autoprefixer['"]?\: ?\{\}/.test(line) +} + +function isTailwindCSSNestingPlugin(line: string) { + return /['"]tailwindcss\/nesting['"]\: ?(\{\}|['"]postcss-nesting['"])/.test(line) +} From 606957720d895221de4fbf278cb4fb84d264dc4a Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Wed, 9 Oct 2024 16:29:00 +0200 Subject: [PATCH 09/12] Handle package.json PostCSS config --- integrations/upgrade/index.test.ts | 73 +++++++ .../src/migrate-postcss.ts | 196 +++++++++++++++--- 2 files changed, 237 insertions(+), 32 deletions(-) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index e5e30b1f3313..3b7297d64f96 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -349,6 +349,79 @@ test( }, ) +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` +
+ `, + 'src/index.css': css` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + }, + }, + async ({ fs, exec }) => { + console.log(await exec('npx @tailwindcss/upgrade')) + + await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`) + await fs.expectFileToContain( + 'src/index.html', + // prettier-ignore + js` +
+ `, + ) + + 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 prefixes even if other files have unprefixed versions of the candidate`, { diff --git a/packages/@tailwindcss-upgrade/src/migrate-postcss.ts b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts index 71640de9b587..fa14e1b4ed01 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-postcss.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts @@ -16,18 +16,91 @@ import { info, success, warn } from './utils/renderer' // } // } export async function migratePostCSSConfig(base: string) { - let configPath = await detectConfigPath(base) - if (configPath === null) { - // TODO: We can look for an eventual config inside package.json + let didMigrate = false + let didAddPostcssClient = false + let didRemoveAutoprefixer = false + let didRemovePostCSSImport = false + + // Priority 1: Handle JS config files + let jsConfigPath = await detectJSConfigPath(base) + if (jsConfigPath) { + let result = await migratePostCSSJSConfig(base, jsConfigPath) + + if (result) { + didMigrate = true + didAddPostcssClient = result.didAddPostcssClient + didRemoveAutoprefixer = result.didRemoveAutoprefixer + didRemovePostCSSImport = result.didRemovePostCSSImport + } + } + + // Priority 2: Handle package.json config + let packageJson + try { + packageJson = JSON.parse(await fs.readFile(path.resolve(base, 'package.json'), 'utf-8')) + } catch {} + if (!didMigrate && packageJson && 'postcss' in packageJson) { + let result = await migratePostCSSPackageJsonConfig(base, packageJson) + + if (result) { + didMigrate = true + didAddPostcssClient = result.didAddPostcssClient + didRemoveAutoprefixer = result.didRemoveAutoprefixer + didRemovePostCSSImport = result.didRemovePostCSSImport + } + } + + if (!didMigrate) { + info(`No PostCSS config found, skipping migration.`) return } + if (didAddPostcssClient) { + try { + await pkg('add -D @tailwindcss/postcss@next', base) + } catch {} + } + if (didRemoveAutoprefixer) { + try { + await pkg('remove autoprefixer', base) + } catch {} + } + if (didRemovePostCSSImport) { + try { + await pkg('remove postcss-import', base) + } catch {} + } + + success(`PostCSS config has been upgraded.`) +} + +async function migratePostCSSJSConfig( + base: string, + configPath: string, +): Promise<{ + didAddPostcssClient: boolean + didRemoveAutoprefixer: boolean + didRemovePostCSSImport: boolean +} | null> { + function isTailwindCSSPlugin(line: string) { + return /['"]?tailwindcss['"]?\: ?\{\}/.test(line) + } + function isPostCSSImportPlugin(line: string) { + return /['"]?postcss-import['"]?\: ?\{\}/.test(line) + } + function isAutoprefixerPlugin(line: string) { + return /['"]?autoprefixer['"]?\: ?\{\}/.test(line) + } + function isTailwindCSSNestingPlugin(line: string) { + return /['"]tailwindcss\/nesting['"]\: ?(\{\}|['"]postcss-nesting['"])/.test(line) + } + info(`Attempt to upgrade the PostCSS config in file: ${configPath}`) let isSimpleConfig = await isSimplePostCSSConfig(base, configPath) if (!isSimpleConfig) { warn(`The PostCSS config contains dynamic JavaScript and can not be automatically migrated.`) - return + return null } let didAddPostcssClient = false @@ -81,23 +154,94 @@ export async function migratePostCSSConfig(base: string) { } await fs.writeFile(fullPath, newLines.join('\n')) - if (didAddPostcssClient) { - try { - await pkg('add -D @tailwindcss/postcss@next', base) - } catch {} + return { didAddPostcssClient, didRemoveAutoprefixer, didRemovePostCSSImport } +} + +async function migratePostCSSPackageJsonConfig( + base: string, + packageJson: any, +): Promise<{ + didAddPostcssClient: boolean + didRemoveAutoprefixer: boolean + didRemovePostCSSImport: boolean +} | null> { + function isTailwindCSSPlugin(plugin: string, options: any) { + return plugin === 'tailwindcss' && isEmptyObject(options) } - if (didRemoveAutoprefixer) { - try { - await pkg('remove autoprefixer', base) - } catch {} + function isPostCSSImportPlugin(plugin: string, options: any) { + return plugin === 'postcss-import' && isEmptyObject(options) } - if (didRemovePostCSSImport) { - try { - await pkg('remove postcss-import', base) - } catch {} + function isAutoprefixerPlugin(plugin: string, options: any) { + return plugin === 'autoprefixer' && isEmptyObject(options) + } + function isTailwindCSSNestingPlugin(plugin: string, options: any) { + return ( + plugin === 'tailwindcss/nesting' && (options === 'postcss-nesting' || isEmptyObject(options)) + ) + } + + let postcss = packageJson.postcss + + let didAddPostcssClient = false + let didRemoveAutoprefixer = false + let didRemovePostCSSImport = false + + let plugins = Object.entries(postcss.plugins || {}) + + let newPlugins: [string, any][] = [] + for (let i = 0; i < plugins.length; i++) { + let [plugin, options] = plugins[i] + + if (isTailwindCSSPlugin(plugin, options)) { + didAddPostcssClient = true + newPlugins.push(['@tailwindcss/postcss', options]) + } else if (isAutoprefixerPlugin(plugin, options)) { + didRemoveAutoprefixer = true + } else if (isPostCSSImportPlugin(plugin, options)) { + // Check that there are no unknown plugins before the tailwindcss plugin + let hasUnknownPluginsBeforeTailwindCSS = false + for (let j = i + 1; j < plugins.length; j++) { + let [nextPlugin, nextOptions] = plugins[j] + if (isTailwindCSSPlugin(nextPlugin, nextOptions)) { + break + } + if (isTailwindCSSNestingPlugin(nextPlugin, nextOptions)) { + continue + } + hasUnknownPluginsBeforeTailwindCSS = true + break + } + + if (!hasUnknownPluginsBeforeTailwindCSS) { + didRemovePostCSSImport = true + } else { + newPlugins.push([plugin, options]) + } + } else if (isTailwindCSSNestingPlugin(plugin, options)) { + // Check if the following rule is the tailwindcss plugin + let [nextPlugin, nextOptions] = plugins[i + 1] + if (isTailwindCSSPlugin(nextPlugin, nextOptions)) { + // Since this plugin is bundled with `tailwindcss`, we don't need to + // clean up a package when deleting this line. + } else { + newPlugins.push([plugin, options]) + } + } else { + newPlugins.push([plugin, options]) + } } - success(`PostCSS config in file ${configPath} has been upgraded.`) + let fullPath = path.resolve(base, 'package.json') + await fs.writeFile( + fullPath, + JSON.stringify( + { ...packageJson, postcss: { ...postcss, plugins: Object.fromEntries(newPlugins) } }, + null, + 2, + ), + ) + + return { didAddPostcssClient, didRemoveAutoprefixer, didRemovePostCSSImport } } const CONFIG_FILE_LOCATIONS = [ @@ -114,7 +258,7 @@ const CONFIG_FILE_LOCATIONS = [ 'postcss.config.mts', 'postcss.config.cts', ] -async function detectConfigPath(base: string): Promise { +async function detectJSConfigPath(base: string): Promise { for (let file of CONFIG_FILE_LOCATIONS) { let fullPath = path.resolve(base, file) try { @@ -138,18 +282,6 @@ async function isSimplePostCSSConfig(base: string, configPath: string): Promise< ) } -function isTailwindCSSPlugin(line: string) { - return /['"]?tailwindcss['"]?\: ?\{\}/.test(line) -} - -function isPostCSSImportPlugin(line: string) { - return /['"]?postcss-import['"]?\: ?\{\}/.test(line) -} - -function isAutoprefixerPlugin(line: string) { - return /['"]?autoprefixer['"]?\: ?\{\}/.test(line) -} - -function isTailwindCSSNestingPlugin(line: string) { - return /['"]tailwindcss\/nesting['"]\: ?(\{\}|['"]postcss-nesting['"])/.test(line) +function isEmptyObject(obj: any) { + return typeof obj === 'object' && obj !== null && Object.keys(obj).length === 0 } From e5b2666b7f7c0dbf20ec25a0db6a3ec46e287f43 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Wed, 9 Oct 2024 18:33:57 +0200 Subject: [PATCH 10/12] Update Changelog --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9912e5a1072..7ad5006a7d37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +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)) -- _Experimental_: The upgrade tool now automatically discovers your JavaScript config ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597)) -- _Experimental_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612)) +- _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 From bbb0a8ea89d9461f4daee0ee99c373e050806940 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Thu, 10 Oct 2024 11:45:21 +0200 Subject: [PATCH 11/12] Add support for JSON based config files --- integrations/upgrade/index.test.ts | 75 +++++++++++++++ .../src/migrate-postcss.ts | 93 +++++++++++++------ 2 files changed, 141 insertions(+), 27 deletions(-) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index 3b7297d64f96..f710c7dec1a2 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -422,6 +422,81 @@ test( }, ) +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` +
+ `, + 'src/index.css': css` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + }, + }, + async ({ fs, exec }) => { + console.log(await exec('npx @tailwindcss/upgrade')) + + await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`) + await fs.expectFileToContain( + 'src/index.html', + // prettier-ignore + js` +
+ `, + ) + + let packageJsonContent = await fs.read('.postcssrc.json') + let packageJson = JSON.parse(packageJsonContent) + expect(packageJson).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 prefixes even if other files have unprefixed versions of the candidate`, { diff --git a/packages/@tailwindcss-upgrade/src/migrate-postcss.ts b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts index fa14e1b4ed01..fc57e18175a1 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-postcss.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts @@ -35,14 +35,20 @@ export async function migratePostCSSConfig(base: string) { } // Priority 2: Handle package.json config + let packageJsonPath = path.resolve(base, 'package.json') let packageJson try { - packageJson = JSON.parse(await fs.readFile(path.resolve(base, 'package.json'), 'utf-8')) + packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8')) } catch {} if (!didMigrate && packageJson && 'postcss' in packageJson) { - let result = await migratePostCSSPackageJsonConfig(base, packageJson) + let result = await migratePostCSSJsonConfig(base, packageJson.postcss) if (result) { + await fs.writeFile( + packageJsonPath, + JSON.stringify({ ...packageJson, postcss: result?.json }, null, 2), + ) + didMigrate = true didAddPostcssClient = result.didAddPostcssClient didRemoveAutoprefixer = result.didRemoveAutoprefixer @@ -50,6 +56,27 @@ export async function migratePostCSSConfig(base: string) { } } + // Priority 3: JSON based postcss config files + let jsonConfigPath = await detectJSONConfigPath(base) + let jsonConfig: null | any = null + if (jsonConfigPath) { + try { + jsonConfig = JSON.parse(await fs.readFile(jsonConfigPath, 'utf-8')) + } catch {} + if (jsonConfig) { + let result = await migratePostCSSJsonConfig(base, jsonConfig) + + if (result) { + await fs.writeFile(jsonConfigPath, JSON.stringify(result.json, null, 2)) + + didMigrate = true + didAddPostcssClient = result.didAddPostcssClient + didRemoveAutoprefixer = result.didRemoveAutoprefixer + didRemovePostCSSImport = result.didRemovePostCSSImport + } + } + } + if (!didMigrate) { info(`No PostCSS config found, skipping migration.`) return @@ -60,14 +87,15 @@ export async function migratePostCSSConfig(base: string) { await pkg('add -D @tailwindcss/postcss@next', base) } catch {} } - if (didRemoveAutoprefixer) { + if (didRemoveAutoprefixer || didRemovePostCSSImport) { try { - await pkg('remove autoprefixer', base) - } catch {} - } - if (didRemovePostCSSImport) { - try { - await pkg('remove postcss-import', base) + let packagesToRemove = [ + didRemoveAutoprefixer ? 'autoprefixer' : null, + didRemovePostCSSImport ? 'postcss-import' : null, + ] + .filter(Boolean) + .join(' ') + await pkg(`remove ${packagesToRemove}`, base) } catch {} } @@ -157,10 +185,11 @@ async function migratePostCSSJSConfig( return { didAddPostcssClient, didRemoveAutoprefixer, didRemovePostCSSImport } } -async function migratePostCSSPackageJsonConfig( +async function migratePostCSSJsonConfig( base: string, - packageJson: any, + json: any, ): Promise<{ + json: any didAddPostcssClient: boolean didRemoveAutoprefixer: boolean didRemovePostCSSImport: boolean @@ -180,13 +209,11 @@ async function migratePostCSSPackageJsonConfig( ) } - let postcss = packageJson.postcss - let didAddPostcssClient = false let didRemoveAutoprefixer = false let didRemovePostCSSImport = false - let plugins = Object.entries(postcss.plugins || {}) + let plugins = Object.entries(json.plugins || {}) let newPlugins: [string, any][] = [] for (let i = 0; i < plugins.length; i++) { @@ -231,20 +258,15 @@ async function migratePostCSSPackageJsonConfig( } } - let fullPath = path.resolve(base, 'package.json') - await fs.writeFile( - fullPath, - JSON.stringify( - { ...packageJson, postcss: { ...postcss, plugins: Object.fromEntries(newPlugins) } }, - null, - 2, - ), - ) - - return { didAddPostcssClient, didRemoveAutoprefixer, didRemovePostCSSImport } + return { + json: { ...json, plugins: Object.fromEntries(newPlugins) }, + didAddPostcssClient, + didRemoveAutoprefixer, + didRemovePostCSSImport, + } } -const CONFIG_FILE_LOCATIONS = [ +const JS_CONFIG_FILE_LOCATIONS = [ '.postcssrc.js', '.postcssrc.mjs', '.postcssrc.cjs', @@ -259,7 +281,24 @@ const CONFIG_FILE_LOCATIONS = [ 'postcss.config.cts', ] async function detectJSConfigPath(base: string): Promise { - for (let file of CONFIG_FILE_LOCATIONS) { + for (let file of JS_CONFIG_FILE_LOCATIONS) { + let fullPath = path.resolve(base, file) + try { + await fs.access(fullPath) + return file + } catch {} + } + return null +} + +const JSON_CONFIG_FILE_LOCATIONS = [ + '.postcssrc', + '.postcssrc.json', + // yaml syntax is not supported + // '.postcssrc.yml' +] +async function detectJSONConfigPath(base: string): Promise { + for (let file of JSON_CONFIG_FILE_LOCATIONS) { let fullPath = path.resolve(base, file) try { await fs.access(fullPath) From ffd741717e15e89fa0625d17f231f2797494a75f Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Thu, 10 Oct 2024 12:31:18 +0200 Subject: [PATCH 12/12] Fix test --- integrations/upgrade/index.test.ts | 12 +++++++----- packages/@tailwindcss-upgrade/src/migrate-postcss.ts | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index f710c7dec1a2..37c327faebe2 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -390,7 +390,7 @@ test( }, }, async ({ fs, exec }) => { - console.log(await exec('npx @tailwindcss/upgrade')) + await exec('npx @tailwindcss/upgrade') await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`) await fs.expectFileToContain( @@ -465,7 +465,7 @@ test( }, }, async ({ fs, exec }) => { - console.log(await exec('npx @tailwindcss/upgrade')) + await exec('npx @tailwindcss/upgrade') await fs.expectFileToContain('src/index.css', css`@import 'tailwindcss';`) await fs.expectFileToContain( @@ -476,9 +476,9 @@ test( `, ) - let packageJsonContent = await fs.read('.postcssrc.json') - let packageJson = JSON.parse(packageJsonContent) - expect(packageJson).toMatchInlineSnapshot(` + let jsonConfigContent = await fs.read('.postcssrc.json') + let jsonConfig = JSON.parse(jsonConfigContent) + expect(jsonConfig).toMatchInlineSnapshot(` { "plugins": { "@tailwindcss/postcss": {}, @@ -486,6 +486,8 @@ test( } `) + let packageJsonContent = await fs.read('package.json') + let packageJson = JSON.parse(packageJsonContent) expect(packageJson.dependencies).toMatchObject({ tailwindcss: expect.stringContaining('4.0.0'), }) diff --git a/packages/@tailwindcss-upgrade/src/migrate-postcss.ts b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts index fc57e18175a1..2b383425d2fd 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-postcss.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts @@ -59,7 +59,7 @@ export async function migratePostCSSConfig(base: string) { // Priority 3: JSON based postcss config files let jsonConfigPath = await detectJSONConfigPath(base) let jsonConfig: null | any = null - if (jsonConfigPath) { + if (!didMigrate && jsonConfigPath) { try { jsonConfig = JSON.parse(await fs.readFile(jsonConfigPath, 'utf-8')) } catch {}