parseCss() — tailwindcss Function Reference
Architecture documentation for the parseCss() function in index.ts from the tailwindcss codebase.
Entity Profile
Dependency Diagram
graph TD 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c["parseCss()"] 6a3a8ab4_d53c_7516_c736_663c060fe979["compileAst()"] 6a3a8ab4_d53c_7516_c736_663c060fe979 -->|calls| 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c 70b71984_f090_260f_6475_e3bfd4e6108f["__unstable__loadDesignSystem()"] 70b71984_f090_260f_6475_e3bfd4e6108f -->|calls| 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c 761efede_46a5_5722_0699_0e1b0947c406["substituteAtImports()"] 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| 761efede_46a5_5722_0699_0e1b0947c406 e9d556bc_f22d_356c_1bd2_27442c34b5c7["walk()"] 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| e9d556bc_f22d_356c_1bd2_27442c34b5c7 aac1ce38_87b8_e2ee_838b_b9196b3e9299["cssContext()"] 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| aac1ce38_87b8_e2ee_838b_b9196b3e9299 2a20ea29_c850_cd61_5600_aeebbe3dda66["segment()"] 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| 2a20ea29_c850_cd61_5600_aeebbe3dda66 5c9381d6_815c_d899_eaab_849d755be47e["createCssUtility()"] 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| 5c9381d6_815c_d899_eaab_849d755be47e 1f29c0ee_be09_f644_3fb8_8d80057c5d97["expand()"] 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| 1f29c0ee_be09_f644_3fb8_8d80057c5d97 13f9b85f_58c4_5390_3b22_04d00b93361b["set()"] 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| 13f9b85f_58c4_5390_3b22_04d00b93361b c3b56f1d_0d90_0f17_2f55_85f3419d74bd["styleRule()"] 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| c3b56f1d_0d90_0f17_2f55_85f3419d74bd 08f33202_11d1_569a_e8df_de23eb987e2f["rule()"] 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| 08f33202_11d1_569a_e8df_de23eb987e2f 828655d7_fafb_f7a4_ba36_e6b356a78fbc["compoundsForSelectors()"] 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| 828655d7_fafb_f7a4_ba36_e6b356a78fbc 3ba19013_498f_3c9b_5c44_0eb24efc4394["add()"] 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| 3ba19013_498f_3c9b_5c44_0eb24efc4394 43c6ae59_76f1_6f53_dfa2_f3151fd259d1["fromAst()"] 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| 43c6ae59_76f1_6f53_dfa2_f3151fd259d1 style 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c fill:#6366f1,stroke:#818cf8,color:#fff
Relationship Graph
Source Code
packages/tailwindcss/src/index.ts lines 141–707
async function parseCss(
ast: AstNode[],
{
base = '',
from,
loadModule = throwOnLoadModule,
loadStylesheet = throwOnLoadStylesheet,
}: CompileOptions = {},
) {
let features = Features.None
ast = [contextNode({ base }, ast)] as AstNode[]
features |= await substituteAtImports(ast, base, loadStylesheet, 0, from !== undefined)
let important = null as boolean | null
let theme = new Theme()
let customVariants = new Map<string, (designSystem: DesignSystem) => void>()
let customVariantDependencies = new Map<string, Set<string>>()
let customUtilities: ((designSystem: DesignSystem) => void)[] = []
let firstThemeRule = null as StyleRule | null
let utilitiesNode = null as AtRule | null
let variantNodes: AtRule[] = []
let sources: { base: string; pattern: string; negated: boolean }[] = []
let inlineCandidates: string[] = []
let ignoredCandidates: string[] = []
let root = null as Root
// Handle at-rules
walk(ast, (node, _ctx) => {
if (node.kind !== 'at-rule') return
let ctx = cssContext(_ctx)
// Find `@tailwind utilities` so that we can later replace it with the
// actual generated utility class CSS.
if (
node.name === '@tailwind' &&
(node.params === 'utilities' || node.params.startsWith('utilities'))
) {
// Any additional `@tailwind utilities` nodes can be removed
if (utilitiesNode !== null) {
return WalkAction.Replace([])
}
// When inside `@reference` we should treat `@tailwind utilities` as if
// it wasn't there in the first place. This should also let `build()`
// return the cached static AST.
if (ctx.context.reference) {
return WalkAction.Replace([])
}
let params = segment(node.params, ' ')
for (let param of params) {
if (param.startsWith('source(')) {
let path = param.slice(7, -1)
// Keyword: `source(none)`
if (path === 'none') {
root = path
continue
}
// Explicit path: `source('…')`
if (
(path[0] === '"' && path[path.length - 1] !== '"') ||
(path[0] === "'" && path[path.length - 1] !== "'") ||
(path[0] !== "'" && path[0] !== '"')
) {
throw new Error('`source(…)` paths must be quoted.')
}
root = {
base: (ctx.context.sourceBase as string) ?? (ctx.context.base as string),
pattern: path.slice(1, -1),
}
}
}
utilitiesNode = node
features |= Features.Utilities
}
// Collect custom `@utility` at-rules
if (node.name === '@utility') {
if (ctx.parent !== null) {
throw new Error('`@utility` cannot be nested.')
}
if (node.nodes.length === 0) {
throw new Error(
`\`@utility ${node.params}\` is empty. Utilities should include at least one property.`,
)
}
let utility = createCssUtility(node)
if (utility === null) {
if (!node.params.endsWith('-*')) {
if (node.params.endsWith('*')) {
throw new Error(
`\`@utility ${node.params}\` defines an invalid utility name. A functional utility must end in \`-*\`.`,
)
} else if (node.params.includes('*')) {
throw new Error(
`\`@utility ${node.params}\` defines an invalid utility name. The dynamic portion marked by \`-*\` must appear once at the end.`,
)
}
}
throw new Error(
`\`@utility ${node.params}\` defines an invalid utility name. Utilities should be alphanumeric and start with a lowercase letter.`,
)
}
customUtilities.push(utility)
}
// Collect paths from `@source` at-rules
if (node.name === '@source') {
if (node.nodes.length > 0) {
throw new Error('`@source` cannot have a body.')
}
if (ctx.parent !== null) {
throw new Error('`@source` cannot be nested.')
}
let not = false
let inline = false
let path = node.params
if (path[0] === 'n' && path.startsWith('not ')) {
not = true
path = path.slice(4)
}
if (path[0] === 'i' && path.startsWith('inline(')) {
inline = true
path = path.slice(7, -1).trim()
}
if (
(path[0] === '"' && path[path.length - 1] !== '"') ||
(path[0] === "'" && path[path.length - 1] !== "'") ||
(path[0] !== "'" && path[0] !== '"')
) {
throw new Error('`@source` paths must be quoted.')
}
let source = path.slice(1, -1)
if (inline) {
let destination = not ? ignoredCandidates : inlineCandidates
let sources = segment(source, ' ')
for (let source of sources) {
for (let candidate of expand(source)) {
destination.push(candidate)
}
}
} else {
sources.push({
base: ctx.context.base as string,
pattern: source,
negated: not,
})
}
return WalkAction.ReplaceSkip([])
}
// Apply `@variant` at-rules
if (node.name === '@variant') {
// Legacy `@variant` at-rules containing `@slot` or without a body should
// be considered a `@custom-variant` at-rule.
if (ctx.parent === null) {
// Body-less `@variant`, e.g.: `@variant foo (…);`
if (node.nodes.length === 0) {
node.name = '@custom-variant'
}
// Using `@slot`:
//
// ```css
// @variant foo {
// &:hover {
// @slot;
// }
// }
// ```
else {
walk(node.nodes, (child) => {
if (child.kind === 'at-rule' && child.name === '@slot') {
node.name = '@custom-variant'
return WalkAction.Stop
}
})
// No `@slot` found, so this is still a regular `@variant` at-rule
if (node.name === '@variant') {
variantNodes.push(node)
}
}
}
// Collect all the `@variant` at-rules, we will replace them later once
// all variants are registered in the system.
else {
variantNodes.push(node)
}
}
// Register custom variants from `@custom-variant` at-rules
if (node.name === '@custom-variant') {
if (ctx.parent !== null) {
throw new Error('`@custom-variant` cannot be nested.')
}
let [name, selector] = segment(node.params, ' ')
if (!IS_VALID_VARIANT_NAME.test(name)) {
throw new Error(
`\`@custom-variant ${name}\` defines an invalid variant name. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number.`,
)
}
if (node.nodes.length > 0 && selector) {
throw new Error(`\`@custom-variant ${name}\` cannot have both a selector and a body.`)
}
// Variants with a selector, but without a body, e.g.: `@custom-variant hocus (&:hover, &:focus);`
if (node.nodes.length === 0) {
if (!selector) {
throw new Error(`\`@custom-variant ${name}\` has no selector or body.`)
}
let selectors = segment(selector.slice(1, -1), ',')
if (selectors.length === 0 || selectors.some((selector) => selector.trim() === '')) {
throw new Error(
`\`@custom-variant ${name} (${selectors.join(',')})\` selector is invalid.`,
)
}
let atRuleParams: string[] = []
let styleRuleSelectors: string[] = []
for (let selector of selectors) {
selector = selector.trim()
if (selector[0] === '@') {
atRuleParams.push(selector)
} else {
styleRuleSelectors.push(selector)
}
}
customVariants.set(name, (designSystem) => {
designSystem.variants.static(
name,
(r) => {
let nodes: AstNode[] = []
if (styleRuleSelectors.length > 0) {
nodes.push(styleRule(styleRuleSelectors.join(', '), r.nodes))
}
for (let selector of atRuleParams) {
nodes.push(rule(selector, r.nodes))
}
r.nodes = nodes
},
{
compounds: compoundsForSelectors([...styleRuleSelectors, ...atRuleParams]),
},
)
})
customVariantDependencies.set(name, new Set<string>())
}
// Variants without a selector, but with a body:
//
// E.g.:
//
// ```css
// @custom-variant hocus {
// &:hover {
// @slot;
// }
//
// &:focus {
// @slot;
// }
// }
// ```
else {
let dependencies = new Set<string>()
walk(node.nodes, (child) => {
if (child.kind === 'at-rule' && child.name === '@variant') {
dependencies.add(child.params)
}
})
customVariants.set(name, (designSystem) => {
designSystem.variants.fromAst(name, node.nodes, designSystem)
})
customVariantDependencies.set(name, dependencies)
}
// Remove `@custom-variant` at-rule so it's not included in the compiled CSS
return WalkAction.ReplaceSkip([])
}
if (node.name === '@media') {
let params = segment(node.params, ' ')
let unknownParams: string[] = []
for (let param of params) {
// Handle `@media source(…)`
if (param.startsWith('source(')) {
let path = param.slice(7, -1)
walk(node.nodes, (child) => {
if (child.kind !== 'at-rule') return
if (child.name === '@tailwind' && child.params === 'utilities') {
child.params += ` source(${path})`
return WalkAction.ReplaceStop([
contextNode({ sourceBase: ctx.context.base }, [child]),
])
}
})
}
// Handle `@media theme(…)`
//
// We support `@import "tailwindcss" theme(reference)` as a way to
// import an external theme file as a reference, which becomes `@media
// theme(reference) { … }` when the `@import` is processed.
else if (param.startsWith('theme(')) {
let themeParams = param.slice(6, -1)
let hasReference = themeParams.includes('reference')
walk(node.nodes, (child) => {
if (child.kind === 'context') return
if (child.kind !== 'at-rule') {
if (hasReference) {
throw new Error(
`Files imported with \`@import "…" theme(reference)\` must only contain \`@theme\` blocks.\nUse \`@reference "…";\` instead.`,
)
}
return WalkAction.Continue
}
if (child.name === '@theme') {
child.params += ' ' + themeParams
return WalkAction.Skip
}
})
}
// 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 !== 'at-rule') return
if (child.name === '@theme') {
child.params += ` prefix(${prefix})`
return WalkAction.Skip
}
})
}
// Handle important
else if (param === 'important') {
important = true
}
// Handle `@import "…" reference`
else if (param === 'reference') {
node.nodes = [contextNode({ reference: true }, node.nodes)]
}
//
else {
unknownParams.push(param)
}
}
if (unknownParams.length > 0) {
node.params = unknownParams.join(' ')
} else if (params.length > 0) {
return WalkAction.Replace(node.nodes)
}
return WalkAction.Continue
}
// Handle `@theme`
if (node.name === '@theme') {
let [themeOptions, themePrefix] = parseThemeOptions(node.params)
features |= Features.AtTheme
if (ctx.context.reference) {
themeOptions |= ThemeOptions.REFERENCE
}
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
}
// Record all custom properties in the `@theme` declaration
walk(node.nodes, (child) => {
// Collect `@keyframes` rules to re-insert with theme variables later,
// since the `@theme` rule itself will be removed.
if (child.kind === 'at-rule' && child.name === '@keyframes') {
theme.addKeyframes(child)
return WalkAction.Skip
}
if (child.kind === 'comment') return
if (child.kind === 'declaration' && child.property.startsWith('--')) {
theme.add(unescape(child.property), child.value ?? '', themeOptions, child.src)
return
}
let snippet = toCss([atRule(node.name, node.params, [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}`,
)
})
// Keep a reference to the first `@theme` rule to update with the full
// theme later, and delete any other `@theme` rules.
if (!firstThemeRule) {
firstThemeRule = styleRule(':root, :host', [])
firstThemeRule.src = node.src
return WalkAction.ReplaceSkip(firstThemeRule)
} else {
return WalkAction.ReplaceSkip([])
}
}
})
let designSystem = buildDesignSystem(theme, utilitiesNode?.src)
if (important) {
designSystem.important = important
}
if (ignoredCandidates.length > 0) {
for (let candidate of ignoredCandidates) {
designSystem.invalidCandidates.add(candidate)
}
}
// Apply hooks from backwards compatibility layer. This function takes a lot
// of random arguments because it really just needs access to "the world" to
// do whatever ungodly things it needs to do to make things backwards
// compatible without polluting core.
features |= await applyCompatibilityHooks({
designSystem,
base,
ast,
loadModule,
sources,
})
for (let name of customVariants.keys()) {
// Pre-register the variant to ensure its position in the variant list is
// based on the order we see them in the CSS.
designSystem.variants.static(name, () => {})
}
// Register custom variants in order
for (let variant of topologicalSort(customVariantDependencies, {
onCircularDependency(path, start) {
let output = toCss(
path.map((name, idx) => {
return atRule('@custom-variant', name, [atRule('@variant', path[idx + 1] ?? start, [])])
}),
)
.replaceAll(';', ' { … }')
.replace(`@custom-variant ${start} {`, `@custom-variant ${start} { /* ← */`)
throw new Error(`Circular dependency detected in custom variants:\n\n${output}`)
},
})) {
customVariants.get(variant)?.(designSystem)
}
for (let customUtility of customUtilities) {
customUtility(designSystem)
}
// Output final set of theme variables at the position of the first
// `@theme` rule.
if (firstThemeRule) {
let nodes = []
for (let [key, value] of designSystem.theme.entries()) {
if (value.options & ThemeOptions.REFERENCE) continue
let node = decl(escape(key), value.value)
node.src = value.src
nodes.push(node)
}
let keyframesRules = designSystem.theme.getKeyframes()
for (let keyframes of keyframesRules) {
// Wrap `@keyframes` in `AtRoot` so they are hoisted out of `:root` when
// printing. We push it to the top-level of the AST so that an eventual
// `@reference` does not cut it out when printing the document.
ast.push(context({ theme: true }, [atRoot([keyframes])]))
}
firstThemeRule.nodes = [context({ theme: true }, nodes)]
}
features |= substituteAtVariant(ast, designSystem)
features |= substituteFunctions(ast, designSystem)
features |= substituteAtApply(ast, designSystem)
// Replace the `@tailwind utilities` node with a context since it prints
// children directly.
if (utilitiesNode) {
let node = utilitiesNode as AstNode as Context
node.kind = 'context'
node.context = {}
}
// Remove `@utility`, we couldn't replace it before yet because we had to
// handle the nested `@apply` at-rules first.
walk(ast, (node) => {
if (node.kind !== 'at-rule') return
if (node.name === '@utility') {
return WalkAction.Replace([])
}
// The `@utility` has to be top-level, therefore we don't have to traverse
// into nested trees.
return WalkAction.Skip
})
return {
designSystem,
ast,
sources,
root,
utilitiesNode,
features,
inlineCandidates,
}
}
Domain
Subdomains
Calls
- add()
- addKeyframes()
- applyCompatibilityHooks()
- atRoot()
- atRule()
- buildDesignSystem()
- compoundsForSelectors()
- context()
- createCssUtility()
- cssContext()
- decl()
- entries()
- escape()
- expand()
- fromAst()
- get()
- getKeyframes()
- keys()
- parseThemeOptions()
- rule()
- segment()
- set()
- styleRule()
- substituteAtApply()
- substituteAtImports()
- substituteAtVariant()
- substituteFunctions()
- theme()
- toCss()
- topologicalSort()
- unescape()
- walk()
Source
Frequently Asked Questions
What does parseCss() do?
parseCss() is a function in the tailwindcss codebase.
What does parseCss() call?
parseCss() calls 32 function(s): add, addKeyframes, applyCompatibilityHooks, atRoot, atRule, buildDesignSystem, compoundsForSelectors, context, and 24 more.
What calls parseCss()?
parseCss() is called by 2 function(s): __unstable__loadDesignSystem, compileAst.
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free