Skip to content

Commit 3b86506

Browse files
Upgrade: Rewrite imports of relative files to use relative file paths
1 parent 557ed8c commit 3b86506

File tree

6 files changed

+154
-17
lines changed

6 files changed

+154
-17
lines changed

CHANGELOG.md

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

1212
- _Upgrade (experimental)_: Migrate `plugins` with options to CSS ([#14700](https://github.com/tailwindlabs/tailwindcss/pull/14700))
1313
- _Upgrade (experimental)_: Allow JS configuration files with `corePlugins` options to be migrated to CSS ([#14742](https://github.com/tailwindlabs/tailwindcss/pull/14742))
14+
- _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))
1415

1516
### Fixed
1617

@@ -26,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2627

2728
### Changed
2829

30+
- No longer resolve filenames without a prefix as relative files (e.g. `@import 'styles.css'` instead of `@import './styles.css'`) ([#14755](https://github.com/tailwindlabs/tailwindcss/pull/14755))
2931
- _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))
3032

3133
## [4.0.0-alpha.28] - 2024-10-17

packages/@tailwindcss-node/src/compile.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import EnhancedResolve from 'enhanced-resolve'
22
import { createJiti, type Jiti } from 'jiti'
33
import fs from 'node:fs'
44
import fsPromises from 'node:fs/promises'
5-
import path, { dirname, extname } from 'node:path'
5+
import path, { dirname } from 'node:path'
66
import { pathToFileURL } from 'node:url'
77
import {
88
__unstable__loadDesignSystem as ___unstable__loadDesignSystem,
@@ -120,21 +120,6 @@ async function resolveCssId(id: string, base: string): Promise<string | false |
120120
}
121121
}
122122

123-
// CSS imports that do not have a dir prefix are considered relative. Since
124-
// the resolver does not account for this, we need to do a first pass with an
125-
// assumed relative import by prefixing `./${path}`. We don't have to do this
126-
// when the path starts with a `.` or when the path has no extension (at which
127-
// case it's likely an npm package and not a relative stylesheet).
128-
let skipRelativeCheck = extname(id) === '' || id.startsWith('.')
129-
130-
if (!skipRelativeCheck) {
131-
try {
132-
let dotResolved = await runResolver(cssResolver, `./${id}`, base)
133-
if (!dotResolved) throw new Error()
134-
return dotResolved
135-
} catch {}
136-
}
137-
138123
return runResolver(cssResolver, id, base)
139124
}
140125

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.foo {
2+
color: red;
3+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
2+
import dedent from 'dedent'
3+
import postcss from 'postcss'
4+
import { expect, it } from 'vitest'
5+
import type { UserConfig } from '../../../tailwindcss/src/compat/config/types'
6+
import { migrateImport } from './migrate-import'
7+
8+
const css = dedent
9+
10+
async function migrate(input: string, userConfig: UserConfig = {}) {
11+
return postcss()
12+
.use(
13+
migrateImport({
14+
designSystem: await __unstable__loadDesignSystem(`@import 'tailwindcss';`, {
15+
base: __dirname,
16+
}),
17+
userConfig,
18+
}),
19+
)
20+
.process(input, { from: expect.getState().testPath })
21+
.then((result) => result.css)
22+
}
23+
24+
it('prints relative file imports as relative paths', async () => {
25+
expect(
26+
await migrate(css`
27+
@import 'fixtures/test';
28+
@import 'fixtures/test.css';
29+
@import './fixtures/test.css';
30+
@import './fixtures/test';
31+
32+
@import 'fixtures/test' screen;
33+
@import 'fixtures/test.css' screen;
34+
@import './fixtures/test.css' screen;
35+
@import './fixtures/test' screen;
36+
37+
@import 'fixtures/test' supports(display: grid);
38+
@import 'fixtures/test.css' supports(display: grid);
39+
@import './fixtures/test.css' supports(display: grid);
40+
@import './fixtures/test' supports(display: grid);
41+
42+
@import 'fixtures/test' layer(utilities);
43+
@import 'fixtures/test.css' layer(utilities);
44+
@import './fixtures/test.css' layer(utilities);
45+
@import './fixtures/test' layer(utilities);
46+
47+
@import 'fixtures/test' theme(inline);
48+
@import 'fixtures/test.css' theme(inline);
49+
@import './fixtures/test.css' theme(inline);
50+
@import './fixtures/test' theme(inline);
51+
52+
@import 'fixtures/test' layer(utilities) supports(display: grid) screen and (min-width: 600px);
53+
@import 'fixtures/test.css' layer(utilities) supports(display: grid) screen and
54+
(min-width: 600px);
55+
@import './fixtures/test.css' layer(utilities) supports(display: grid) screen and
56+
(min-width: 600px);
57+
@import './fixtures/test' layer(utilities) supports(display: grid) screen and
58+
(min-width: 600px);
59+
60+
@import 'tailwindcss';
61+
@import 'tailwindcss/theme.css';
62+
@import 'tailwindcss/theme';
63+
`),
64+
).toMatchInlineSnapshot(`
65+
"@import './fixtures/test.css';
66+
@import './fixtures/test.css';
67+
@import './fixtures/test.css';
68+
@import './fixtures/test.css';
69+
70+
@import './fixtures/test.css' screen;
71+
@import './fixtures/test.css' screen;
72+
@import './fixtures/test.css' screen;
73+
@import './fixtures/test.css' screen;
74+
75+
@import './fixtures/test.css' supports(display: grid);
76+
@import './fixtures/test.css' supports(display: grid);
77+
@import './fixtures/test.css' supports(display: grid);
78+
@import './fixtures/test.css' supports(display: grid);
79+
80+
@import './fixtures/test.css' layer(utilities);
81+
@import './fixtures/test.css' layer(utilities);
82+
@import './fixtures/test.css' layer(utilities);
83+
@import './fixtures/test.css' layer(utilities);
84+
85+
@import './fixtures/test.css' theme(inline);
86+
@import './fixtures/test.css' theme(inline);
87+
@import './fixtures/test.css' theme(inline);
88+
@import './fixtures/test.css' theme(inline);
89+
90+
@import './fixtures/test.css' layer(utilities) supports(display: grid) screen and (min-width: 600px);
91+
@import './fixtures/test.css' layer(utilities) supports(display: grid) screen and
92+
(min-width: 600px);
93+
@import './fixtures/test.css' layer(utilities) supports(display: grid) screen and
94+
(min-width: 600px);
95+
@import './fixtures/test.css' layer(utilities) supports(display: grid) screen and
96+
(min-width: 600px);
97+
98+
@import 'tailwindcss';
99+
@import 'tailwindcss/theme.css';
100+
@import 'tailwindcss/theme';"
101+
`)
102+
})
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import fs from 'node:fs/promises'
2+
import { dirname, resolve } from 'node:path'
3+
import { type Plugin, type Root } from 'postcss'
4+
import { parseImportParams } from '../../../tailwindcss/src/at-import'
5+
import { segment } from '../../../tailwindcss/src/utils/segment'
6+
import * as ValueParser from '../../../tailwindcss/src/value-parser'
7+
8+
export function migrateImport(): Plugin {
9+
async function migrate(root: Root) {
10+
let file = root.source?.input.file
11+
if (!file) return
12+
13+
let promises: Promise<void>[] = []
14+
root.walkAtRules('import', (rule) => {
15+
let [firstParam, ...rest] = segment(rule.params, ' ')
16+
17+
let params = parseImportParams(ValueParser.parse(firstParam))
18+
19+
let isRelative = params.uri[0] === '.'
20+
let hasCssExtension = params.uri.endsWith('.css')
21+
22+
if (isRelative && hasCssExtension) {
23+
return
24+
}
25+
26+
let fullPath = resolve(dirname(file), params.uri)
27+
if (!hasCssExtension) fullPath += '.css'
28+
29+
promises.push(
30+
fs.stat(fullPath).then(() => {
31+
let ext = hasCssExtension ? '' : '.css'
32+
let path = isRelative ? params.uri : `./${params.uri}`
33+
rule.params = [`'${path}${ext}'`, ...rest].join(' ')
34+
}),
35+
)
36+
})
37+
38+
await Promise.allSettled(promises)
39+
}
40+
41+
return {
42+
postcssPlugin: '@tailwindcss/upgrade/migrate-import',
43+
OnceExit: migrate,
44+
}
45+
}

packages/tailwindcss/src/at-import.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export async function substituteAtImports(
6767
// `postcss-import` <https://github.com/postcss/postcss-import>
6868
// Copyright (c) 2014 Maxime Thirouin, Jason Campbell & Kevin Mårtensson
6969
// Released under the MIT License.
70-
function parseImportParams(params: ValueParser.ValueAstNode[]) {
70+
export function parseImportParams(params: ValueParser.ValueAstNode[]) {
7171
let uri
7272
let layer: string | null = null
7373
let media: string | null = null

0 commit comments

Comments
 (0)