Home / Function/ parseCss() — tailwindcss Function Reference

parseCss() — tailwindcss Function Reference

Architecture documentation for the parseCss() function in index.ts from the tailwindcss codebase.

Entity Profile

Dependency Diagram

graph TD
  95cb326e_6b59_0903_0c96_d221fca5c2b1["parseCss()"]
  f5e443d2_a934_36af_23f2_b1c002aaa585["compileAst()"]
  f5e443d2_a934_36af_23f2_b1c002aaa585 -->|calls| 95cb326e_6b59_0903_0c96_d221fca5c2b1
  fb5a2bcd_8ed3_9973_b6d8_b4f47715ef8e["__unstable__loadDesignSystem()"]
  fb5a2bcd_8ed3_9973_b6d8_b4f47715ef8e -->|calls| 95cb326e_6b59_0903_0c96_d221fca5c2b1
  65cd5723_5f2b_da42_e83b_2add05ebc004["substituteAtImports()"]
  95cb326e_6b59_0903_0c96_d221fca5c2b1 -->|calls| 65cd5723_5f2b_da42_e83b_2add05ebc004
  a32bba76_f60d_883f_1ff1_276a0bb9db9f["walk()"]
  95cb326e_6b59_0903_0c96_d221fca5c2b1 -->|calls| a32bba76_f60d_883f_1ff1_276a0bb9db9f
  4883e410_7ef0_3846_5ee9_bba194796fa1["cssContext()"]
  95cb326e_6b59_0903_0c96_d221fca5c2b1 -->|calls| 4883e410_7ef0_3846_5ee9_bba194796fa1
  03b8d706_a876_a776_0056_186ced5d6067["segment()"]
  95cb326e_6b59_0903_0c96_d221fca5c2b1 -->|calls| 03b8d706_a876_a776_0056_186ced5d6067
  5af2f27c_4323_fb62_14cc_5b4f717b4d2f["createCssUtility()"]
  95cb326e_6b59_0903_0c96_d221fca5c2b1 -->|calls| 5af2f27c_4323_fb62_14cc_5b4f717b4d2f
  1926c33e_3a42_f5fb_dd2f_a9422b055595["expand()"]
  95cb326e_6b59_0903_0c96_d221fca5c2b1 -->|calls| 1926c33e_3a42_f5fb_dd2f_a9422b055595
  34278c80_0fb5_dbdf_08de_b896b9703f2c["set()"]
  95cb326e_6b59_0903_0c96_d221fca5c2b1 -->|calls| 34278c80_0fb5_dbdf_08de_b896b9703f2c
  9db2a11b_4852_e036_e394_622c7a90dd1f["styleRule()"]
  95cb326e_6b59_0903_0c96_d221fca5c2b1 -->|calls| 9db2a11b_4852_e036_e394_622c7a90dd1f
  f4f92a3d_c13e_a751_8402_451ffa4c772f["rule()"]
  95cb326e_6b59_0903_0c96_d221fca5c2b1 -->|calls| f4f92a3d_c13e_a751_8402_451ffa4c772f
  3a207375_0c4d_0a9e_f1f0_05b2b56ac8a7["compoundsForSelectors()"]
  95cb326e_6b59_0903_0c96_d221fca5c2b1 -->|calls| 3a207375_0c4d_0a9e_f1f0_05b2b56ac8a7
  24d95be4_356f_a1f9_9702_2a4f413db0f5["add()"]
  95cb326e_6b59_0903_0c96_d221fca5c2b1 -->|calls| 24d95be4_356f_a1f9_9702_2a4f413db0f5
  24c6462c_8059_28e8_4bfc_8d04c9da1c7e["fromAst()"]
  95cb326e_6b59_0903_0c96_d221fca5c2b1 -->|calls| 24c6462c_8059_28e8_4bfc_8d04c9da1c7e
  style 95cb326e_6b59_0903_0c96_d221fca5c2b1 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

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