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.
This commit is contained in:
Robin Malfait 2024-10-08 13:47:55 +02:00 committed by GitHub
parent 7be5346e2e
commit 68c32d1a60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 32 additions and 33 deletions

View File

@ -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<string, string>, 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`

View File

@ -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)
}

View File

@ -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)])),
)
}
}

View File

@ -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}"` : `"*"`),

View File

@ -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
}
})