Install @tailwindcss/postcss next to tailwindcss (#14830)
This PR improves the PostCSS migrations to make sure that we install `@tailwindcss/postcss` in the same bucket as `tailwindcss`. If `tailwindcss` exists in the `dependencies` bucket, we install `@tailwindcss/postcss` in the same bucket. If `tailwindcss` exists in the `devDependencies` bucket, we install `@tailwindcss/postcss` in the same bucket. This also contains an internal refactor that normalizes the package manager to make sure we can install a package to the correct bucket depending on the package manager. --------- Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This commit is contained in:
parent
840c9e65b9
commit
eb54dcdbfc
6 changed files with 150 additions and 27 deletions
|
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
|
||||
- Detect classes in new files when using `@tailwindcss/postcss` ([#14829](https://github.com/tailwindlabs/tailwindcss/pull/14829))
|
||||
- _Upgrade (experimental)_: Install `@tailwindcss/postcss` next to `tailwindcss` ([#14830](https://github.com/tailwindlabs/tailwindcss/pull/14830))
|
||||
|
||||
## [4.0.0-alpha.31] - 2024-10-29
|
||||
|
||||
|
|
|
|||
|
|
@ -544,6 +544,100 @@ test(
|
|||
})
|
||||
expect(packageJson.dependencies).not.toHaveProperty('autoprefixer')
|
||||
expect(packageJson.dependencies).not.toHaveProperty('postcss-import')
|
||||
expect(packageJson.dependencies).toMatchObject({
|
||||
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'`@tailwindcss/postcss` should be installed in dependencies when `tailwindcss` exists in dependencies',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"postcss": "^8",
|
||||
"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: {},
|
||||
},
|
||||
}
|
||||
`,
|
||||
'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')
|
||||
|
||||
let packageJsonContent = await fs.read('package.json')
|
||||
let packageJson = JSON.parse(packageJsonContent)
|
||||
expect(packageJson.dependencies).toMatchObject({
|
||||
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'`@tailwindcss/postcss` should be installed in devDependencies when `tailwindcss` exists in dev dependencies',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"devDependencies": {
|
||||
"postcss": "^8",
|
||||
"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: {},
|
||||
},
|
||||
}
|
||||
`,
|
||||
'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')
|
||||
|
||||
let packageJsonContent = await fs.read('package.json')
|
||||
let packageJson = JSON.parse(packageJsonContent)
|
||||
expect(packageJson.devDependencies).toMatchObject({
|
||||
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
|
||||
})
|
||||
|
|
@ -617,7 +711,7 @@ test(
|
|||
})
|
||||
expect(packageJson.dependencies).not.toHaveProperty('autoprefixer')
|
||||
expect(packageJson.dependencies).not.toHaveProperty('postcss-import')
|
||||
expect(packageJson.devDependencies).toMatchObject({
|
||||
expect(packageJson.dependencies).toMatchObject({
|
||||
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
|
||||
})
|
||||
},
|
||||
|
|
@ -694,7 +788,7 @@ test(
|
|||
})
|
||||
expect(packageJson.dependencies).not.toHaveProperty('autoprefixer')
|
||||
expect(packageJson.dependencies).not.toHaveProperty('postcss-import')
|
||||
expect(packageJson.devDependencies).toMatchObject({
|
||||
expect(packageJson.dependencies).toMatchObject({
|
||||
'@tailwindcss/postcss': expect.stringContaining('4.0.0'),
|
||||
})
|
||||
},
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ async function run() {
|
|||
|
||||
try {
|
||||
// Upgrade Tailwind CSS
|
||||
await pkg('add tailwindcss@next', base)
|
||||
await pkg(base).add(['tailwindcss@next'])
|
||||
} catch {}
|
||||
|
||||
// Remove the JS config if it was fully migrated
|
||||
|
|
|
|||
|
|
@ -83,19 +83,25 @@ export async function migratePostCSSConfig(base: string) {
|
|||
}
|
||||
|
||||
if (didAddPostcssClient) {
|
||||
try {
|
||||
await pkg('add -D @tailwindcss/postcss@next', base)
|
||||
} catch {}
|
||||
let location = Object.hasOwn(packageJson?.dependencies ?? {}, 'tailwindcss')
|
||||
? ('dependencies' as const)
|
||||
: Object.hasOwn(packageJson?.devDependencies ?? {}, 'tailwindcss')
|
||||
? ('devDependencies' as const)
|
||||
: null
|
||||
|
||||
if (location !== null) {
|
||||
try {
|
||||
await pkg(base).add(['@tailwindcss/postcss@next'], location)
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
if (didRemoveAutoprefixer || didRemovePostCSSImport) {
|
||||
try {
|
||||
let packagesToRemove = [
|
||||
didRemoveAutoprefixer ? 'autoprefixer' : null,
|
||||
didRemovePostCSSImport ? 'postcss-import' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
await pkg(`remove ${packagesToRemove}`, base)
|
||||
].filter(Boolean) as string[]
|
||||
await pkg(base).remove(packagesToRemove)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export async function migratePrettierPlugin(base: string) {
|
|||
try {
|
||||
let packageJson = await fs.readFile(packageJsonPath, 'utf-8')
|
||||
if (packageJson.includes('prettier-plugin-tailwindcss')) {
|
||||
await pkg('add prettier-plugin-tailwindcss@latest', base)
|
||||
await pkg(base).add(['prettier-plugin-tailwindcss@latest'])
|
||||
success(`Prettier plugin migrated to latest version.`)
|
||||
}
|
||||
} catch {}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,36 @@
|
|||
import { execSync } from 'node:child_process'
|
||||
import { exec as execCb } from 'node:child_process'
|
||||
import fs from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { promisify } from 'node:util'
|
||||
import { DefaultMap } from '../../../tailwindcss/src/utils/default-map'
|
||||
import { warn } from './renderer'
|
||||
|
||||
let didWarnAboutPackageManager = false
|
||||
const exec = promisify(execCb)
|
||||
|
||||
export async function pkg(command: string, base: string): Promise<Buffer | void> {
|
||||
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,
|
||||
})
|
||||
const SAVE_DEV: Record<string, string> = {
|
||||
default: '-D',
|
||||
bun: '-d',
|
||||
}
|
||||
|
||||
async function detectPackageManager(base: string): Promise<null | string> {
|
||||
export function pkg(base: string) {
|
||||
return {
|
||||
async add(packages: string[], location: 'dependencies' | 'devDependencies' = 'dependencies') {
|
||||
let packageManager = await packageManagerForBase.get(base)
|
||||
let args = packages.slice()
|
||||
if (location === 'devDependencies') {
|
||||
args.push(SAVE_DEV[packageManager] || SAVE_DEV.default)
|
||||
}
|
||||
return exec(`${packageManager} add ${args.join(' ')}`, { cwd: base })
|
||||
},
|
||||
async remove(packages: string[]) {
|
||||
let packageManager = await packageManagerForBase.get(base)
|
||||
return exec(`${packageManager} remove ${packages.join(' ')}`, { cwd: base })
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let didWarnAboutPackageManager = false
|
||||
let packageManagerForBase = new DefaultMap(async (base) => {
|
||||
do {
|
||||
// 1. Check package.json for a `packageManager` field
|
||||
let packageJsonPath = resolve(base, 'package.json')
|
||||
|
|
@ -67,6 +78,17 @@ async function detectPackageManager(base: string): Promise<null | string> {
|
|||
} catch {}
|
||||
|
||||
// 3. If no lockfile is found, we might be in a monorepo
|
||||
let previousBase = base
|
||||
base = dirname(base)
|
||||
|
||||
// Already at the root
|
||||
if (previousBase === base) {
|
||||
if (!didWarnAboutPackageManager) {
|
||||
didWarnAboutPackageManager = true
|
||||
warn('Could not detect a package manager. Please manually update `tailwindcss` to v4.')
|
||||
}
|
||||
|
||||
return Promise.reject('No package manager detected')
|
||||
}
|
||||
} while (true)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue