diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts index 82a2b8592..24b72ce42 100644 --- a/packages/tailwindcss/src/variants.ts +++ b/packages/tailwindcss/src/variants.ts @@ -447,6 +447,7 @@ export function createVariants(theme: Theme): Variants { if (variant.modifier) return null let didApply = false + let replacement: AstNode | null = null walk([ruleNode], (node, ctx) => { if (node.kind !== 'rule' && node.kind !== 'at-rule') return WalkAction.Continue @@ -492,7 +493,7 @@ export function createVariants(theme: Theme): Variants { rules.push(negatedAtRule) } - Object.assign(ruleNode, styleRule('&', rules)) + replacement = styleRule('&', rules) // Track that the variant was actually applied didApply = true @@ -500,6 +501,10 @@ export function createVariants(theme: Theme): Variants { return WalkAction.Skip }) + if (replacement) { + Object.assign(ruleNode, replacement) + } + // TODO: Tweak group, peer, has to ignore intermediate `&` selectors (maybe?) if (ruleNode.kind === 'rule' && ruleNode.selector === '&' && ruleNode.nodes.length === 1) { Object.assign(ruleNode, ruleNode.nodes[0]) diff --git a/packages/tailwindcss/src/walk.ts b/packages/tailwindcss/src/walk.ts index ccb384256..daef318d7 100644 --- a/packages/tailwindcss/src/walk.ts +++ b/packages/tailwindcss/src/walk.ts @@ -57,36 +57,44 @@ function walkImplementation( enter: (node: T, ctx: VisitContext) => EnterResult | void = () => WalkAction.Continue, exit: (node: T, ctx: VisitContext) => ExitResult | void = () => WalkAction.Continue, ) { - let stack: [nodes: T[], offset: number, parent: Parent | null][] = [[ast, 0, null]] + let surrogate = { nodes: ast } as Parent + + // Reduce memory usage by tracking 2 different objects instead of a single + // stack data structure. We could use 2 arrays, but objects are faster in Bun. + // In Node.js the 2 arrays or 2 objects have similar performance. + // + // Used indexing to prevent `push()` / `pop()` overhead. + let offsets: Record = { 0: 0 } + let parents: Record> = { 0: surrogate } + + let depth = 0 + let ctx: VisitContext = { parent: null, depth: 0, path() { let path: T[] = [] - for (let i = 1; i < stack.length; i++) { - let parent = stack[i][2] - if (parent) path.push(parent) + for (let i = 1; i <= depth; i++) { + path.push(parents[i]) } return path }, } - while (stack.length > 0) { - let depth = stack.length - 1 - let frame = stack[depth] - let nodes = frame[0] - let offset = frame[1] - let parent = frame[2] + while (depth >= 0) { + let offset = offsets[depth] + let parent = parents[depth] + let nodes = parent.nodes // Done with this level if (offset >= nodes.length) { - stack.pop() + depth-- continue } - ctx.parent = parent + ctx.parent = depth === 0 ? null : parent ctx.depth = depth // Enter phase (offsets are positive) @@ -96,11 +104,13 @@ function walkImplementation( switch (result.kind) { case WalkKind.Continue: { - if (node.nodes && node.nodes.length > 0) { - stack.push([node.nodes, 0, node as Parent]) - } + offsets[depth] = ~offset // Prepare for exit phase, same offset - frame[1] = ~offset // Prepare for exit phase, same offset + if (node.nodes && node.nodes.length > 0) { + depth++ + offsets[depth] = 0 + parents[depth] = node as Parent + } continue } @@ -108,7 +118,7 @@ function walkImplementation( return // Stop immediately case WalkKind.Skip: { - frame[1] = ~offset // Prepare for exit phase, same offset + offsets[depth] = ~offset // Prepare for exit phase, same offset continue } @@ -124,7 +134,7 @@ function walkImplementation( case WalkKind.ReplaceSkip: { nodes.splice(offset, 1, ...result.nodes) - frame[1] += result.nodes.length // Advance to next sibling past replacements + offsets[depth] += result.nodes.length // Advance to next sibling past replacements continue } @@ -146,7 +156,7 @@ function walkImplementation( switch (result.kind) { case WalkKind.Continue: - frame[1] = index + 1 // Advance to next sibling + offsets[depth] = index + 1 // Advance to next sibling continue case WalkKind.Stop: @@ -154,7 +164,7 @@ function walkImplementation( case WalkKind.Replace: { nodes.splice(index, 1, ...result.nodes) - frame[1] = index + result.nodes.length // Advance to next sibling past replacements + offsets[depth] = index + result.nodes.length // Advance to next sibling past replacements continue } @@ -165,7 +175,7 @@ function walkImplementation( case WalkKind.ReplaceSkip: { nodes.splice(index, 1, ...result.nodes) - frame[1] = index + result.nodes.length // Advance to next sibling past replacements + offsets[depth] = index + result.nodes.length // Advance to next sibling past replacements continue }