From f2b8f19fb5bc23f1c5a98c082405a4e7f96ad7ae Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 23 Oct 2024 12:34:52 +0200 Subject: [PATCH 1/3] refactor `@media` handling Instead of re-parsing the `@media`, we parse it once and handle each param individually. --- packages/tailwindcss/src/index.ts | 102 ++++++++++++++---------------- 1 file changed, 47 insertions(+), 55 deletions(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index e4e46b0cf854..f62f6e7f975f 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -196,70 +196,62 @@ async function parseCss( } } - // Drop instances of `@media theme(…)` - // - // We support `@import "tailwindcss/theme" theme(reference)` as a way to - // import an external theme file as a reference, which becomes `@media - // theme(reference) { … }` when the `@import` is processed. - if (node.selector.startsWith('@media theme(')) { - let themeParams = node.selector.slice(13, -1) - - walk(node.nodes, (child) => { - if (child.kind !== 'rule') { - throw new Error( - 'Files imported with `@import "…" theme(…)` must only contain `@theme` blocks.', - ) - } - if (child.selector === '@theme' || child.selector.startsWith('@theme ')) { - child.selector += ' ' + themeParams - return WalkAction.Skip - } - }) - replaceWith(node.nodes) - return WalkAction.Skip - } + if (node.selector[0] === '@' && node.selector.startsWith('@media ')) { + let params = segment(node.selector.slice(7), ' ') + let unknownParams: string[] = [] - // Drop instances of `@media prefix(…)` - // - // We support `@import "tailwindcss" prefix(ident)` as a way to - // configure a theme prefix for variables and utilities. - if (node.selector.startsWith('@media prefix(')) { - let themeParams = node.selector.slice(7) - - walk(node.nodes, (child) => { - if (child.kind !== 'rule') return - if (child.selector === '@theme' || child.selector.startsWith('@theme ')) { - child.selector += ' ' + themeParams - return WalkAction.Skip + for (let param of params) { + // Handle `@media theme(…)` + // + // We support `@import "tailwindcss/theme" theme(reference)` as a way to + // import an external theme file as a reference, which becomes `@media + // theme(reference) { … }` when the `@import` is processed. + if (param.startsWith('theme(')) { + let themeParams = param.slice(6, -1) + + walk(node.nodes, (child) => { + if (child.kind !== 'rule') { + throw new Error( + 'Files imported with `@import "…" theme(…)` must only contain `@theme` blocks.', + ) + } + if (child.selector === '@theme' || child.selector.startsWith('@theme ')) { + child.selector += ' ' + themeParams + return WalkAction.Skip + } + }) } - }) - replaceWith(node.nodes) - return WalkAction.Skip - } - if (node.selector.startsWith('@media')) { - let features = segment(node.selector.slice(6), ' ') - let shouldReplace = true + // Handle `@media prefix(…)` + // + // We support `@import "tailwindcss" prefix(ident)` as a way to + // configure a theme prefix for variables and utilities. + else if (param.startsWith('prefix(')) { + let prefix = param.slice(7, -1) + + walk(node.nodes, (child) => { + if (child.kind !== 'rule') return + if (child.selector === '@theme' || child.selector.startsWith('@theme ')) { + child.selector += ` prefix(${prefix})` + return WalkAction.Skip + } + }) + } - for (let i = 0; i < features.length; i++) { - let part = features[i] + // Handle important + else if (param === 'important') { + important = true + } - // Drop instances of `@media important` // - // We support `@import "tailwindcss" important` to mark all declarations - // in generated utilities as `!important`. - if (part === 'important') { - important = true - shouldReplace = true - features[i] = '' + else { + unknownParams.push(param) } } - let remaining = features.filter(Boolean).join(' ') - - node.selector = `@media ${remaining}` - - if (remaining.trim() === '' && shouldReplace) { + if (unknownParams.length > 0) { + node.selector = `@media ${unknownParams.join(' ')}` + } else if (params.length > 0) { replaceWith(node.nodes) } From b8a8e31c33b1a53275dd1c4738f58dc51dd63847 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 23 Oct 2024 13:30:13 +0200 Subject: [PATCH 2/3] wrap `@theme` handling in an if-block We do this for handling all other parts as well. This makes it more consistent. --- packages/tailwindcss/src/index.ts | 81 ++++++++++++++++--------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index f62f6e7f975f..4c03298b4fd1 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -258,54 +258,55 @@ async function parseCss( return WalkAction.Skip } - if (node.selector !== '@theme' && !node.selector.startsWith('@theme ')) return - - let [themeOptions, themePrefix] = parseThemeOptions(node.selector) + // Handle `@theme` + if (node.selector === '@theme' || node.selector.startsWith('@theme ')) { + let [themeOptions, themePrefix] = parseThemeOptions(node.selector) + + if (themePrefix) { + if (!IS_VALID_PREFIX.test(themePrefix)) { + throw new Error( + `The prefix "${themePrefix}" is invalid. Prefixes must be lowercase ASCII letters (a-z) only.`, + ) + } - if (themePrefix) { - if (!IS_VALID_PREFIX.test(themePrefix)) { - throw new Error( - `The prefix "${themePrefix}" is invalid. Prefixes must be lowercase ASCII letters (a-z) only.`, - ) + theme.prefix = themePrefix } - theme.prefix = themePrefix - } - - // Record all custom properties in the `@theme` declaration - walk(node.nodes, (child, { replaceWith }) => { - // Collect `@keyframes` rules to re-insert with theme variables later, - // since the `@theme` rule itself will be removed. - if (child.kind === 'rule' && child.selector.startsWith('@keyframes ')) { - theme.addKeyframes(child) - replaceWith([]) - return WalkAction.Skip - } + // Record all custom properties in the `@theme` declaration + walk(node.nodes, (child, { replaceWith }) => { + // Collect `@keyframes` rules to re-insert with theme variables later, + // since the `@theme` rule itself will be removed. + if (child.kind === 'rule' && child.selector.startsWith('@keyframes ')) { + theme.addKeyframes(child) + replaceWith([]) + return WalkAction.Skip + } - if (child.kind === 'comment') return - if (child.kind === 'declaration' && child.property.startsWith('--')) { - theme.add(child.property, child.value ?? '', themeOptions) - return - } + if (child.kind === 'comment') return + if (child.kind === 'declaration' && child.property.startsWith('--')) { + theme.add(child.property, child.value ?? '', themeOptions) + return + } - let snippet = toCss([rule(node.selector, [child])]) - .split('\n') - .map((line, idx, all) => `${idx === 0 || idx >= all.length - 2 ? ' ' : '>'} ${line}`) - .join('\n') + let snippet = toCss([rule(node.selector, [child])]) + .split('\n') + .map((line, idx, all) => `${idx === 0 || idx >= all.length - 2 ? ' ' : '>'} ${line}`) + .join('\n') - throw new Error( - `\`@theme\` blocks must only contain custom properties or \`@keyframes\`.\n\n${snippet}`, - ) - }) + throw new Error( + `\`@theme\` blocks must only contain custom properties or \`@keyframes\`.\n\n${snippet}`, + ) + }) - // Keep a reference to the first `@theme` rule to update with the full theme - // later, and delete any other `@theme` rules. - if (!firstThemeRule && !(themeOptions & ThemeOptions.REFERENCE)) { - firstThemeRule = node - } else { - replaceWith([]) + // Keep a reference to the first `@theme` rule to update with the full + // theme later, and delete any other `@theme` rules. + if (!firstThemeRule && !(themeOptions & ThemeOptions.REFERENCE)) { + firstThemeRule = node + } else { + replaceWith([]) + } + return WalkAction.Skip } - return WalkAction.Skip }) let designSystem = buildDesignSystem(theme) From aa4f13a419dce60ec6ad3ebdccb5fc69cc532378 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 23 Oct 2024 18:18:18 +0200 Subject: [PATCH 3/3] only handle `at-rules` --- packages/tailwindcss/src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 4c03298b4fd1..331816d7554b 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -76,7 +76,6 @@ async function parseCss( await substituteAtImports(ast, base, loadStylesheet) - // Find all `@theme` declarations let important: boolean | null = null let theme = new Theme() let customVariants: ((designSystem: DesignSystem) => void)[] = [] @@ -84,8 +83,10 @@ async function parseCss( let firstThemeRule: Rule | null = null let globs: { base: string; pattern: string }[] = [] + // Handle at-rules walk(ast, (node, { parent, replaceWith, context }) => { if (node.kind !== 'rule') return + if (node.selector[0] !== '@') return // Collect custom `@utility` at-rules if (node.selector.startsWith('@utility ')) { @@ -196,7 +197,7 @@ async function parseCss( } } - if (node.selector[0] === '@' && node.selector.startsWith('@media ')) { + if (node.selector.startsWith('@media ')) { let params = segment(node.selector.slice(7), ' ') let unknownParams: string[] = []