Home / Function/ buildPluginApi() — tailwindcss Function Reference

buildPluginApi() — tailwindcss Function Reference

Architecture documentation for the buildPluginApi() function in plugin-api.ts from the tailwindcss codebase.

Entity Profile

Dependency Diagram

graph TD
  ad196438_55f7_af7b_1604_1d75c1c27d8e["buildPluginApi()"]
  0f8ac574_990e_8595_a6ed_11422b8a8ec4["upgradeToFullPluginSupport()"]
  0f8ac574_990e_8595_a6ed_11422b8a8ec4 -->|calls| ad196438_55f7_af7b_1604_1d75c1c27d8e
  a073a4cd_758b_23aa_b3d4_216815e4dd3d["objectToAst()"]
  ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| a073a4cd_758b_23aa_b3d4_216815e4dd3d
  4bf7802a_6d99_1399_1f81_b8d1aa3ab685["substituteFunctions()"]
  ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 4bf7802a_6d99_1399_1f81_b8d1aa3ab685
  a9af385a_fd12_f1d8_7cf0_ccb9b281ca18["atRule()"]
  ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| a9af385a_fd12_f1d8_7cf0_ccb9b281ca18
  e9d556bc_f22d_356c_1bd2_27442c34b5c7["walk()"]
  ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| e9d556bc_f22d_356c_1bd2_27442c34b5c7
  23f7e85c_49fd_cf23_507a_d8d770053d92["entries()"]
  ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 23f7e85c_49fd_cf23_507a_d8d770053d92
  3ec7100d_c372_c98a_2ba0_9ec5e70f401d["parseVariantValue()"]
  ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 3ec7100d_c372_c98a_2ba0_9ec5e70f401d
  828655d7_fafb_f7a4_ba36_e6b356a78fbc["compoundsForSelectors()"]
  ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 828655d7_fafb_f7a4_ba36_e6b356a78fbc
  43c6ae59_76f1_6f53_dfa2_f3151fd259d1["fromAst()"]
  ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 43c6ae59_76f1_6f53_dfa2_f3151fd259d1
  3ea38218_2a7c_f912_d458_de38bb3579b6["group()"]
  ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 3ea38218_2a7c_f912_d458_de38bb3579b6
  2a20ea29_c850_cd61_5600_aeebbe3dda66["segment()"]
  ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 2a20ea29_c850_cd61_5600_aeebbe3dda66
  08f33202_11d1_569a_e8df_de23eb987e2f["rule()"]
  ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 08f33202_11d1_569a_e8df_de23eb987e2f
  9508f2e7_ca66_c4bb_0665_b6dc278d127a["replaceNestedClassNameReferences()"]
  ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 9508f2e7_ca66_c4bb_0665_b6dc278d127a
  5f3acb43_b93f_4293_caaa_25ba26d38178["substituteAtApply()"]
  ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 5f3acb43_b93f_4293_caaa_25ba26d38178
  style ad196438_55f7_af7b_1604_1d75c1c27d8e fill:#6366f1,stroke:#818cf8,color:#fff

Relationship Graph

Source Code

packages/tailwindcss/src/compat/plugin-api.ts lines 93–542

export function buildPluginApi({
  designSystem,
  ast,
  resolvedConfig,
  featuresRef,
  referenceMode,
  src,
}: {
  designSystem: DesignSystem
  ast: AstNode[]
  resolvedConfig: ResolvedConfig
  featuresRef: { current: Features }
  referenceMode: boolean
  src: SourceLocation | undefined
}): PluginAPI {
  let api: PluginAPI = {
    addBase(css) {
      if (referenceMode) return
      let baseNodes = objectToAst(css)
      featuresRef.current |= substituteFunctions(baseNodes, designSystem)
      let rule = atRule('@layer', 'base', baseNodes)
      walk([rule], (node) => {
        node.src = src
      })
      ast.push(rule)
    },

    addVariant(name, variant) {
      if (!IS_VALID_VARIANT_NAME.test(name)) {
        throw new Error(
          `\`addVariant('${name}')\` defines an invalid variant name. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number.`,
        )
      }

      // Ignore variants emitting v3 `:merge(…)` rules. In v4, the `group-*` and `peer-*` variants
      // compound automatically.
      if (typeof variant === 'string') {
        if (variant.includes(':merge(')) return
      } else if (Array.isArray(variant)) {
        if (variant.some((v) => v.includes(':merge('))) return
      } else if (typeof variant === 'object') {
        function keyIncludes(object: Record<string, any>, search: string): boolean {
          return Object.entries(object).some(
            ([key, value]) =>
              key.includes(search) || (typeof value === 'object' && keyIncludes(value, search)),
          )
        }
        if (keyIncludes(variant, ':merge(')) return
      }

      // Single selector or multiple parallel selectors
      if (typeof variant === 'string' || Array.isArray(variant)) {
        designSystem.variants.static(
          name,
          (r) => {
            r.nodes = parseVariantValue(variant, r.nodes)
          },
          {
            compounds: compoundsForSelectors(typeof variant === 'string' ? [variant] : variant),
          },
        )
      }

      // CSS-in-JS object
      else if (typeof variant === 'object') {
        designSystem.variants.fromAst(name, objectToAst(variant), designSystem)
      }
    },
    matchVariant(name, fn, options) {
      function resolveVariantValue<T extends Parameters<typeof fn>[0]>(
        value: T,
        modifier: CandidateModifier | null,
        nodes: AstNode[],
      ): AstNode[] {
        let resolved = fn(value, { modifier: modifier?.value ?? null })
        return parseVariantValue(resolved, nodes)
      }

      try {
        // Sample variant value and ignore variants emitting v3 `:merge` rules. In
        // v4, the `group-*` and `peer-*` variants compound automatically.
        let sample = fn('a', { modifier: null })
        if (typeof sample === 'string' && sample.includes(':merge(')) {
          return
        } else if (Array.isArray(sample) && sample.some((r) => r.includes(':merge('))) {
          return
        }
      } catch {}

      let defaultOptionKeys = Object.keys(options?.values ?? {})
      designSystem.variants.group(
        () => {
          designSystem.variants.functional(name, (ruleNodes, variant) => {
            if (!variant.value) {
              if (options?.values && 'DEFAULT' in options.values) {
                ruleNodes.nodes = resolveVariantValue(
                  options.values.DEFAULT,
                  variant.modifier,
                  ruleNodes.nodes,
                )
                return
              }
              return null
            }

            if (variant.value.kind === 'arbitrary') {
              ruleNodes.nodes = resolveVariantValue(
                variant.value.value,
                variant.modifier,
                ruleNodes.nodes,
              )
            } else if (variant.value.kind === 'named' && options?.values) {
              let defaultValue = options.values[variant.value.value]
              if (typeof defaultValue !== 'string') {
                return null
              }

              ruleNodes.nodes = resolveVariantValue(defaultValue, variant.modifier, ruleNodes.nodes)
            } else {
              return null
            }
          })
        },
        (a, z) => {
          // Since we only define a functional variant in the group, the `kind`
          // has to be `functional`.
          if (a.kind !== 'functional' || z.kind !== 'functional') {
            return 0
          }

          let aValueKey = a.value ? a.value.value : 'DEFAULT'
          let zValueKey = z.value ? z.value.value : 'DEFAULT'

          let aValue = options?.values?.[aValueKey] ?? aValueKey
          let zValue = options?.values?.[zValueKey] ?? zValueKey

          if (options && typeof options.sort === 'function') {
            return options.sort(
              { value: aValue, modifier: a.modifier?.value ?? null },
              { value: zValue, modifier: z.modifier?.value ?? null },
            )
          }

          let aOrder = defaultOptionKeys.indexOf(aValueKey)
          let zOrder = defaultOptionKeys.indexOf(zValueKey)

          // Sort arbitrary values after configured values
          aOrder = aOrder === -1 ? defaultOptionKeys.length : aOrder
          zOrder = zOrder === -1 ? defaultOptionKeys.length : zOrder

          if (aOrder !== zOrder) return aOrder - zOrder

          // SAFETY: The values don't need to be checked for equality as they
          // are guaranteed to be unique since we sort a list of de-duped
          // variants and different (valid) variants cannot produce the same AST.
          return aValue < zValue ? -1 : 1
        },
      )

      designSystem.variants.suggest(name, () =>
        Object.keys(options?.values ?? {}).filter((v) => v !== 'DEFAULT'),
      )
    },

    addUtilities(utilities) {
      utilities = Array.isArray(utilities) ? utilities : [utilities]

      let entries = utilities.flatMap((u) => Object.entries(u))

      // Split multi-selector utilities into individual utilities
      entries = entries.flatMap(([name, css]) =>
        segment(name, ',').map((selector) => [selector.trim(), css] as [string, CssInJs]),
      )

      // Merge entries for the same class
      let utils = new DefaultMap<string, AstNode[]>(() => [])

      for (let [name, css] of entries) {
        if (name.startsWith('@keyframes ')) {
          if (!referenceMode) {
            let keyframes = rule(name, objectToAst(css))
            walk([keyframes], (node) => {
              node.src = src
            })
            ast.push(keyframes)
          }
          continue
        }

        let selectorAst = SelectorParser.parse(name)
        let foundValidUtility = false

        walk(selectorAst, (node) => {
          if (
            node.kind === 'selector' &&
            node.value[0] === '.' &&
            IS_VALID_UTILITY_NAME.test(node.value.slice(1))
          ) {
            let value = node.value
            node.value = '&'
            let selector = SelectorParser.toCss(selectorAst)

            let className = value.slice(1)
            let contents = selector === '&' ? objectToAst(css) : [rule(selector, objectToAst(css))]
            utils.get(className).push(...contents)
            foundValidUtility = true

            node.value = value
            return
          }

          if (node.kind === 'function' && node.value === ':not') {
            return WalkAction.Skip
          }
        })

        if (!foundValidUtility) {
          throw new Error(
            `\`addUtilities({ '${name}' : … })\` defines an invalid utility selector. Utilities must be a single class name and start with a lowercase letter, eg. \`.scrollbar-none\`.`,
          )
        }
      }

      for (let [className, ast] of utils) {
        // Prefix all class selector with the configured theme prefix
        if (designSystem.theme.prefix) {
          walk(ast, (node) => {
            if (node.kind === 'rule') {
              let selectorAst = SelectorParser.parse(node.selector)
              walk(selectorAst, (node) => {
                if (node.kind === 'selector' && node.value[0] === '.') {
                  node.value = `.${designSystem.theme.prefix}\\:${node.value.slice(1)}`
                }
              })
              node.selector = SelectorParser.toCss(selectorAst)
            }
          })
        }

        designSystem.utilities.static(className, (candidate) => {
          let clonedAst = ast.map(cloneAstNode)
          replaceNestedClassNameReferences(clonedAst, className, candidate.raw)
          featuresRef.current |= substituteAtApply(clonedAst, designSystem)
          return clonedAst
        })
      }
    },

    matchUtilities(utilities, options) {
      let types = options?.type
        ? Array.isArray(options?.type)
          ? options.type
          : [options.type]
        : ['any']

      for (let [name, fn] of Object.entries(utilities)) {
        if (!IS_VALID_UTILITY_NAME.test(name)) {
          throw new Error(
            `\`matchUtilities({ '${name}' : … })\` defines an invalid utility name. Utilities should be alphanumeric and start with a lowercase letter, eg. \`scrollbar\`.`,
          )
        }

        function compileFn({ negative }: { negative: boolean }) {
          return (candidate: Extract<Candidate, { kind: 'functional' }>) => {
            // Throw out any candidate whose value is not a supported type
            if (
              candidate.value?.kind === 'arbitrary' &&
              types.length > 0 &&
              !types.includes('any')
            ) {
              // The candidate has an explicit data type but it's not in the list
              // of supported types by this utility. For example, a `scrollbar`
              // utility that is only used to change the scrollbar color but is
              // used with a `length` value: `scrollbar-[length:var(--whatever)]`
              if (candidate.value.dataType && !types.includes(candidate.value.dataType)) {
                return
              }

              // The candidate does not have an explicit data type and the value
              // cannot be inferred as one of the supported types. For example, a
              // `scrollbar` utility that is only used to change the scrollbar
              // color but is used with a `length` value: `scrollbar-[33px]`
              if (
                !candidate.value.dataType &&
                !inferDataType(candidate.value.value, types as any[])
              ) {
                return
              }
            }

            let isColor = types.includes('color')

            // Resolve the candidate value
            let value: string | null = null
            let ignoreModifier = false

            {
              let values = options?.values ?? {}

              if (isColor) {
                // Color utilities implicitly support `inherit`, `transparent`, and `currentcolor`
                // for backwards compatibility but still allow them to be overridden
                values = Object.assign(
                  {
                    inherit: 'inherit',
                    transparent: 'transparent',
                    current: 'currentcolor',
                  },
                  values,
                )
              }

              if (!candidate.value) {
                value = values.DEFAULT ?? null
              } else if (candidate.value.kind === 'arbitrary') {
                value = candidate.value.value
              } else if (candidate.value.fraction && values[candidate.value.fraction]) {
                value = values[candidate.value.fraction]
                ignoreModifier = true
              } else if (values[candidate.value.value]) {
                value = values[candidate.value.value]
              } else if (values.__BARE_VALUE__) {
                value = values.__BARE_VALUE__(candidate.value) ?? null
                ignoreModifier =
                  (candidate.value.fraction !== null && value?.includes('/')) ?? false
              }
            }

            if (value === null) return

            // Resolve the modifier value
            let modifier: string | null

            {
              let modifiers = options?.modifiers ?? null

              if (!candidate.modifier) {
                modifier = null
              } else if (modifiers === 'any' || candidate.modifier.kind === 'arbitrary') {
                modifier = candidate.modifier.value
              } else if (modifiers?.[candidate.modifier.value]) {
                modifier = modifiers[candidate.modifier.value]
              } else if (isColor && !Number.isNaN(Number(candidate.modifier.value))) {
                modifier = `${candidate.modifier.value}%`
              } else {
                modifier = null
              }
            }

            // A modifier was provided but is invalid
            if (candidate.modifier && modifier === null && !ignoreModifier) {
              // For arbitrary values, return `null` to avoid falling through to the next utility
              return candidate.value?.kind === 'arbitrary' ? null : undefined
            }

            if (isColor && modifier !== null) {
              value = withAlpha(value, modifier)
            }

            if (negative) {
              value = `calc(${value} * -1)`
            }

            let ast = objectToAst(fn(value, { modifier }))
            replaceNestedClassNameReferences(ast, name, candidate.raw)
            featuresRef.current |= substituteAtApply(ast, designSystem)
            return ast
          }
        }

        if (options?.supportsNegativeValues) {
          designSystem.utilities.functional(`-${name}`, compileFn({ negative: true }), { types })
        }
        designSystem.utilities.functional(name, compileFn({ negative: false }), { types })

        designSystem.utilities.suggest(name, () => {
          let values = options?.values ?? {}
          let valueKeys = new Set<string | null>(Object.keys(values))

          // The `__BARE_VALUE__` key is a special key used to ensure bare values
          // work even with legacy configs and plugins
          valueKeys.delete('__BARE_VALUE__')

          // The `__CSS_VALUES__` key is a special key used by the theme function
          // to transport `@theme` metadata about values from CSS
          valueKeys.delete('__CSS_VALUES__')

          // The `DEFAULT` key is represented as `null` in the utility API
          if (valueKeys.has('DEFAULT')) {
            valueKeys.delete('DEFAULT')
            valueKeys.add(null)
          }

          let modifiers = options?.modifiers ?? {}
          let modifierKeys = modifiers === 'any' ? [] : Object.keys(modifiers)

          return [
            {
              supportsNegative: options?.supportsNegativeValues ?? false,
              values: Array.from(valueKeys),
              modifiers: modifierKeys,
            },
          ]
        })
      }
    },

    addComponents(components, options) {
      this.addUtilities(components, options)
    },

    matchComponents(components, options) {
      this.matchUtilities(components, options)
    },

    theme: createThemeFn(
      designSystem,
      () => resolvedConfig.theme ?? {},
      (value) => value,
    ),

    prefix(className) {
      return className
    },

    config(path, defaultValue) {
      let obj: Record<any, any> = resolvedConfig

      if (!path) return obj

      let keypath = toKeyPath(path)

      for (let i = 0; i < keypath.length; ++i) {
        let key = keypath[i]

        if (obj[key] === undefined) return defaultValue

        obj = obj[key]
      }

      return obj ?? defaultValue
    },
  }

  // Bind these functions so they can use `this`
  api.addComponents = api.addComponents.bind(api)
  api.matchComponents = api.matchComponents.bind(api)

  return api
}

Subdomains

Frequently Asked Questions

What does buildPluginApi() do?
buildPluginApi() is a function in the tailwindcss codebase.
What does buildPluginApi() call?
buildPluginApi() calls 24 function(s): atRule, compoundsForSelectors, createThemeFn, entries, fromAst, functional, get, group, and 16 more.
What calls buildPluginApi()?
buildPluginApi() is called by 1 function(s): upgradeToFullPluginSupport.

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free