Register migrateImport to ensure it actually runs (#14769)

This PR makes sure the `migrateImport` codemod is properly registered so
that it runs as part of the upgrade process.

## Test plan

This PR adds a new `v3` playground with an `upgrade` script that you can
use to run the upgrade from the local package. When you add a
non-prefixed `@import` to the v3 example, the paths are now properly
updated with no errors logged:


https://github.com/user-attachments/assets/85949bbb-756b-4ee2-8ac0-234fe1b2ca39

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This commit is contained in:
Adam Wathan 2024-10-24 11:00:25 -04:00 committed by GitHub
parent d643d79f4b
commit 39cfcfa427
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 1143 additions and 78 deletions

View File

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Ensure individual logical property utilities are sorted later than left/right pair utilities ([#14777](https://github.com/tailwindlabs/tailwindcss/pull/14777))
- _Upgrade (experimental)_: Ensure `@import` statements for relative CSS files are actually migrated to use relative path syntax ([#14769](https://github.com/tailwindlabs/tailwindcss/pull/14769))
## [4.0.0-alpha.29] - 2024-10-23

View File

@ -1775,6 +1775,7 @@ test(
/* Inject missing @config due to nested imports with tailwind imports */
@import './root.4/base.css';
@import './root.4/utilities.css';
@config '../tailwind.config.ts';
/*
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
@ -1808,7 +1809,6 @@ test(
border-width: 0;
}
}
@config '../tailwind.config.ts';
--- ./src/root.5.css ---
@import './root.5/tailwind.css';
@ -1963,3 +1963,80 @@ test(
`)
},
)
test(
'relative imports without a relative path prefix are migrated to include a relative path prefix',
{
fs: {
'package.json': json`
{
"dependencies": {
"tailwindcss": "workspace:^",
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.js': js`module.exports = {}`,
'src/index.css': css`
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'styles/components';
@import 'tailwindcss/utilities';
`,
'src/styles/components.css': css`
.btn {
@apply bg-black px-4 py-2 rounded-md text-white font-medium hover:bg-zinc-800;
}
`,
},
},
async ({ fs, exec }) => {
await exec('npx @tailwindcss/upgrade --force')
expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(`
"
--- ./src/index.css ---
@import 'tailwindcss';
@import './styles/components.css' layer(components);
/*
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
/*
Form elements have a 1px border by default in Tailwind CSS v4, so we've
added these compatibility styles to make sure everything still looks the
same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add \`border-0\` to
any form elements that shouldn't have a border.
*/
@layer base {
input:where(:not([type='button'], [type='reset'], [type='submit'])),
select,
textarea {
border-width: 0;
}
}
--- ./src/styles/components.css ---
.btn {
@apply bg-black px-4 py-2 rounded-md text-white font-medium hover:bg-zinc-800;
}
"
`)
},
)

View File

@ -620,11 +620,3 @@ async function gracefullyRemove(dir: string) {
await fs.rm(dir, { recursive: true, force: true })
}
}
async function dirExists(dir: string): Promise<boolean> {
try {
return await fs.stat(dir).then((stat) => stat.isDirectory())
} catch {
return false
}
}

View File

@ -8,6 +8,7 @@ import { migrateAtApply } from './codemods/migrate-at-apply'
import { migrateAtLayerUtilities } from './codemods/migrate-at-layer-utilities'
import { migrateBorderCompatibility } from './codemods/migrate-border-compatibility'
import { migrateConfig } from './codemods/migrate-config'
import { migrateImport } from './codemods/migrate-import'
import { migrateMediaScreen } from './codemods/migrate-media-screen'
import { migrateMissingLayers } from './codemods/migrate-missing-layers'
import { migrateTailwindDirectives } from './codemods/migrate-tailwind-directives'
@ -37,6 +38,7 @@ export async function migrateContents(
}
return postcss()
.use(migrateImport())
.use(migrateAtApply(options))
.use(migrateMediaScreen(options))
.use(migrateVariantsDirective())
@ -84,9 +86,20 @@ export async function analyze(stylesheets: Stylesheet[]) {
: process.cwd()
// Resolve the import to a file path
let resolvedPath: string | false
let resolvedPath: string | false = false
try {
resolvedPath = resolveCssId(id, basePath)
// We first try to resolve the file as relative to the current file
// to mimic the behavior of `postcss-import` since that's what was
// used to resolve imports in Tailwind CSS v3.
if (id[0] !== '.') {
try {
resolvedPath = resolveCssId(`./${id}`, basePath)
} catch {}
}
if (!resolvedPath) {
resolvedPath = resolveCssId(id, basePath)
}
} catch (err) {
console.warn(`Failed to resolve import: ${id}. Skipping.`)
console.error(err)

View File

@ -0,0 +1,7 @@
{
"extends": "next/core-web-vitals",
"rules": {
"react/no-unescaped-entities": "off",
"react/jsx-no-comment-textnodes": "off"
}
}

36
playgrounds/v3/.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@ -0,0 +1,3 @@
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

View File

@ -0,0 +1,23 @@
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en" className="[&_h1]:font-thin">
<head>{/* <script src="https://cdn.tailwindcss.com"></script> */}</head>
<body className={inter.className}>{children}</body>
</html>
)
}

View File

@ -0,0 +1,3 @@
export default function Home() {
return <h1 className="text-3xl font-bold underline border ring">Hello world!</h1>
}

View File

@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}
export default nextConfig

View File

@ -0,0 +1,26 @@
{
"name": "v3-playground",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"upgrade": "node scripts/upgrade.mjs"
},
"dependencies": {
"next": "14.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwindcss": "^3"
},
"devDependencies": {
"@types/node": "^20.14.8",
"@types/react": "^18.3.9",
"@types/react-dom": "^18.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^9.11.1",
"eslint-config-next": "^14.2.5",
"typescript": "^5.5.4"
}
}

View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -0,0 +1,40 @@
import { execSync } from 'node:child_process'
import fs from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const cwd = path.join(__dirname, '..')
let originalLockfile = await fs.readFile(path.join(cwd, '../../pnpm-lock.yaml'), 'utf-8')
console.log('Overwriting dependencies for @tailwindcss/upgrade')
// Apply package patches
let json = JSON.parse(await fs.readFile('package.json', 'utf-8'))
json.pnpm = {
overrides: {
'@tailwindcss/upgrade>tailwindcss': 'file:../../dist/tailwindcss.tgz',
'@tailwindcss/upgrade>@tailwindcss/node': 'file:../../dist/tailwindcss-node.tgz',
},
}
json.devDependencies['@tailwindcss/upgrade'] = 'file:../../dist/tailwindcss-upgrade.tgz'
await fs.writeFile('package.json', JSON.stringify(json, null, 2))
try {
execSync('pnpm install --ignore-workspace', { cwd })
} catch (error) {
console.error(error.stdout?.toString() ?? error)
}
execSync('npx @tailwindcss/upgrade --force', { cwd, stdio: 'inherit' })
// Undo package patches
json = JSON.parse(await fs.readFile('package.json', 'utf-8'))
delete json.pnpm
delete json.devDependencies['@tailwindcss/upgrade']
await fs.writeFile('package.json', JSON.stringify(json, null, 2))
// Restore original lockfile (to avoid unnecessary changes in git diff)
await fs.writeFile(path.join(cwd, '../../pnpm-lock.yaml'), originalLockfile)
await fs.unlink(path.join(cwd, 'pnpm-lock.yaml'))

View File

@ -0,0 +1,4 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./app/**/*.tsx'],
}

View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next",
},
],
"paths": {
"@/*": ["./*"],
},
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"],
}

938
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff