Skip to content

Commit a1d56d8

Browse files
Ensure content globs defined in @config files are relative to that file (#14314)
When you configure custom content globs inside an `@config` file, we want to tread these globs as being relative to that config file and not the CSS file that requires the content file. A config can be used by multiple CSS configs. --------- Co-authored-by: Adam Wathan <[email protected]>
1 parent dcfaaac commit a1d56d8

File tree

10 files changed

+115
-24
lines changed

10 files changed

+115
-24
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
- Nothing yet!
10+
### Fixed
11+
12+
- Ensure content globs defined in `@config` files are relative to that file ([#14314](https://github.com/tailwindlabs/tailwindcss/pull/14314))
1113

1214
## [4.0.0-alpha.21] - 2024-09-02
1315

integrations/cli/index.test.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,24 @@ describe.each([
5252
addVariant('hocus', ['&:focus', '&:hover'])
5353
}
5454
`,
55+
'project-a/tailwind.config.js': js`
56+
module.exports = {
57+
content: ['../project-b/src/**/*.js'],
58+
}
59+
`,
5560
'project-a/src/index.css': css`
5661
@import 'tailwindcss/utilities';
57-
@source '../../project-b/src/**/*.js';
62+
@config '../tailwind.config.js';
63+
@source '../../project-b/src/**/*.html';
5864
@plugin '../plugin.js';
5965
`,
6066
'project-a/src/index.js': js`
6167
const className = "content-['project-a/src/index.js']"
6268
module.exports = { className }
6369
`,
70+
'project-b/src/index.html': html`
71+
<div class="flex" />
72+
`,
6473
'project-b/src/index.js': js`
6574
const className = "content-['project-b/src/index.js']"
6675
module.exports = { className }
@@ -74,6 +83,7 @@ describe.each([
7483

7584
await fs.expectFileToContain('project-a/dist/out.css', [
7685
candidate`underline`,
86+
candidate`flex`,
7787
candidate`content-['project-a/src/index.js']`,
7888
candidate`content-['project-b/src/index.js']`,
7989
candidate`inverted:flex`,
@@ -111,15 +121,24 @@ describe.each([
111121
addVariant('hocus', ['&:focus', '&:hover'])
112122
}
113123
`,
124+
'project-a/tailwind.config.js': js`
125+
module.exports = {
126+
content: ['../project-b/src/**/*.js'],
127+
}
128+
`,
114129
'project-a/src/index.css': css`
115130
@import 'tailwindcss/utilities';
116-
@source '../../project-b/src/**/*.js';
131+
@config '../tailwind.config.js';
132+
@source '../../project-b/src/**/*.html';
117133
@plugin '../plugin.js';
118134
`,
119135
'project-a/src/index.js': js`
120136
const className = "content-['project-a/src/index.js']"
121137
module.exports = { className }
122138
`,
139+
'project-b/src/index.html': html`
140+
<div class="flex" />
141+
`,
123142
'project-b/src/index.js': js`
124143
const className = "content-['project-b/src/index.js']"
125144
module.exports = { className }
@@ -133,6 +152,7 @@ describe.each([
133152

134153
await fs.expectFileToContain('project-a/dist/out.css', [
135154
candidate`underline`,
155+
candidate`flex`,
136156
candidate`content-['project-a/src/index.js']`,
137157
candidate`content-['project-b/src/index.js']`,
138158
candidate`inverted:flex`,

integrations/postcss/index.test.ts

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,24 @@ test(
3939
addVariant('hocus', ['&:focus', '&:hover'])
4040
}
4141
`,
42+
'project-a/tailwind.config.js': js`
43+
module.exports = {
44+
content: ['../project-b/src/**/*.js'],
45+
}
46+
`,
4247
'project-a/src/index.css': css`
4348
@import 'tailwindcss/utilities';
44-
@source '../../project-b/src/**/*.js';
49+
@config '../tailwind.config.js';
50+
@source '../../project-b/src/**/*.html';
4551
@plugin '../plugin.js';
4652
`,
4753
'project-a/src/index.js': js`
4854
const className = "content-['a/src/index.js']"
4955
module.exports = { className }
5056
`,
57+
'project-b/src/index.html': html`
58+
<div class="flex" />
59+
`,
5160
'project-b/src/index.js': js`
5261
const className = "content-['b/src/index.js']"
5362
module.exports = { className }
@@ -61,6 +70,7 @@ test(
6170

6271
await fs.expectFileToContain('project-a/dist/out.css', [
6372
candidate`underline`,
73+
candidate`flex`,
6474
candidate`content-['a/src/index.js']`,
6575
candidate`content-['b/src/index.js']`,
6676
candidate`inverted:flex`,
@@ -106,15 +116,24 @@ test(
106116
addVariant('hocus', ['&:focus', '&:hover'])
107117
}
108118
`,
119+
'project-a/tailwind.config.js': js`
120+
module.exports = {
121+
content: ['../project-b/src/**/*.js'],
122+
}
123+
`,
109124
'project-a/src/index.css': css`
110125
@import 'tailwindcss/utilities';
111-
@source '../../project-b/src/**/*.js';
126+
@config '../tailwind.config.js';
127+
@source '../../project-b/src/**/*.html';
112128
@plugin '../plugin.js';
113129
`,
114130
'project-a/src/index.js': js`
115131
const className = "content-['a/src/index.js']"
116132
module.exports = { className }
117133
`,
134+
'project-b/src/index.html': html`
135+
<div class="flex" />
136+
`,
118137
'project-b/src/index.js': js`
119138
const className = "content-['b/src/index.js']"
120139
module.exports = { className }
@@ -128,6 +147,7 @@ test(
128147

129148
await fs.expectFileToContain('project-a/dist/out.css', [
130149
candidate`underline`,
150+
candidate`flex`,
131151
candidate`content-['a/src/index.js']`,
132152
candidate`content-['b/src/index.js']`,
133153
candidate`inverted:flex`,
@@ -173,15 +193,24 @@ test(
173193
addVariant('hocus', ['&:focus', '&:hover'])
174194
}
175195
`,
196+
'project-a/tailwind.config.js': js`
197+
module.exports = {
198+
content: ['../project-b/src/**/*.js'],
199+
}
200+
`,
176201
'project-a/src/index.css': css`
177202
@import 'tailwindcss/utilities';
178-
@source '../../project-b/src/**/*.js';
203+
@config '../tailwind.config.js';
204+
@source '../../project-b/src/**/*.html';
179205
@plugin '../plugin.js';
180206
`,
181207
'project-a/src/index.js': js`
182208
const className = "content-['a/src/index.js']"
183209
module.exports = { className }
184210
`,
211+
'project-b/src/index.html': html`
212+
<div class="flex" />
213+
`,
185214
'project-b/src/index.js': js`
186215
const className = "content-['b/src/index.js']"
187216
module.exports = { className }
@@ -195,6 +224,7 @@ test(
195224

196225
await fs.expectFileToContain('project-a/dist/out.css', [
197226
candidate`underline`,
227+
candidate`flex`,
198228
candidate`content-['a/src/index.js']`,
199229
candidate`content-['b/src/index.js']`,
200230
candidate`inverted:flex`,
@@ -241,15 +271,24 @@ test(
241271
addVariant('hocus', ['&:focus', '&:hover'])
242272
}
243273
`,
274+
'project-a/tailwind.config.js': js`
275+
module.exports = {
276+
content: ['../project-b/src/**/*.js'],
277+
}
278+
`,
244279
'project-a/src/index.css': css`
245280
@import 'tailwindcss/utilities';
246-
@source '../../project-b/src/**/*.js';
281+
@config '../tailwind.config.js';
282+
@source '../../project-b/src/**/*.html';
247283
@plugin '../plugin.js';
248284
`,
249285
'project-a/src/index.js': js`
250286
const className = "content-['a/src/index.js']"
251287
module.exports = { className }
252288
`,
289+
'project-b/src/index.html': html`
290+
<div class="flex" />
291+
`,
253292
'project-b/src/index.js': js`
254293
const className = "content-['b/src/index.js']"
255294
module.exports = { className }
@@ -265,6 +304,7 @@ test(
265304

266305
await fs.expectFileToContain('project-a/dist/out.css', [
267306
candidate`underline`,
307+
candidate`flex`,
268308
candidate`content-['a/src/index.js']`,
269309
candidate`content-['b/src/index.js']`,
270310
candidate`inverted:flex`,

integrations/vite/index.test.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,19 @@ test(
5252
<div class="underline m-2">Hello, world!</div>
5353
</body>
5454
`,
55+
'project-a/tailwind.config.js': js`
56+
export default {
57+
content: ['../project-b/src/**/*.js'],
58+
}
59+
`,
5560
'project-a/src/index.css': css`
5661
@import 'tailwindcss/theme' theme(reference);
5762
@import 'tailwindcss/utilities';
58-
@source '../../project-b/src/**/*.js';
63+
@config '../tailwind.config.js';
64+
@source '../../project-b/src/**/*.html';
65+
`,
66+
'project-b/src/index.html': html`
67+
<div class="flex" />
5968
`,
6069
'project-b/src/index.js': js`
6170
const className = "content-['project-b/src/index.js']"
@@ -72,6 +81,7 @@ test(
7281

7382
await fs.expectFileToContain(filename, [
7483
candidate`underline`,
84+
candidate`flex`,
7585
candidate`m-2`,
7686
candidate`content-['project-b/src/index.js']`,
7787
])
@@ -125,10 +135,19 @@ test(
125135
<div class="font-bold ">Tailwind Labs</div>
126136
</body>
127137
`,
138+
'project-a/tailwind.config.js': js`
139+
export default {
140+
content: ['../project-b/src/**/*.js'],
141+
}
142+
`,
128143
'project-a/src/index.css': css`
129144
@import 'tailwindcss/theme' theme(reference);
130145
@import 'tailwindcss/utilities';
131-
@source '../../project-b/src/**/*.js';
146+
@config '../tailwind.config.js';
147+
@source '../../project-b/src/**/*.html';
148+
`,
149+
'project-b/src/index.html': html`
150+
<div class="flex" />
132151
`,
133152
'project-b/src/index.js': js`
134153
const className = "content-['project-b/src/index.js']"
@@ -147,6 +166,7 @@ test(
147166
await retryAssertion(async () => {
148167
let css = await fetchStyles(port, '/index.html')
149168
expect(css).toContain(candidate`underline`)
169+
expect(css).toContain(candidate`flex`)
150170
expect(css).not.toContain(candidate`font-bold`)
151171
})
152172

@@ -155,6 +175,7 @@ test(
155175
await retryAssertion(async () => {
156176
let css = await fetchStyles(port, '/about.html')
157177
expect(css).toContain(candidate`underline`)
178+
expect(css).toContain(candidate`flex`)
158179
expect(css).toContain(candidate`font-bold`)
159180
})
160181

packages/@tailwindcss-cli/src/commands/build/index.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,10 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
143143
let compiler = await createCompiler(input)
144144
let scanner = new Scanner({
145145
detectSources: { base },
146-
sources: compiler.globs.map((pattern) => ({
147-
base: inputBasePath, // Globs are relative to the input.css file
146+
sources: compiler.globs.map(({ origin, pattern }) => ({
147+
// Ensure the glob is relative to the input CSS file or the config file
148+
// where it is specified.
149+
base: origin ? path.dirname(path.resolve(inputBasePath, origin)) : inputBasePath,
148150
pattern,
149151
})),
150152
})
@@ -212,8 +214,10 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
212214
// Re-scan the directory to get the new `candidates`
213215
scanner = new Scanner({
214216
detectSources: { base },
215-
sources: compiler.globs.map((pattern) => ({
216-
base: inputBasePath, // Globs are relative to the input.css file
217+
sources: compiler.globs.map(({ origin, pattern }) => ({
218+
// Ensure the glob is relative to the input CSS file or the
219+
// config file where it is specified.
220+
base: origin ? path.dirname(path.resolve(inputBasePath, origin)) : inputBasePath,
217221
pattern,
218222
})),
219223
})

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,10 @@ function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
141141
// Look for candidates used to generate the CSS
142142
let scanner = new Scanner({
143143
detectSources: { base },
144-
sources: context.compiler.globs.map((pattern) => ({
145-
base: inputBasePath, // Globs are relative to the input.css file
144+
sources: context.compiler.globs.map(({ origin, pattern }) => ({
145+
// Ensure the glob is relative to the input CSS file or the config
146+
// file where it is specified.
147+
base: origin ? path.dirname(path.resolve(inputBasePath, origin)) : inputBasePath,
146148
pattern,
147149
})),
148150
})

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,10 @@ export default function tailwindcss(): Plugin[] {
9393
})
9494

9595
scanner = new Scanner({
96-
sources: globs.map((pattern) => ({
97-
base: inputBasePath, // Globs are relative to the input.css file
96+
sources: globs.map(({ origin, pattern }) => ({
97+
// Ensure the glob is relative to the input CSS file or the config file
98+
// where it is specified.
99+
base: origin ? path.dirname(path.resolve(inputBasePath, origin)) : inputBasePath,
98100
pattern,
99101
})),
100102
})

packages/tailwindcss/src/compat/config.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ test('Config files can add content', async ({ expect }) => {
1414
loadConfig: async () => ({ content: ['./file.txt'] }),
1515
})
1616

17-
expect(compiler.globs).toEqual(['./file.txt'])
17+
expect(compiler.globs).toEqual([{ origin: './config.js', pattern: './file.txt' }])
1818
})
1919

2020
test('Config files can change dark mode (media)', async ({ expect }) => {

packages/tailwindcss/src/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,7 +1776,7 @@ describe('@source', () => {
17761776
@source "./foo/bar/*.ts";
17771777
`)
17781778

1779-
expect(globs).toEqual(['./foo/bar/*.ts'])
1779+
expect(globs).toEqual([{ pattern: './foo/bar/*.ts' }])
17801780
})
17811781

17821782
test('emits multiple @source files', async () => {
@@ -1785,7 +1785,7 @@ describe('@source', () => {
17851785
@source "./php/secr3t/smarty.php";
17861786
`)
17871787

1788-
expect(globs).toEqual(['./foo/**/*.ts', './php/secr3t/smarty.php'])
1788+
expect(globs).toEqual([{ pattern: './foo/**/*.ts' }, { pattern: './php/secr3t/smarty.php' }])
17891789
})
17901790
})
17911791

packages/tailwindcss/src/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ async function parseCss(
5454
let customUtilities: ((designSystem: DesignSystem) => void)[] = []
5555
let firstThemeRule: Rule | null = null
5656
let keyframesRules: Rule[] = []
57-
let globs: string[] = []
57+
let globs: { origin?: string; pattern: string }[] = []
5858

5959
walk(ast, (node, { parent, replaceWith }) => {
6060
if (node.kind !== 'rule') return
@@ -178,7 +178,7 @@ async function parseCss(
178178
) {
179179
throw new Error('`@source` paths must be quoted.')
180180
}
181-
globs.push(path.slice(1, -1))
181+
globs.push({ pattern: path.slice(1, -1) })
182182
replaceWith([])
183183
return
184184
}
@@ -398,7 +398,7 @@ async function parseCss(
398398
)
399399
}
400400

401-
globs.push(file.pattern)
401+
globs.push({ origin: file.base, pattern: file.pattern })
402402
}
403403

404404
return {
@@ -413,7 +413,7 @@ export async function compile(
413413
css: string,
414414
opts: CompileOptions = {},
415415
): Promise<{
416-
globs: string[]
416+
globs: { origin?: string; pattern: string }[]
417417
build(candidates: string[]): string
418418
}> {
419419
let { designSystem, ast, globs, pluginApi } = await parseCss(css, opts)

0 commit comments

Comments
 (0)