Skip to content

Commit 39cfcfa

Browse files
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 <[email protected]> Co-authored-by: Philipp Spiess <[email protected]>
1 parent d643d79 commit 39cfcfa

File tree

16 files changed

+1145
-80
lines changed

16 files changed

+1145
-80
lines changed

CHANGELOG.md

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

1212
- Ensure individual logical property utilities are sorted later than left/right pair utilities ([#14777](https://github.com/tailwindlabs/tailwindcss/pull/14777))
13+
- _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))
1314

1415
## [4.0.0-alpha.29] - 2024-10-23
1516

integrations/upgrade/index.test.ts

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1775,6 +1775,7 @@ test(
17751775
/* Inject missing @config due to nested imports with tailwind imports */
17761776
@import './root.4/base.css';
17771777
@import './root.4/utilities.css';
1778+
@config '../tailwind.config.ts';
17781779
17791780
/*
17801781
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
@@ -1808,7 +1809,6 @@ test(
18081809
border-width: 0;
18091810
}
18101811
}
1811-
@config '../tailwind.config.ts';
18121812
18131813
--- ./src/root.5.css ---
18141814
@import './root.5/tailwind.css';
@@ -1963,3 +1963,80 @@ test(
19631963
`)
19641964
},
19651965
)
1966+
1967+
test(
1968+
'relative imports without a relative path prefix are migrated to include a relative path prefix',
1969+
{
1970+
fs: {
1971+
'package.json': json`
1972+
{
1973+
"dependencies": {
1974+
"tailwindcss": "workspace:^",
1975+
"@tailwindcss/upgrade": "workspace:^"
1976+
}
1977+
}
1978+
`,
1979+
'tailwind.config.js': js`module.exports = {}`,
1980+
'src/index.css': css`
1981+
@import 'tailwindcss/base';
1982+
@import 'tailwindcss/components';
1983+
@import 'styles/components';
1984+
@import 'tailwindcss/utilities';
1985+
`,
1986+
'src/styles/components.css': css`
1987+
.btn {
1988+
@apply bg-black px-4 py-2 rounded-md text-white font-medium hover:bg-zinc-800;
1989+
}
1990+
`,
1991+
},
1992+
},
1993+
async ({ fs, exec }) => {
1994+
await exec('npx @tailwindcss/upgrade --force')
1995+
1996+
expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(`
1997+
"
1998+
--- ./src/index.css ---
1999+
@import 'tailwindcss';
2000+
@import './styles/components.css' layer(components);
2001+
2002+
/*
2003+
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
2004+
so we've added these compatibility styles to make sure everything still
2005+
looks the same as it did with Tailwind CSS v3.
2006+
2007+
If we ever want to remove these styles, we need to add an explicit border
2008+
color utility to any element that depends on these defaults.
2009+
*/
2010+
@layer base {
2011+
*,
2012+
::after,
2013+
::before,
2014+
::backdrop,
2015+
::file-selector-button {
2016+
border-color: var(--color-gray-200, currentColor);
2017+
}
2018+
}
2019+
/*
2020+
Form elements have a 1px border by default in Tailwind CSS v4, so we've
2021+
added these compatibility styles to make sure everything still looks the
2022+
same as it did with Tailwind CSS v3.
2023+
2024+
If we ever want to remove these styles, we need to add \`border-0\` to
2025+
any form elements that shouldn't have a border.
2026+
*/
2027+
@layer base {
2028+
input:where(:not([type='button'], [type='reset'], [type='submit'])),
2029+
select,
2030+
textarea {
2031+
border-width: 0;
2032+
}
2033+
}
2034+
2035+
--- ./src/styles/components.css ---
2036+
.btn {
2037+
@apply bg-black px-4 py-2 rounded-md text-white font-medium hover:bg-zinc-800;
2038+
}
2039+
"
2040+
`)
2041+
},
2042+
)

integrations/utils.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -620,11 +620,3 @@ async function gracefullyRemove(dir: string) {
620620
await fs.rm(dir, { recursive: true, force: true })
621621
}
622622
}
623-
624-
async function dirExists(dir: string): Promise<boolean> {
625-
try {
626-
return await fs.stat(dir).then((stat) => stat.isDirectory())
627-
} catch {
628-
return false
629-
}
630-
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { migrateAtApply } from './codemods/migrate-at-apply'
88
import { migrateAtLayerUtilities } from './codemods/migrate-at-layer-utilities'
99
import { migrateBorderCompatibility } from './codemods/migrate-border-compatibility'
1010
import { migrateConfig } from './codemods/migrate-config'
11+
import { migrateImport } from './codemods/migrate-import'
1112
import { migrateMediaScreen } from './codemods/migrate-media-screen'
1213
import { migrateMissingLayers } from './codemods/migrate-missing-layers'
1314
import { migrateTailwindDirectives } from './codemods/migrate-tailwind-directives'
@@ -37,6 +38,7 @@ export async function migrateContents(
3738
}
3839

3940
return postcss()
41+
.use(migrateImport())
4042
.use(migrateAtApply(options))
4143
.use(migrateMediaScreen(options))
4244
.use(migrateVariantsDirective())
@@ -84,9 +86,20 @@ export async function analyze(stylesheets: Stylesheet[]) {
8486
: process.cwd()
8587

8688
// Resolve the import to a file path
87-
let resolvedPath: string | false
89+
let resolvedPath: string | false = false
8890
try {
89-
resolvedPath = resolveCssId(id, basePath)
91+
// We first try to resolve the file as relative to the current file
92+
// to mimic the behavior of `postcss-import` since that's what was
93+
// used to resolve imports in Tailwind CSS v3.
94+
if (id[0] !== '.') {
95+
try {
96+
resolvedPath = resolveCssId(`./${id}`, basePath)
97+
} catch {}
98+
}
99+
100+
if (!resolvedPath) {
101+
resolvedPath = resolveCssId(id, basePath)
102+
}
90103
} catch (err) {
91104
console.warn(`Failed to resolve import: ${id}. Skipping.`)
92105
console.error(err)

playgrounds/v3/.eslintrc.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "next/core-web-vitals",
3+
"rules": {
4+
"react/no-unescaped-entities": "off",
5+
"react/jsx-no-comment-textnodes": "off"
6+
}
7+
}

playgrounds/v3/.gitignore

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
.yarn/install-state.gz
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts

playgrounds/v3/app/globals.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@import 'tailwindcss/base';
2+
@import 'tailwindcss/components';
3+
@import 'tailwindcss/utilities';

playgrounds/v3/app/layout.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { Metadata } from 'next'
2+
import { Inter } from 'next/font/google'
3+
import './globals.css'
4+
5+
const inter = Inter({ subsets: ['latin'] })
6+
7+
export const metadata: Metadata = {
8+
title: 'Create Next App',
9+
description: 'Generated by create next app',
10+
}
11+
12+
export default function RootLayout({
13+
children,
14+
}: Readonly<{
15+
children: React.ReactNode
16+
}>) {
17+
return (
18+
<html lang="en" className="[&_h1]:font-thin">
19+
<head>{/* <script src="https://cdn.tailwindcss.com"></script> */}</head>
20+
<body className={inter.className}>{children}</body>
21+
</html>
22+
)
23+
}

playgrounds/v3/app/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Home() {
2+
return <h1 className="text-3xl font-bold underline border ring">Hello world!</h1>
3+
}

playgrounds/v3/next.config.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {}
3+
4+
export default nextConfig

0 commit comments

Comments
 (0)