From 68c32d1a6059ec77498cf273f24b213607128864 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Tue, 8 Oct 2024 13:47:55 +0200 Subject: [PATCH] Make custom `@at-root` private API (#14618) This PR makes the internal `@at-root` API private. Before this PR you could use `@at-root` in your own CSS, which means that it was part of the public API. If you (accidentally) used it in variants, you could generate CSS that was completely broken. This now introduces a new private `AtRoot` node (similar to the recently introduced `Context` node) and can only be constructed within the framework. --- packages/tailwindcss/src/ast.ts | 30 +++++++++++++++++++-------- packages/tailwindcss/src/compile.ts | 8 ++----- packages/tailwindcss/src/index.ts | 10 ++++----- packages/tailwindcss/src/utilities.ts | 6 +----- packages/tailwindcss/src/variants.ts | 11 ++++------ 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index c34a3d5af..e9d1ce48c 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -22,7 +22,12 @@ export type Context = { nodes: AstNode[] } -export type AstNode = Rule | Declaration | Comment | Context +export type AtRoot = { + kind: 'at-root' + nodes: AstNode[] +} + +export type AstNode = Rule | Declaration | Comment | Context | AtRoot export function rule(selector: string, nodes: AstNode[]): Rule { return { @@ -56,6 +61,13 @@ export function context(context: Record, nodes: AstNode[]): Cont } } +export function atRoot(nodes: AstNode[]): AtRoot { + return { + kind: 'at-root', + nodes, + } +} + export enum WalkAction { /** Continue walking, which is the default */ Continue, @@ -128,14 +140,6 @@ export function toCss(ast: AstNode[]) { // Rule if (node.kind === 'rule') { - // Pull out `@at-root` rules to append later - if (node.selector === '@at-root') { - for (let child of node.nodes) { - atRoots += stringify(child, 0) - } - return css - } - if (node.selector === '@tailwind utilities') { for (let child of node.nodes) { css += stringify(child, depth) @@ -204,6 +208,14 @@ export function toCss(ast: AstNode[]) { } } + // AtRoot Node + else if (node.kind === 'at-root') { + for (let child of node.nodes) { + atRoots += stringify(child, 0) + } + return css + } + // Declaration else if (node.property !== '--tw-sort' && node.value !== undefined && node.value !== null) { css += `${indent}${node.property}: ${node.value}${node.important ? ' !important' : ''};\n` diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts index f7d9c5125..87b5133da 100644 --- a/packages/tailwindcss/src/compile.ts +++ b/packages/tailwindcss/src/compile.ts @@ -293,9 +293,9 @@ function compileBaseUtility(candidate: Candidate, designSystem: DesignSystem) { function applyImportant(ast: AstNode[]): void { for (let node of ast) { - // Skip any `@at-root` rules — we don't want to make the contents of things + // Skip any `AtRoot` nodes — we don't want to make the contents of things // like `@keyframes` or `@property` important. - if (node.kind === 'rule' && node.selector === '@at-root') { + if (node.kind === 'at-root') { continue } @@ -328,10 +328,6 @@ function getPropertySort(nodes: AstNode[]) { let idx = GLOBAL_PROPERTY_ORDER.indexOf(node.property) if (idx !== -1) propertySort.add(idx) } else if (node.kind === 'rule') { - // Don't consider properties within `@at-root` when determining the sort - // order for a rule. - if (node.selector === '@at-root') continue - for (let child of node.nodes) { q.push(child) } diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 2d247b3aa..742f459cc 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -1,6 +1,7 @@ import { version } from '../package.json' import { substituteAtApply } from './apply' import { + atRoot, comment, context, decl, @@ -361,13 +362,10 @@ async function parseCss( continue } - // Wrap `@keyframes` in `@at-root` so they are hoisted out of `:root` - // when printing. + // Wrap `@keyframes` in `AtRoot` so they are hoisted out of `:root` when + // printing. nodes.push( - Object.assign(keyframesRule, { - selector: '@at-root', - nodes: [rule(keyframesRule.selector, keyframesRule.nodes)], - }), + Object.assign(keyframesRule, atRoot([rule(keyframesRule.selector, keyframesRule.nodes)])), ) } } diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index e6e064b0e..a308b49c3 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -1,4 +1,4 @@ -import { decl, rule, type AstNode, type Rule } from './ast' +import { atRoot, decl, rule, type AstNode } from './ast' import type { Candidate, CandidateModifier, NamedUtilityValue } from './candidate' import type { Theme, ThemeKey } from './theme' import { DefaultMap } from './utils/default-map' @@ -84,10 +84,6 @@ export class Utilities { } } -function atRoot(rules: Rule[]) { - return rule('@at-root', rules) -} - function property(ident: string, initialValue?: string, syntax?: string) { return rule(`@property ${ident}`, [ decl('syntax', syntax ? `"${syntax}"` : `"*"`), diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts index 968f8d7a1..693222860 100644 --- a/packages/tailwindcss/src/variants.ts +++ b/packages/tailwindcss/src/variants.ts @@ -1,4 +1,4 @@ -import { WalkAction, decl, rule, walk, type AstNode, type Rule } from './ast' +import { WalkAction, atRoot, decl, rule, walk, type AstNode, type Rule } from './ast' import { type Variant } from './candidate' import type { Theme } from './theme' import { DefaultMap } from './utils/default-map' @@ -380,7 +380,7 @@ export function createVariants(theme: Theme): Variants { { function contentProperties() { - return rule('@at-root', [ + return atRoot([ rule('@property --tw-content', [ decl('syntax', '"*"'), decl('initial-value', '""'), @@ -933,16 +933,13 @@ export function substituteAtSlot(ast: AstNode[], nodes: AstNode[]) { replaceWith(nodes) } - // Wrap `@keyframes` and `@property` in `@at-root` + // Wrap `@keyframes` and `@property` in `AtRoot` nodes else if ( node.kind === 'rule' && node.selector[0] === '@' && (node.selector.startsWith('@keyframes ') || node.selector.startsWith('@property ')) ) { - Object.assign(node, { - selector: '@at-root', - nodes: [rule(node.selector, node.nodes)], - }) + Object.assign(node, atRoot([rule(node.selector, node.nodes)])) return WalkAction.Skip } })