Skip to content

Commit 1ab29dd

Browse files
committed
extends ctx of CSS AstNode's
The CSS AST has `context` nodes that you can access while walking the tree, where they build up information over time. Additionally, when looking at the `path` or the `parent` all the `context` nodes are hidden. This adds a small wrapper for the context object when walking over AstNode's and have to access the `.context`, `.parent` or `.path()`
1 parent e7d7f37 commit 1ab29dd

File tree

5 files changed

+61
-19
lines changed

5 files changed

+61
-19
lines changed

packages/tailwindcss/src/ast.test.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import { expect, it } from 'vitest'
2-
import { atRule, context, decl, optimizeAst, styleRule, toCss, type AstNode } from './ast'
2+
import {
3+
atRule,
4+
context,
5+
cssContext,
6+
decl,
7+
optimizeAst,
8+
styleRule,
9+
toCss,
10+
type AstNode,
11+
} from './ast'
312
import * as CSS from './css-parser'
413
import { buildDesignSystem } from './design-system'
514
import { Theme } from './theme'
@@ -39,23 +48,18 @@ it('allows the placement of context nodes', () => {
3948
let blueContext
4049
let greenContext
4150

42-
let walkContext: Record<string, string | boolean> = {}
43-
walk(ast, (node) => {
44-
if (node.kind === 'context') {
45-
walkContext = { ...walkContext, ...node.context }
46-
return
47-
}
48-
51+
walk(ast, (node, _ctx) => {
4952
if (node.kind !== 'declaration') return
53+
let ctx = cssContext(_ctx)
5054
switch (node.value) {
5155
case 'red':
52-
redContext = walkContext
56+
redContext = ctx.context
5357
break
5458
case 'blue':
55-
blueContext = walkContext
59+
blueContext = ctx.context
5660
break
5761
case 'green':
58-
greenContext = walkContext
62+
greenContext = ctx.context
5963
break
6064
}
6165
})

packages/tailwindcss/src/ast.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Theme, ThemeOptions } from './theme'
66
import { DefaultMap } from './utils/default-map'
77
import { extractUsedVariables } from './utils/variables'
88
import * as ValueParser from './value-parser'
9-
import { walk, WalkAction } from './walk'
9+
import { walk, WalkAction, type VisitContext } from './walk'
1010

1111
const AT_SIGN = 0x40
1212

@@ -185,6 +185,36 @@ export function cloneAstNode<T extends AstNode>(node: T): T {
185185
}
186186
}
187187

188+
export function cssContext(
189+
ctx: VisitContext<AstNode>,
190+
): VisitContext<AstNode> & { context: Record<string, string | boolean> } {
191+
return {
192+
depth: ctx.depth,
193+
get context() {
194+
let context: Record<string, string | boolean> = {}
195+
for (let child of ctx.path()) {
196+
if (child.kind === 'context') {
197+
Object.assign(context, child.context)
198+
}
199+
}
200+
201+
// Once computed, we never need to compute this again
202+
Object.defineProperty(this, 'context', { value: context })
203+
return context
204+
},
205+
get parent() {
206+
let parent = (this.path().pop() as Extract<AstNode, { nodes: AstNode[] }>) ?? null
207+
208+
// Once computed, we never need to compute this again
209+
Object.defineProperty(this, 'parent', { value: parent })
210+
return parent
211+
},
212+
path() {
213+
return ctx.path().filter((n) => n.kind !== 'context')
214+
},
215+
}
216+
}
217+
188218
// Optimize the AST for printing where all the special nodes that require custom
189219
// handling are handled such that the printing is a 1-to-1 transformation.
190220
export function optimizeAst(

packages/tailwindcss/src/compat/apply-compat-hooks.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Features } from '..'
2-
import { styleRule, toCss, walk, WalkAction, type AstNode } from '../ast'
2+
import { cssContext, styleRule, toCss, type AstNode } from '../ast'
33
import type { DesignSystem } from '../design-system'
44
import type { SourceLocation } from '../source-maps/source'
55
import { segment } from '../utils/segment'
6-
import { WalkAction } from '../walk'
6+
import { walk, WalkAction } from '../walk'
77
import { applyConfigToTheme } from './apply-config-to-theme'
88
import { applyKeyframesToTheme } from './apply-keyframes-to-theme'
99
import { createCompatConfig } from './config/create-compat-config'
@@ -51,8 +51,9 @@ export async function applyCompatibilityHooks({
5151
src: SourceLocation | undefined
5252
}[] = []
5353

54-
walk(ast, (node, ctx) => {
54+
walk(ast, (node, _ctx) => {
5555
if (node.kind !== 'at-rule') return
56+
let ctx = cssContext(_ctx)
5657

5758
// Collect paths from `@plugin` at-rules
5859
if (node.name === '@plugin') {
@@ -385,10 +386,12 @@ function upgradeToFullPluginSupport({
385386
if (typeof resolvedConfig.important === 'string') {
386387
let wrappingSelector = resolvedConfig.important
387388

388-
walk(ast, (node, ctx) => {
389+
walk(ast, (node, _ctx) => {
389390
if (node.kind !== 'at-rule') return
390391
if (node.name !== '@tailwind' || node.params !== 'utilities') return
391392

393+
let ctx = cssContext(_ctx)
394+
392395
// The AST node was already manually wrapped so there's nothing to do
393396
if (ctx.parent?.kind === 'rule' && ctx.parent.selector === wrappingSelector) {
394397
return WalkAction.Stop

packages/tailwindcss/src/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
comment,
77
context,
88
context as contextNode,
9+
cssContext,
910
decl,
1011
optimizeAst,
1112
rule,
@@ -165,8 +166,9 @@ async function parseCss(
165166
let root = null as Root
166167

167168
// Handle at-rules
168-
walk(ast, (node, ctx) => {
169+
walk(ast, (node, _ctx) => {
169170
if (node.kind !== 'at-rule') return
171+
let ctx = cssContext(_ctx)
170172

171173
// Find `@tailwind utilities` so that we can later replace it with the
172174
// actual generated utility class CSS.
@@ -460,7 +462,9 @@ async function parseCss(
460462

461463
if (child.name === '@tailwind' && child.params === 'utilities') {
462464
child.params += ` source(${path})`
463-
return WalkAction.ReplaceStop([contextNode({ sourceBase: context.base }, [child])])
465+
return WalkAction.ReplaceStop([
466+
contextNode({ sourceBase: ctx.context.base }, [child]),
467+
])
464468
}
465469
})
466470
}
@@ -475,6 +479,7 @@ async function parseCss(
475479
let hasReference = themeParams.includes('reference')
476480

477481
walk(node.nodes, (child) => {
482+
if (child.kind === 'context') return
478483
if (child.kind !== 'at-rule') {
479484
if (hasReference) {
480485
throw new Error(

packages/tailwindcss/src/walk.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type ExitResult<T> = Exclude<WalkResult<T>, { kind: WalkKind.Skip }>
3333

3434
type Parent<T> = T & { nodes: T[] }
3535

36-
interface VisitContext<N> {
36+
export interface VisitContext<N> {
3737
parent: Parent<N> | null
3838
depth: number
3939
path: () => N[]

0 commit comments

Comments
 (0)