Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve imports from CSS file #15010

Merged
merged 8 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Ensure `flex` is suggested ([#15014](https://github.com/tailwindlabs/tailwindcss/pull/15014))
- _Upgrade (experimental)_: Resolve imports from passed CSS file(s) ([#15010](https://github.com/tailwindlabs/tailwindcss/pull/15010))

### Changed

Expand Down
147 changes: 135 additions & 12 deletions integrations/upgrade/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ test(
'package.json': json`
{
"dependencies": {
"tailwindcss": "workspace:^",
"tailwindcss": "^3",
"@tailwindcss/upgrade": "workspace:^"
}
}
Expand Down Expand Up @@ -1000,14 +1000,14 @@ test(
'package.json': json`
{
"dependencies": {
"tailwindcss": "workspace:^",
"tailwindcss": "^3",
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.js': js`module.exports = {}`,
'src/index.css': css`
@import 'tailwindcss';
@import 'tailwindcss/tailwind.css';
@import './utilities.css' layer(utilities);
`,
'src/utilities.css': css`
Expand Down Expand Up @@ -1069,7 +1069,7 @@ test(
'package.json': json`
{
"dependencies": {
"tailwindcss": "workspace:^",
"tailwindcss": "^3",
"@tailwindcss/upgrade": "workspace:^"
}
}
Expand Down Expand Up @@ -1123,7 +1123,7 @@ test(
'package.json': json`
{
"dependencies": {
"tailwindcss": "workspace:^",
"tailwindcss": "^3",
"@tailwindcss/cli": "workspace:^",
"@tailwindcss/upgrade": "workspace:^"
}
Expand Down Expand Up @@ -1310,7 +1310,7 @@ test(
'package.json': json`
{
"dependencies": {
"tailwindcss": "workspace:^",
"tailwindcss": "^3",
"@tailwindcss/cli": "workspace:^",
"@tailwindcss/upgrade": "workspace:^"
}
Expand Down Expand Up @@ -1376,6 +1376,7 @@ test(
'package.json': json`
{
"dependencies": {
"tailwindcss": "^3",
"@tailwindcss/upgrade": "workspace:^"
}
}
Expand Down Expand Up @@ -1435,7 +1436,7 @@ test(
'src/root.5.css': css`@import './root.5/tailwind.css';`,
'src/root.5/tailwind.css': css`
/* Inject missing @config in this file, due to full import */
@import 'tailwindcss';
@import 'tailwindcss/tailwind.css';
`,
},
},
Expand Down Expand Up @@ -1871,7 +1872,7 @@ test(
'package.json': json`
{
"dependencies": {
"tailwindcss": "workspace:^",
"tailwindcss": "^3",
"@tailwindcss/upgrade": "workspace:^"
}
}
Expand Down Expand Up @@ -1933,7 +1934,7 @@ test(
'package.json': json`
{
"dependencies": {
"tailwindcss": "workspace:^",
"tailwindcss": "^3",
"@tailwindcss/upgrade": "workspace:^"
}
}
Expand Down Expand Up @@ -2017,7 +2018,7 @@ test(
'package.json': json`
{
"dependencies": {
"tailwindcss": "workspace:^",
"tailwindcss": "^3",
"@tailwindcss/upgrade": "workspace:^"
},
"devDependencies": {
Expand Down Expand Up @@ -2047,7 +2048,7 @@ test(
'package.json': json`
{
"dependencies": {
"tailwindcss": "^3.4.14",
"tailwindcss": "^3",
"@tailwindcss/upgrade": "workspace:^"
},
"devDependencies": {
Expand Down Expand Up @@ -2152,7 +2153,7 @@ test(
'package.json': json`
{
"dependencies": {
"tailwindcss": "^3.4.14",
"tailwindcss": "^3",
"@tailwindcss/upgrade": "workspace:^"
},
"devDependencies": {
Expand Down Expand Up @@ -2236,3 +2237,125 @@ test(
`)
},
)

test(
'passing in a single CSS file should resolve all imports and migrate them',
{
fs: {
'package.json': json`
{
"dependencies": {
"tailwindcss": "^3",
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.js': js`module.exports = {}`,
'src/index.css': css`
@import './base.css';
@import './components.css';
@import './utilities.css';
@import './generated/ignore-me.css';
`,
'src/generated/.gitignore': `
*
!.gitignore
`,
'src/generated/ignore-me.css': css`
/* This should not be converted */
@layer utilities {
.ignore-me {
color: red;
}
}
`,
'src/base.css': css`@import 'tailwindcss/base';`,
'src/components.css': css`
@import './typography.css';
@layer components {
.foo {
color: red;
}
}
@tailwind components;
`,
'src/utilities.css': css`
@layer utilities {
.bar {
color: blue;
}
}
@tailwind utilities;
`,
'src/typography.css': css`
@layer components {
.typography {
color: red;
}
}
`,
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade ./src/index.css')

expect(await fs.dumpFiles('./src/**/*.{css,html}')).toMatchInlineSnapshot(`
"
--- ./src/index.css ---
@import './base.css';
@import './components.css';
@import './utilities.css';
@import './generated/ignore-me.css';

--- ./src/base.css ---
@import 'tailwindcss/theme' layer(theme);
@import 'tailwindcss/preflight' layer(base);

/*
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);
}
}

--- ./src/components.css ---
@import './typography.css';

@utility foo {
color: red;
}

--- ./src/typography.css ---
@utility typography {
color: red;
}

--- ./src/utilities.css ---
@import 'tailwindcss/utilities' layer(utilities);

@utility bar {
color: blue;
}

--- ./src/generated/ignore-me.css ---
/* This should not be converted */
@layer utilities {
.ignore-me {
color: red;
}
}
"
`)
},
)
4 changes: 1 addition & 3 deletions packages/@tailwindcss-upgrade/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ async function run() {

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

files = await globby(['**/*.css'], {
absolute: true,
Expand Down
43 changes: 35 additions & 8 deletions packages/@tailwindcss-upgrade/src/migrate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { normalizePath } from '@tailwindcss/node'
import { isGitIgnored } from 'globby'
import path from 'node:path'
import postcss from 'postcss'
import postcss, { type Result } from 'postcss'
import type { Config } from '../../tailwindcss/src/compat/plugin-api'
import type { DesignSystem } from '../../tailwindcss/src/design-system'
import { DefaultMap } from '../../tailwindcss/src/utils/default-map'
Expand Down Expand Up @@ -65,13 +66,28 @@ export async function migrate(stylesheet: Stylesheet, options: MigrateOptions) {
}

export async function analyze(stylesheets: Stylesheet[]) {
let stylesheetsByFile = new Map<string, Stylesheet>()
let isIgnored = await isGitIgnored()
let processingQueue: (() => Promise<Result>)[] = []
let stylesheetsByFile = new DefaultMap<string, Stylesheet | null>((file) => {
// We don't want to process ignored files (like node_modules)
if (isIgnored(file)) {
return null
}

for (let sheet of stylesheets) {
if (sheet.file) {
stylesheetsByFile.set(sheet.file, sheet)
try {
let sheet = Stylesheet.loadSync(file)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a little sad this isn't async but that's a much larger change

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep exactly.


// Mutate incoming stylesheets to include the newly discovered sheet
stylesheets.push(sheet)

// Queue up the processing of this stylesheet
processingQueue.push(() => processor.process(sheet.root, { from: sheet.file! }))

return sheet
} catch {
return null
}
}
})

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

// Seed the map with all the known stylesheets, and queue up the processing of
// each incoming stylesheet.
for (let sheet of stylesheets) {
if (!sheet.file) continue
if (sheet.file) {
stylesheetsByFile.set(sheet.file, sheet)
processingQueue.push(() => processor.process(sheet.root, { from: sheet.file ?? undefined }))
}
}

await processor.process(sheet.root, { from: sheet.file })
// Process all the stylesheets from step 1
while (processingQueue.length > 0) {
let task = processingQueue.shift()!
await task()
}

// ---

let commonPath = process.cwd()

function pathToString(path: StylesheetConnection[]) {
Expand Down
10 changes: 10 additions & 0 deletions packages/@tailwindcss-upgrade/src/stylesheet.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as fsSync from 'node:fs'
import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import * as util from 'node:util'
Expand Down Expand Up @@ -72,6 +73,15 @@ export class Stylesheet {
return new Stylesheet(root, filepath)
}

static loadSync(filepath: string) {
filepath = path.resolve(process.cwd(), filepath)

let css = fsSync.readFileSync(filepath, 'utf-8')
let root = postcss.parse(css, { from: filepath })

return new Stylesheet(root, filepath)
}

static async fromString(css: string) {
let root = postcss.parse(css)

Expand Down