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
This commit is contained in:
parent
3fb6902f08
commit
57f87be440
5 changed files with 184 additions and 25 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ test(
|
|||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "workspace:^",
|
||||
"tailwindcss": "^3",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
}
|
||||
|
|
@ -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`
|
||||
|
|
@ -1069,7 +1069,7 @@ test(
|
|||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "workspace:^",
|
||||
"tailwindcss": "^3",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
}
|
||||
|
|
@ -1123,7 +1123,7 @@ test(
|
|||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "workspace:^",
|
||||
"tailwindcss": "^3",
|
||||
"@tailwindcss/cli": "workspace:^",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
|
|
@ -1310,7 +1310,7 @@ test(
|
|||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "workspace:^",
|
||||
"tailwindcss": "^3",
|
||||
"@tailwindcss/cli": "workspace:^",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
|
|
@ -1376,6 +1376,7 @@ test(
|
|||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "^3",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
`,
|
||||
},
|
||||
},
|
||||
|
|
@ -1871,7 +1872,7 @@ test(
|
|||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "workspace:^",
|
||||
"tailwindcss": "^3",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
}
|
||||
|
|
@ -1933,7 +1934,7 @@ test(
|
|||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "workspace:^",
|
||||
"tailwindcss": "^3",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
}
|
||||
|
|
@ -2017,7 +2018,7 @@ test(
|
|||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "workspace:^",
|
||||
"tailwindcss": "^3",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -2047,7 +2048,7 @@ test(
|
|||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "^3.4.14",
|
||||
"tailwindcss": "^3",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -2152,7 +2153,7 @@ test(
|
|||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"tailwindcss": "^3.4.14",
|
||||
"tailwindcss": "^3",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
"
|
||||
`)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -65,13 +66,28 @@ export async function migrate(stylesheet: Stylesheet, options: MigrateOptions) {
|
|||
}
|
||||
|
||||
export async function analyze(stylesheets: Stylesheet[]) {
|
||||
let stylesheetsByFile = new Map<string, Stylesheet>()
|
||||
|
||||
for (let sheet of stylesheets) {
|
||||
if (sheet.file) {
|
||||
stylesheetsByFile.set(sheet.file, sheet)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let sheet = Stylesheet.loadSync(file)
|
||||
|
||||
// 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
|
||||
|
|
@ -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
|
||||
|
||||
await processor.process(sheet.root, { from: sheet.file })
|
||||
if (sheet.file) {
|
||||
stylesheetsByFile.set(sheet.file, sheet)
|
||||
processingQueue.push(() => processor.process(sheet.root, { from: sheet.file ?? undefined }))
|
||||
}
|
||||
}
|
||||
|
||||
// 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[]) {
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue