diff --git a/CHANGELOG.md b/CHANGELOG.md index fc12e8684858..261e67188b24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix incorrectly named `bg-round` and `bg-space` utilities to `bg-repeat-round` to `bg-repeat-space` ([#15462](https://github.com/tailwindlabs/tailwindcss/pull/15462)) - Fix `inset-shadow-*` suggestions in IntelliSense ([#15471](https://github.com/tailwindlabs/tailwindcss/pull/15471)) - Only compile arbitrary values ending in `]` ([#15503](https://github.com/tailwindlabs/tailwindcss/pull/15503)) +- Improve performance and memory usage ([#15529](https://github.com/tailwindlabs/tailwindcss/pull/15529)) ### Changed @@ -781,3 +782,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [4.0.0-alpha.1] - 2024-03-06 - First 4.0.0-alpha.1 release + diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index 74a2bc63eb7c..2fe9d6901a4f 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -119,39 +119,49 @@ export function walk( path: AstNode[] }, ) => void | WalkAction, - parentPath: AstNode[] = [], + path: AstNode[] = [], context: Record = {}, ) { for (let i = 0; i < ast.length; i++) { let node = ast[i] - let path = [...parentPath, node] - let parent = parentPath.at(-1) ?? null + let parent = path[path.length - 1] ?? null // We want context nodes to be transparent in walks. This means that // whenever we encounter one, we immediately walk through its children and // furthermore we also don't update the parent. if (node.kind === 'context') { - if ( - walk(node.nodes, visit, parentPath, { ...context, ...node.context }) === WalkAction.Stop - ) { + if (walk(node.nodes, visit, path, { ...context, ...node.context }) === WalkAction.Stop) { return WalkAction.Stop } continue } + path.push(node) let status = visit(node, { parent, context, path, replaceWith(newNode) { - ast.splice(i, 1, ...(Array.isArray(newNode) ? newNode : [newNode])) + if (Array.isArray(newNode)) { + if (newNode.length === 0) { + ast.splice(i, 1) + } else if (newNode.length === 1) { + ast[i] = newNode[0] + } else { + ast.splice(i, 1, ...newNode) + } + } else { + ast[i] = newNode + } + // We want to visit the newly replaced node(s), which start at the // current index (i). By decrementing the index here, the next loop // will process this position (containing the replaced node) again. i-- }, }) ?? WalkAction.Continue + path.pop() // Stop the walk entirely if (status === WalkAction.Stop) return WalkAction.Stop @@ -160,7 +170,11 @@ export function walk( if (status === WalkAction.Skip) continue if (node.kind === 'rule' || node.kind === 'at-rule') { - if (walk(node.nodes, visit, path, context) === WalkAction.Stop) { + path.push(node) + let result = walk(node.nodes, visit, path, context) + path.pop() + + if (result === WalkAction.Stop) { return WalkAction.Stop } } @@ -179,32 +193,45 @@ export function walkDepth( replaceWith(newNode: AstNode[]): void }, ) => void, - parentPath: AstNode[] = [], + path: AstNode[] = [], context: Record = {}, ) { for (let i = 0; i < ast.length; i++) { let node = ast[i] - let path = [...parentPath, node] - let parent = parentPath.at(-1) ?? null + let parent = path[path.length - 1] ?? null if (node.kind === 'rule' || node.kind === 'at-rule') { + path.push(node) walkDepth(node.nodes, visit, path, context) + path.pop() } else if (node.kind === 'context') { - walkDepth(node.nodes, visit, parentPath, { ...context, ...node.context }) + walkDepth(node.nodes, visit, path, { ...context, ...node.context }) continue } + path.push(node) visit(node, { parent, context, path, replaceWith(newNode) { - ast.splice(i, 1, ...newNode) + if (Array.isArray(newNode)) { + if (newNode.length === 0) { + ast.splice(i, 1) + } else if (newNode.length === 1) { + ast[i] = newNode[0] + } else { + ast.splice(i, 1, ...newNode) + } + } else { + ast[i] = newNode + } // Skip over the newly inserted nodes (being depth-first it doesn't make sense to visit them) i += newNode.length - 1 }, }) + path.pop() } } diff --git a/packages/tailwindcss/src/compat/config/deep-merge.ts b/packages/tailwindcss/src/compat/config/deep-merge.ts index 3bbae51ddcef..2bf15c9b3b00 100644 --- a/packages/tailwindcss/src/compat/config/deep-merge.ts +++ b/packages/tailwindcss/src/compat/config/deep-merge.ts @@ -11,7 +11,7 @@ export function deepMerge( target: T, sources: (Partial | null | undefined)[], customizer: (a: any, b: any, keypath: (keyof T)[]) => any, - parentPath: (keyof T)[] = [], + path: (keyof T)[] = [], ) { type Key = keyof T type Value = T[Key] @@ -22,21 +22,17 @@ export function deepMerge( } for (let k of Reflect.ownKeys(source) as Key[]) { - let currentParentPath = [...parentPath, k] - let merged = customizer(target[k], source[k], currentParentPath) + path.push(k) + let merged = customizer(target[k], source[k], path) if (merged !== undefined) { target[k] = merged } else if (!isPlainObject(target[k]) || !isPlainObject(source[k])) { target[k] = source[k] as Value } else { - target[k] = deepMerge( - {}, - [target[k], source[k]], - customizer, - currentParentPath as any, - ) as Value + target[k] = deepMerge({}, [target[k], source[k]], customizer, path as any) as Value } + path.pop() } } diff --git a/packages/tailwindcss/src/compat/selector-parser.ts b/packages/tailwindcss/src/compat/selector-parser.ts index 7dc0b82f8f1a..f62e0aed4cec 100644 --- a/packages/tailwindcss/src/compat/selector-parser.ts +++ b/packages/tailwindcss/src/compat/selector-parser.ts @@ -96,7 +96,18 @@ export function walk( visit(node, { parent, replaceWith(newNode) { - ast.splice(i, 1, ...(Array.isArray(newNode) ? newNode : [newNode])) + if (Array.isArray(newNode)) { + if (newNode.length === 0) { + ast.splice(i, 1) + } else if (newNode.length === 1) { + ast[i] = newNode[0] + } else { + ast.splice(i, 1, ...newNode) + } + } else { + ast[i] = newNode + } + // We want to visit the newly replaced node(s), which start at the // current index (i). By decrementing the index here, the next loop // will process this position (containing the replaced node) again. diff --git a/packages/tailwindcss/src/value-parser.ts b/packages/tailwindcss/src/value-parser.ts index d51b793cb254..80a7827b2e8a 100644 --- a/packages/tailwindcss/src/value-parser.ts +++ b/packages/tailwindcss/src/value-parser.ts @@ -67,7 +67,18 @@ export function walk( visit(node, { parent, replaceWith(newNode) { - ast.splice(i, 1, ...(Array.isArray(newNode) ? newNode : [newNode])) + if (Array.isArray(newNode)) { + if (newNode.length === 0) { + ast.splice(i, 1) + } else if (newNode.length === 1) { + ast[i] = newNode[0] + } else { + ast.splice(i, 1, ...newNode) + } + } else { + ast[i] = newNode + } + // We want to visit the newly replaced node(s), which start at the // current index (i). By decrementing the index here, the next loop // will process this position (containing the replaced node) again.