Skip to content

Commit 436be33

Browse files
committed
handle source(…)
Forward it from `@media source(…)` to `@tailwind utilities source(…)`. Next, handle `@tailwind utilities source(…)` and retrieve the path.
1 parent 5700c5d commit 436be33

File tree

2 files changed

+77
-25
lines changed

2 files changed

+77
-25
lines changed

packages/tailwindcss/src/at-import.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,12 @@ export async function substituteAtImports(
4444
let ast = CSS.parse(loaded.content)
4545
await substituteAtImports(ast, loaded.base, loadStylesheet, recurseCount + 1)
4646

47-
contextNode.nodes = buildImportNodes(ast, layer, media, supports)
48-
contextNode.context.base = loaded.base
47+
contextNode.nodes = buildImportNodes(
48+
[context({ base: loaded.base }, ast)],
49+
layer,
50+
media,
51+
supports,
52+
)
4953
})(),
5054
)
5155

packages/tailwindcss/src/index.ts

Lines changed: 71 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { substituteAtApply } from './apply'
33
import {
44
atRoot,
55
comment,
6-
context,
6+
context as contextNode,
77
decl,
88
rule,
99
toCss,
@@ -72,22 +72,64 @@ async function parseCss(
7272
loadStylesheet = throwOnLoadStylesheet,
7373
}: CompileOptions = {},
7474
) {
75-
let ast = [context({ base }, CSS.parse(css))] as AstNode[]
75+
let ast = [contextNode({ base }, CSS.parse(css))] as AstNode[]
7676

7777
await substituteAtImports(ast, base, loadStylesheet)
7878

79-
let important: boolean | null = null
79+
let important = null as boolean | null
8080
let theme = new Theme()
8181
let customVariants: ((designSystem: DesignSystem) => void)[] = []
8282
let customUtilities: ((designSystem: DesignSystem) => void)[] = []
83-
let firstThemeRule: Rule | null = null
83+
let firstThemeRule = null as Rule | null
84+
let utilitiesNode = null as Rule | null
8485
let globs: { base: string; pattern: string }[] = []
86+
let root:
87+
| null // Unknown root
88+
| 'none' // Explicitly no root specified via `source(none)`
89+
// Specified via `source(…)`, relative to the `base`
90+
| { base: string; pattern: string } = null
8591

8692
// Handle at-rules
8793
walk(ast, (node, { parent, replaceWith, context }) => {
8894
if (node.kind !== 'rule') return
8995
if (node.selector[0] !== '@') return
9096

97+
// Find `@tailwind utilities` so that we can later replace it with the
98+
// actual generated utility class CSS.
99+
if (
100+
utilitiesNode === null &&
101+
(node.selector === '@tailwind utilities' || node.selector.startsWith('@tailwind utilities '))
102+
) {
103+
let params = segment(node.selector.slice(20).trim(), ' ')
104+
for (let param of params) {
105+
if (param.startsWith('source(')) {
106+
let path = param.slice(7, -1)
107+
108+
// Keyword: `source(none)`
109+
if (path === 'none') {
110+
root = path
111+
continue
112+
}
113+
114+
// Explicit path: `source('…')`
115+
if (
116+
(path[0] === '"' && path[path.length - 1] !== '"') ||
117+
(path[0] === "'" && path[path.length - 1] !== "'") ||
118+
(path[0] !== "'" && path[0] !== '"')
119+
) {
120+
throw new Error('`source(…)` paths must be quoted.')
121+
}
122+
123+
root = {
124+
base: context.sourceBase ?? context.base,
125+
pattern: path.slice(1, -1),
126+
}
127+
}
128+
}
129+
130+
utilitiesNode = node
131+
}
132+
91133
// Collect custom `@utility` at-rules
92134
if (node.selector.startsWith('@utility ')) {
93135
if (parent !== null) {
@@ -202,12 +244,26 @@ async function parseCss(
202244
let unknownParams: string[] = []
203245

204246
for (let param of params) {
247+
// Handle `@media source(…)`
248+
if (param.startsWith('source(')) {
249+
let path = param.slice(7, -1)
250+
251+
walk(node.nodes, (child, { replaceWith }) => {
252+
if (child.kind !== 'rule') return
253+
if (child.selector === '@tailwind utilities') {
254+
child.selector += ` source(${path})`
255+
replaceWith([contextNode({ sourceBase: context.base }, [child])])
256+
return WalkAction.Stop
257+
}
258+
})
259+
}
260+
205261
// Handle `@media theme(…)`
206262
//
207263
// We support `@import "tailwindcss/theme" theme(reference)` as a way to
208264
// import an external theme file as a reference, which becomes `@media
209265
// theme(reference) { … }` when the `@import` is processed.
210-
if (param.startsWith('theme(')) {
266+
else if (param.startsWith('theme(')) {
211267
let themeParams = param.slice(6, -1)
212268

213269
walk(node.nodes, (child) => {
@@ -389,6 +445,8 @@ async function parseCss(
389445
designSystem,
390446
ast,
391447
globs,
448+
root,
449+
utilitiesNode,
392450
}
393451
}
394452

@@ -397,24 +455,13 @@ export async function compile(
397455
opts: CompileOptions = {},
398456
): Promise<{
399457
globs: { base: string; pattern: string }[]
458+
root:
459+
| null // Unknown root
460+
| 'none' // Explicitly no root specified via `source(none)`
461+
| { base: string; pattern: string } // Specified via `source(…)`, relative to the `base`
400462
build(candidates: string[]): string
401463
}> {
402-
let { designSystem, ast, globs } = await parseCss(css, opts)
403-
404-
let tailwindUtilitiesNode: Rule | null = null
405-
406-
// Find `@tailwind utilities` so that we can later replace it with the actual
407-
// generated utility class CSS.
408-
walk(ast, (node) => {
409-
if (node.kind === 'rule' && node.selector === '@tailwind utilities') {
410-
tailwindUtilitiesNode = node
411-
412-
// Stop walking after finding `@tailwind utilities` to avoid walking all
413-
// of the generated CSS. This means `@tailwind utilities` can only appear
414-
// once per file but that's the intended usage at this point in time.
415-
return WalkAction.Stop
416-
}
417-
})
464+
let { designSystem, ast, globs, root, utilitiesNode } = await parseCss(css, opts)
418465

419466
if (process.env.NODE_ENV !== 'test') {
420467
ast.unshift(comment(`! tailwindcss v${version} | MIT License | https://tailwindcss.com `))
@@ -434,6 +481,7 @@ export async function compile(
434481

435482
return {
436483
globs,
484+
root,
437485
build(newRawCandidates: string[]) {
438486
let didChange = false
439487

@@ -452,7 +500,7 @@ export async function compile(
452500
return compiledCss
453501
}
454502

455-
if (tailwindUtilitiesNode) {
503+
if (utilitiesNode) {
456504
let newNodes = compileCandidates(allValidCandidates, designSystem, {
457505
onInvalidCandidate,
458506
}).astNodes
@@ -466,7 +514,7 @@ export async function compile(
466514

467515
previousAstNodeCount = newNodes.length
468516

469-
tailwindUtilitiesNode.nodes = newNodes
517+
utilitiesNode.nodes = newNodes
470518
compiledCss = toCss(ast)
471519
}
472520

0 commit comments

Comments
 (0)