diff --git a/CHANGELOG.md b/CHANGELOG.md index 891e6475634e..1898bb4a1a72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - _Upgrade (experimental)_: Migrate `plugins` with options to CSS ([#14700](https://github.com/tailwindlabs/tailwindcss/pull/14700)) - _Upgrade (experimental)_: Allow JS configuration files with `corePlugins` options to be migrated to CSS ([#14742](https://github.com/tailwindlabs/tailwindcss/pull/14742)) +- _Upgrade (experimental)_: Migrate `@import` statements for relative CSS files to use relative path syntax (e.g. `./file.css`) ([#14755](https://github.com/tailwindlabs/tailwindcss/pull/14755)) - _Upgrade (experimental)_: Migrate `max-w-screen-*` utilities to `max-w-[var(…)]`([#14754](https://github.com/tailwindlabs/tailwindcss/pull/14754)) - _Upgrade (experimental)_: Migrate `@variants` and `@responsive` directives ([#14748](https://github.com/tailwindlabs/tailwindcss/pull/14748)) - _Upgrade (experimental)_: Migrate `@screen` directive ([#14749](https://github.com/tailwindlabs/tailwindcss/pull/14749)) @@ -36,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Require a relative path prefix for importing relative CSS files (e.g. `@import './styles.css'` instead of `@import 'styles.css'`) ([#14755](https://github.com/tailwindlabs/tailwindcss/pull/14755)) - _Upgrade (experimental)_: Don't create `@source` rules for `content` paths that are already covered by automatic source detection ([#14714](https://github.com/tailwindlabs/tailwindcss/pull/14714)) ## [4.0.0-alpha.28] - 2024-10-17 diff --git a/packages/@tailwindcss-node/src/compile.ts b/packages/@tailwindcss-node/src/compile.ts index 25a2d037dfb0..4804a1c8bb87 100644 --- a/packages/@tailwindcss-node/src/compile.ts +++ b/packages/@tailwindcss-node/src/compile.ts @@ -2,7 +2,7 @@ import EnhancedResolve from 'enhanced-resolve' import { createJiti, type Jiti } from 'jiti' import fs from 'node:fs' import fsPromises from 'node:fs/promises' -import path, { dirname, extname } from 'node:path' +import path, { dirname } from 'node:path' import { pathToFileURL } from 'node:url' import { __unstable__loadDesignSystem as ___unstable__loadDesignSystem, @@ -120,21 +120,6 @@ async function resolveCssId(id: string, base: string): Promise result.css) +} + +it('prints relative file imports as relative paths', async () => { + expect( + await migrate(css` + @import 'fixtures/test'; + @import 'fixtures/test.css'; + @import './fixtures/test.css'; + @import './fixtures/test'; + + @import 'fixtures/test' screen; + @import 'fixtures/test.css' screen; + @import './fixtures/test.css' screen; + @import './fixtures/test' screen; + + @import 'fixtures/test' supports(display: grid); + @import 'fixtures/test.css' supports(display: grid); + @import './fixtures/test.css' supports(display: grid); + @import './fixtures/test' supports(display: grid); + + @import 'fixtures/test' layer(utilities); + @import 'fixtures/test.css' layer(utilities); + @import './fixtures/test.css' layer(utilities); + @import './fixtures/test' layer(utilities); + + @import 'fixtures/test' theme(inline); + @import 'fixtures/test.css' theme(inline); + @import './fixtures/test.css' theme(inline); + @import './fixtures/test' theme(inline); + + @import 'fixtures/test' layer(utilities) supports(display: grid) screen and (min-width: 600px); + @import 'fixtures/test.css' layer(utilities) supports(display: grid) screen and + (min-width: 600px); + @import './fixtures/test.css' layer(utilities) supports(display: grid) screen and + (min-width: 600px); + @import './fixtures/test' layer(utilities) supports(display: grid) screen and + (min-width: 600px); + + @import 'tailwindcss'; + @import 'tailwindcss/theme.css'; + @import 'tailwindcss/theme'; + `), + ).toMatchInlineSnapshot(` + "@import './fixtures/test.css'; + @import './fixtures/test.css'; + @import './fixtures/test.css'; + @import './fixtures/test.css'; + + @import './fixtures/test.css' screen; + @import './fixtures/test.css' screen; + @import './fixtures/test.css' screen; + @import './fixtures/test.css' screen; + + @import './fixtures/test.css' supports(display: grid); + @import './fixtures/test.css' supports(display: grid); + @import './fixtures/test.css' supports(display: grid); + @import './fixtures/test.css' supports(display: grid); + + @import './fixtures/test.css' layer(utilities); + @import './fixtures/test.css' layer(utilities); + @import './fixtures/test.css' layer(utilities); + @import './fixtures/test.css' layer(utilities); + + @import './fixtures/test.css' theme(inline); + @import './fixtures/test.css' theme(inline); + @import './fixtures/test.css' theme(inline); + @import './fixtures/test.css' theme(inline); + + @import './fixtures/test.css' layer(utilities) supports(display: grid) screen and (min-width: 600px); + @import './fixtures/test.css' layer(utilities) supports(display: grid) screen and + (min-width: 600px); + @import './fixtures/test.css' layer(utilities) supports(display: grid) screen and + (min-width: 600px); + @import './fixtures/test.css' layer(utilities) supports(display: grid) screen and + (min-width: 600px); + + @import 'tailwindcss'; + @import 'tailwindcss/theme.css'; + @import 'tailwindcss/theme';" + `) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-import.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-import.ts new file mode 100644 index 000000000000..a34f2d18e5ee --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-import.ts @@ -0,0 +1,45 @@ +import fs from 'node:fs/promises' +import { dirname, resolve } from 'node:path' +import { type Plugin, type Root } from 'postcss' +import { parseImportParams } from '../../../tailwindcss/src/at-import' +import { segment } from '../../../tailwindcss/src/utils/segment' +import * as ValueParser from '../../../tailwindcss/src/value-parser' + +export function migrateImport(): Plugin { + async function migrate(root: Root) { + let file = root.source?.input.file + if (!file) return + + let promises: Promise[] = [] + root.walkAtRules('import', (rule) => { + let [firstParam, ...rest] = segment(rule.params, ' ') + + let params = parseImportParams(ValueParser.parse(firstParam)) + + let isRelative = params.uri[0] === '.' + let hasCssExtension = params.uri.endsWith('.css') + + if (isRelative && hasCssExtension) { + return + } + + let fullPath = resolve(dirname(file), params.uri) + if (!hasCssExtension) fullPath += '.css' + + promises.push( + fs.stat(fullPath).then(() => { + let ext = hasCssExtension ? '' : '.css' + let path = isRelative ? params.uri : `./${params.uri}` + rule.params = [`'${path}${ext}'`, ...rest].join(' ') + }), + ) + }) + + await Promise.allSettled(promises) + } + + return { + postcssPlugin: '@tailwindcss/upgrade/migrate-import', + OnceExit: migrate, + } +} diff --git a/packages/tailwindcss/src/at-import.ts b/packages/tailwindcss/src/at-import.ts index a06000826b05..29c0ce2c5b8f 100644 --- a/packages/tailwindcss/src/at-import.ts +++ b/packages/tailwindcss/src/at-import.ts @@ -67,7 +67,7 @@ export async function substituteAtImports( // `postcss-import` // Copyright (c) 2014 Maxime Thirouin, Jason Campbell & Kevin Mårtensson // Released under the MIT License. -function parseImportParams(params: ValueParser.ValueAstNode[]) { +export function parseImportParams(params: ValueParser.ValueAstNode[]) { let uri let layer: string | null = null let media: string | null = null