Skip to content

Commit 57f87be

Browse files
authored
Resolve imports from CSS file (#15010)
This PR adds an improvement to the upgrade tool to make sure that if you pass a single CSS file, that the upgrade tool resolves all the imports in that file and processes them as well. Test plan: --- Created a project where `index.css` imports `other.css`. Another `leave-me-alone.css` is created to proof that this file is _not_ changed. Running the upgrade guide using `index.css` also migrates `other.css` but not `leave-me-alone.css`. Here is a video so you don't have to manually create it: https://github.com/user-attachments/assets/20decf77-77d2-4a7c-8ff1-accb1c77f8c1
1 parent 3fb6902 commit 57f87be

File tree

5 files changed

+182
-23
lines changed

5 files changed

+182
-23
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 `flex` is suggested ([#15014](https://github.com/tailwindlabs/tailwindcss/pull/15014))
13+
- _Upgrade (experimental)_: Resolve imports from passed CSS file(s) ([#15010](https://github.com/tailwindlabs/tailwindcss/pull/15010))
1314

1415
### Changed
1516

integrations/upgrade/index.test.ts

Lines changed: 135 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ test(
239239
'package.json': json`
240240
{
241241
"dependencies": {
242-
"tailwindcss": "workspace:^",
242+
"tailwindcss": "^3",
243243
"@tailwindcss/upgrade": "workspace:^"
244244
}
245245
}
@@ -1000,14 +1000,14 @@ test(
10001000
'package.json': json`
10011001
{
10021002
"dependencies": {
1003-
"tailwindcss": "workspace:^",
1003+
"tailwindcss": "^3",
10041004
"@tailwindcss/upgrade": "workspace:^"
10051005
}
10061006
}
10071007
`,
10081008
'tailwind.config.js': js`module.exports = {}`,
10091009
'src/index.css': css`
1010-
@import 'tailwindcss';
1010+
@import 'tailwindcss/tailwind.css';
10111011
@import './utilities.css' layer(utilities);
10121012
`,
10131013
'src/utilities.css': css`
@@ -1069,7 +1069,7 @@ test(
10691069
'package.json': json`
10701070
{
10711071
"dependencies": {
1072-
"tailwindcss": "workspace:^",
1072+
"tailwindcss": "^3",
10731073
"@tailwindcss/upgrade": "workspace:^"
10741074
}
10751075
}
@@ -1123,7 +1123,7 @@ test(
11231123
'package.json': json`
11241124
{
11251125
"dependencies": {
1126-
"tailwindcss": "workspace:^",
1126+
"tailwindcss": "^3",
11271127
"@tailwindcss/cli": "workspace:^",
11281128
"@tailwindcss/upgrade": "workspace:^"
11291129
}
@@ -1310,7 +1310,7 @@ test(
13101310
'package.json': json`
13111311
{
13121312
"dependencies": {
1313-
"tailwindcss": "workspace:^",
1313+
"tailwindcss": "^3",
13141314
"@tailwindcss/cli": "workspace:^",
13151315
"@tailwindcss/upgrade": "workspace:^"
13161316
}
@@ -1376,6 +1376,7 @@ test(
13761376
'package.json': json`
13771377
{
13781378
"dependencies": {
1379+
"tailwindcss": "^3",
13791380
"@tailwindcss/upgrade": "workspace:^"
13801381
}
13811382
}
@@ -1435,7 +1436,7 @@ test(
14351436
'src/root.5.css': css`@import './root.5/tailwind.css';`,
14361437
'src/root.5/tailwind.css': css`
14371438
/* Inject missing @config in this file, due to full import */
1438-
@import 'tailwindcss';
1439+
@import 'tailwindcss/tailwind.css';
14391440
`,
14401441
},
14411442
},
@@ -1871,7 +1872,7 @@ test(
18711872
'package.json': json`
18721873
{
18731874
"dependencies": {
1874-
"tailwindcss": "workspace:^",
1875+
"tailwindcss": "^3",
18751876
"@tailwindcss/upgrade": "workspace:^"
18761877
}
18771878
}
@@ -1933,7 +1934,7 @@ test(
19331934
'package.json': json`
19341935
{
19351936
"dependencies": {
1936-
"tailwindcss": "workspace:^",
1937+
"tailwindcss": "^3",
19371938
"@tailwindcss/upgrade": "workspace:^"
19381939
}
19391940
}
@@ -2017,7 +2018,7 @@ test(
20172018
'package.json': json`
20182019
{
20192020
"dependencies": {
2020-
"tailwindcss": "workspace:^",
2021+
"tailwindcss": "^3",
20212022
"@tailwindcss/upgrade": "workspace:^"
20222023
},
20232024
"devDependencies": {
@@ -2047,7 +2048,7 @@ test(
20472048
'package.json': json`
20482049
{
20492050
"dependencies": {
2050-
"tailwindcss": "^3.4.14",
2051+
"tailwindcss": "^3",
20512052
"@tailwindcss/upgrade": "workspace:^"
20522053
},
20532054
"devDependencies": {
@@ -2152,7 +2153,7 @@ test(
21522153
'package.json': json`
21532154
{
21542155
"dependencies": {
2155-
"tailwindcss": "^3.4.14",
2156+
"tailwindcss": "^3",
21562157
"@tailwindcss/upgrade": "workspace:^"
21572158
},
21582159
"devDependencies": {
@@ -2236,3 +2237,125 @@ test(
22362237
`)
22372238
},
22382239
)
2240+
2241+
test(
2242+
'passing in a single CSS file should resolve all imports and migrate them',
2243+
{
2244+
fs: {
2245+
'package.json': json`
2246+
{
2247+
"dependencies": {
2248+
"tailwindcss": "^3",
2249+
"@tailwindcss/upgrade": "workspace:^"
2250+
}
2251+
}
2252+
`,
2253+
'tailwind.config.js': js`module.exports = {}`,
2254+
'src/index.css': css`
2255+
@import './base.css';
2256+
@import './components.css';
2257+
@import './utilities.css';
2258+
@import './generated/ignore-me.css';
2259+
`,
2260+
'src/generated/.gitignore': `
2261+
*
2262+
!.gitignore
2263+
`,
2264+
'src/generated/ignore-me.css': css`
2265+
/* This should not be converted */
2266+
@layer utilities {
2267+
.ignore-me {
2268+
color: red;
2269+
}
2270+
}
2271+
`,
2272+
'src/base.css': css`@import 'tailwindcss/base';`,
2273+
'src/components.css': css`
2274+
@import './typography.css';
2275+
@layer components {
2276+
.foo {
2277+
color: red;
2278+
}
2279+
}
2280+
@tailwind components;
2281+
`,
2282+
'src/utilities.css': css`
2283+
@layer utilities {
2284+
.bar {
2285+
color: blue;
2286+
}
2287+
}
2288+
@tailwind utilities;
2289+
`,
2290+
'src/typography.css': css`
2291+
@layer components {
2292+
.typography {
2293+
color: red;
2294+
}
2295+
}
2296+
`,
2297+
},
2298+
},
2299+
async ({ exec, fs }) => {
2300+
await exec('npx @tailwindcss/upgrade ./src/index.css')
2301+
2302+
expect(await fs.dumpFiles('./src/**/*.{css,html}')).toMatchInlineSnapshot(`
2303+
"
2304+
--- ./src/index.css ---
2305+
@import './base.css';
2306+
@import './components.css';
2307+
@import './utilities.css';
2308+
@import './generated/ignore-me.css';
2309+
2310+
--- ./src/base.css ---
2311+
@import 'tailwindcss/theme' layer(theme);
2312+
@import 'tailwindcss/preflight' layer(base);
2313+
2314+
/*
2315+
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
2316+
so we've added these compatibility styles to make sure everything still
2317+
looks the same as it did with Tailwind CSS v3.
2318+
2319+
If we ever want to remove these styles, we need to add an explicit border
2320+
color utility to any element that depends on these defaults.
2321+
*/
2322+
@layer base {
2323+
*,
2324+
::after,
2325+
::before,
2326+
::backdrop,
2327+
::file-selector-button {
2328+
border-color: var(--color-gray-200, currentColor);
2329+
}
2330+
}
2331+
2332+
--- ./src/components.css ---
2333+
@import './typography.css';
2334+
2335+
@utility foo {
2336+
color: red;
2337+
}
2338+
2339+
--- ./src/typography.css ---
2340+
@utility typography {
2341+
color: red;
2342+
}
2343+
2344+
--- ./src/utilities.css ---
2345+
@import 'tailwindcss/utilities' layer(utilities);
2346+
2347+
@utility bar {
2348+
color: blue;
2349+
}
2350+
2351+
--- ./src/generated/ignore-me.css ---
2352+
/* This should not be converted */
2353+
@layer utilities {
2354+
.ignore-me {
2355+
color: red;
2356+
}
2357+
}
2358+
"
2359+
`)
2360+
},
2361+
)

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ async function run() {
6767

6868
// Discover CSS files in case no files were provided
6969
if (files.length === 0) {
70-
info(
71-
'No input stylesheets provided. Searching for CSS files in the current directory and its subdirectories…',
72-
)
70+
info('Searching for CSS files in the current directory and its subdirectories…')
7371

7472
files = await globby(['**/*.css'], {
7573
absolute: true,

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

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { normalizePath } from '@tailwindcss/node'
2+
import { isGitIgnored } from 'globby'
23
import path from 'node:path'
3-
import postcss from 'postcss'
4+
import postcss, { type Result } from 'postcss'
45
import type { Config } from '../../tailwindcss/src/compat/plugin-api'
56
import type { DesignSystem } from '../../tailwindcss/src/design-system'
67
import { DefaultMap } from '../../tailwindcss/src/utils/default-map'
@@ -65,13 +66,28 @@ export async function migrate(stylesheet: Stylesheet, options: MigrateOptions) {
6566
}
6667

6768
export async function analyze(stylesheets: Stylesheet[]) {
68-
let stylesheetsByFile = new Map<string, Stylesheet>()
69+
let isIgnored = await isGitIgnored()
70+
let processingQueue: (() => Promise<Result>)[] = []
71+
let stylesheetsByFile = new DefaultMap<string, Stylesheet | null>((file) => {
72+
// We don't want to process ignored files (like node_modules)
73+
if (isIgnored(file)) {
74+
return null
75+
}
6976

70-
for (let sheet of stylesheets) {
71-
if (sheet.file) {
72-
stylesheetsByFile.set(sheet.file, sheet)
77+
try {
78+
let sheet = Stylesheet.loadSync(file)
79+
80+
// Mutate incoming stylesheets to include the newly discovered sheet
81+
stylesheets.push(sheet)
82+
83+
// Queue up the processing of this stylesheet
84+
processingQueue.push(() => processor.process(sheet.root, { from: sheet.file! }))
85+
86+
return sheet
87+
} catch {
88+
return null
7389
}
74-
}
90+
})
7591

7692
// Step 1: Record which `@import` rules point to which stylesheets
7793
// and which stylesheets are parents/children of each other
@@ -147,12 +163,23 @@ export async function analyze(stylesheets: Stylesheet[]) {
147163
},
148164
])
149165

166+
// Seed the map with all the known stylesheets, and queue up the processing of
167+
// each incoming stylesheet.
150168
for (let sheet of stylesheets) {
151-
if (!sheet.file) continue
169+
if (sheet.file) {
170+
stylesheetsByFile.set(sheet.file, sheet)
171+
processingQueue.push(() => processor.process(sheet.root, { from: sheet.file ?? undefined }))
172+
}
173+
}
152174

153-
await processor.process(sheet.root, { from: sheet.file })
175+
// Process all the stylesheets from step 1
176+
while (processingQueue.length > 0) {
177+
let task = processingQueue.shift()!
178+
await task()
154179
}
155180

181+
// ---
182+
156183
let commonPath = process.cwd()
157184

158185
function pathToString(path: StylesheetConnection[]) {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as fsSync from 'node:fs'
12
import * as fs from 'node:fs/promises'
23
import * as path from 'node:path'
34
import * as util from 'node:util'
@@ -72,6 +73,15 @@ export class Stylesheet {
7273
return new Stylesheet(root, filepath)
7374
}
7475

76+
static loadSync(filepath: string) {
77+
filepath = path.resolve(process.cwd(), filepath)
78+
79+
let css = fsSync.readFileSync(filepath, 'utf-8')
80+
let root = postcss.parse(css, { from: filepath })
81+
82+
return new Stylesheet(root, filepath)
83+
}
84+
7585
static async fromString(css: string) {
7686
let root = postcss.parse(css)
7787

0 commit comments

Comments
 (0)