substituteAtApply() — tailwindcss Function Reference
Architecture documentation for the substituteAtApply() function in apply.ts from the tailwindcss codebase.
Entity Profile
Dependency Diagram
graph TD 5f3acb43_b93f_4293_caaa_25ba26d38178["substituteAtApply()"] 00f82a70_37cd_d9d2_64c8_29c748c197c6["createUtilitySignatureCache()"] 00f82a70_37cd_d9d2_64c8_29c748c197c6 -->|calls| 5f3acb43_b93f_4293_caaa_25ba26d38178 cb063cf6_f495_bb4b_84d1_f1d23fb0dae1["createVariantSignatureCache()"] cb063cf6_f495_bb4b_84d1_f1d23fb0dae1 -->|calls| 5f3acb43_b93f_4293_caaa_25ba26d38178 ad196438_55f7_af7b_1604_1d75c1c27d8e["buildPluginApi()"] ad196438_55f7_af7b_1604_1d75c1c27d8e -->|calls| 5f3acb43_b93f_4293_caaa_25ba26d38178 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c["parseCss()"] 26086ff1_0d4f_fdb2_3fc4_d0c999f90a8c -->|calls| 5f3acb43_b93f_4293_caaa_25ba26d38178 08f33202_11d1_569a_e8df_de23eb987e2f["rule()"] 5f3acb43_b93f_4293_caaa_25ba26d38178 -->|calls| 08f33202_11d1_569a_e8df_de23eb987e2f e9d556bc_f22d_356c_1bd2_27442c34b5c7["walk()"] 5f3acb43_b93f_4293_caaa_25ba26d38178 -->|calls| e9d556bc_f22d_356c_1bd2_27442c34b5c7 4cd99e59_ac1e_2a1f_0946_33cc1afd2532["get()"] 5f3acb43_b93f_4293_caaa_25ba26d38178 -->|calls| 4cd99e59_ac1e_2a1f_0946_33cc1afd2532 70b67e94_a79b_3e21_ab33_112296141650["resolveApplyDependencies()"] 5f3acb43_b93f_4293_caaa_25ba26d38178 -->|calls| 70b67e94_a79b_3e21_ab33_112296141650 af90c185_29a2_6c4c_ef06_b18f00f7655c["toCss()"] 5f3acb43_b93f_4293_caaa_25ba26d38178 -->|calls| af90c185_29a2_6c4c_ef06_b18f00f7655c cbdeac33_581c_3396_0f10_877935a68176["compileCandidates()"] 5f3acb43_b93f_4293_caaa_25ba26d38178 -->|calls| cbdeac33_581c_3396_0f10_877935a68176 2a20ea29_c850_cd61_5600_aeebbe3dda66["segment()"] 5f3acb43_b93f_4293_caaa_25ba26d38178 -->|calls| 2a20ea29_c850_cd61_5600_aeebbe3dda66 51b06849_01ef_68aa_d189_1f0d4376d897["cloneAstNode()"] 5f3acb43_b93f_4293_caaa_25ba26d38178 -->|calls| 51b06849_01ef_68aa_d189_1f0d4376d897 style 5f3acb43_b93f_4293_caaa_25ba26d38178 fill:#6366f1,stroke:#818cf8,color:#fff
Relationship Graph
Source Code
packages/tailwindcss/src/apply.ts lines 10–300
export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) {
let features = Features.None
// Wrap the whole AST in a root rule to make sure there is always a parent
// available for `@apply` at-rules. In some cases, the incoming `ast` just
// contains `@apply` at-rules which means that there is no proper parent to
// rely on.
let root = rule('&', ast)
// Track all nodes containing `@apply`
let parents = new Set<AstNode>()
// Track all the dependencies of an `AstNode`
let dependencies = new DefaultMap<AstNode, Set<string>>(() => new Set<string>())
// Track all `@utility` definitions by its root (name)
let definitions = new DefaultMap(() => new Set<AstNode>())
// Collect all new `@utility` definitions and all `@apply` rules first
walk([root], (node, ctx) => {
if (node.kind !== 'at-rule') return
// Do not allow `@apply` rules inside `@keyframes` rules.
if (node.name === '@keyframes') {
walk(node.nodes, (child) => {
if (child.kind === 'at-rule' && child.name === '@apply') {
throw new Error(`You cannot use \`@apply\` inside \`@keyframes\`.`)
}
})
return WalkAction.Skip
}
// `@utility` defines a utility, which is important information in order to
// do a correct topological sort later on.
if (node.name === '@utility') {
let name = node.params.replace(/-\*$/, '')
definitions.get(name).add(node)
// In case `@apply` rules are used inside `@utility` rules.
walk(node.nodes, (child) => {
if (child.kind !== 'at-rule' || child.name !== '@apply') return
parents.add(node)
for (let dependency of resolveApplyDependencies(child, designSystem)) {
dependencies.get(node).add(dependency)
}
})
return
}
// Any other `@apply` node.
if (node.name === '@apply') {
// `@apply` cannot be top-level, so we need to have a parent such that we
// can replace the `@apply` node with the actual utility classes later.
if (ctx.parent === null) return
features |= Features.AtApply
parents.add(ctx.parent)
for (let dependency of resolveApplyDependencies(node, designSystem)) {
// Mark every parent in the path as having a dependency to that utility.
for (let parent of ctx.path()) {
if (!parents.has(parent)) continue
dependencies.get(parent).add(dependency)
}
}
}
})
// Topological sort before substituting `@apply`
let seen = new Set<AstNode>()
let sorted: AstNode[] = []
let wip = new Set<AstNode>()
function visit(node: AstNode, path: AstNode[] = []) {
if (seen.has(node)) {
return
}
// Circular dependency detected
if (wip.has(node)) {
// Next node in the path is the one that caused the circular dependency
let next = path[(path.indexOf(node) + 1) % path.length]
if (
node.kind === 'at-rule' &&
node.name === '@utility' &&
next.kind === 'at-rule' &&
next.name === '@utility'
) {
walk(node.nodes, (child) => {
if (child.kind !== 'at-rule' || child.name !== '@apply') return
let candidates = child.params.split(/\s+/g)
for (let candidate of candidates) {
for (let candidateAstNode of designSystem.parseCandidate(candidate)) {
switch (candidateAstNode.kind) {
case 'arbitrary':
break
case 'static':
case 'functional':
if (next.params.replace(/-\*$/, '') === candidateAstNode.root) {
throw new Error(
`You cannot \`@apply\` the \`${candidate}\` utility here because it creates a circular dependency.`,
)
}
break
default:
candidateAstNode satisfies never
}
}
}
})
}
// Generic fallback error in case we cannot properly detect the origin of
// the circular dependency.
throw new Error(
`Circular dependency detected:\n\n${toCss([node])}\nRelies on:\n\n${toCss([next])}`,
)
}
wip.add(node)
for (let dependencyId of dependencies.get(node)) {
for (let dependency of definitions.get(dependencyId)) {
path.push(node)
visit(dependency, path)
path.pop()
}
}
seen.add(node)
wip.delete(node)
sorted.push(node)
}
for (let node of parents) {
visit(node)
}
// Substitute the `@apply` at-rules in order. Note that the list is going to
// be flattened so we do not have to recursively walk over child rules
for (let parent of sorted) {
if (!('nodes' in parent)) continue
walk(parent.nodes, (child) => {
if (child.kind !== 'at-rule' || child.name !== '@apply') return
let parts = child.params.split(/(\s+)/g)
let candidateOffsets: Record<string, number> = {}
let offset = 0
for (let [idx, part] of parts.entries()) {
if (idx % 2 === 0) candidateOffsets[part] = offset
offset += part.length
}
// Replace the `@apply` rule with the actual utility classes
{
// Parse the candidates to an AST that we can replace the `@apply` rule
// with.
let candidates = Object.keys(candidateOffsets)
let compiled = compileCandidates(candidates, designSystem, {
respectImportant: false,
onInvalidCandidate: (candidate) => {
// When using prefix, make sure prefix is used in candidate
if (designSystem.theme.prefix && !candidate.startsWith(designSystem.theme.prefix)) {
throw new Error(
`Cannot apply unprefixed utility class \`${candidate}\`. Did you mean \`${designSystem.theme.prefix}:${candidate}\`?`,
)
}
// When the utility is blocklisted, let the user know
//
// Note: `@apply` is processed before handling incoming classes from
// template files. This means that the `invalidCandidates` set will
// only contain explicit classes via:
//
// - `blocklist` from a JS config
// - `@source not inline(…)`
if (designSystem.invalidCandidates.has(candidate)) {
throw new Error(
`Cannot apply utility class \`${candidate}\` because it has been explicitly disabled: https://tailwindcss.com/docs/detecting-classes-in-source-files#explicitly-excluding-classes`,
)
}
// Verify if variants exist
let parts = segment(candidate, ':')
if (parts.length > 1) {
let utility = parts.pop()!
// Ensure utility on its own compiles, if not, we will fallback to
// the next error
if (designSystem.candidatesToCss([utility])[0]) {
let compiledVariants = designSystem.candidatesToCss(
parts.map((variant) => `${variant}:[--tw-variant-check:1]`),
)
let unknownVariants = parts.filter((_, idx) => compiledVariants[idx] === null)
if (unknownVariants.length > 0) {
if (unknownVariants.length === 1) {
throw new Error(
`Cannot apply utility class \`${candidate}\` because the ${unknownVariants.map((variant) => `\`${variant}\``)} variant does not exist.`,
)
} else {
let formatter = new Intl.ListFormat('en', {
style: 'long',
type: 'conjunction',
})
throw new Error(
`Cannot apply utility class \`${candidate}\` because the ${formatter.format(unknownVariants.map((variant) => `\`${variant}\``))} variants do not exist.`,
)
}
}
}
}
// When the theme is empty, it means that no theme was loaded and
// `@import "tailwindcss"`, `@reference "app.css"` or similar is
// very likely missing.
if (designSystem.theme.size === 0) {
throw new Error(
`Cannot apply unknown utility class \`${candidate}\`. Are you using CSS modules or similar and missing \`@reference\`? https://tailwindcss.com/docs/functions-and-directives#reference-directive`,
)
}
// Fallback to most generic error message
throw new Error(`Cannot apply unknown utility class \`${candidate}\``)
},
})
let src = child.src
let candidateAst = compiled.astNodes.map((node) => {
let candidate = compiled.nodeSorting.get(node)?.candidate
let candidateOffset = candidate ? candidateOffsets[candidate] : undefined
node = cloneAstNode(node)
if (!src || !candidate || candidateOffset === undefined) {
// While the original nodes may have come from an `@utility` we still
// want to replace the source because the `@apply` is ultimately the
// reason the node was emitted into the AST.
walk([node], (node) => {
node.src = src
})
return node
}
let candidateSrc: SourceLocation = [src[0], src[1], src[2]]
candidateSrc[1] += 7 /* '@apply '.length */ + candidateOffset
candidateSrc[2] = candidateSrc[1] + candidate.length
// While the original nodes may have come from an `@utility` we still
// want to replace the source because the `@apply` is ultimately the
// reason the node was emitted into the AST.
walk([node], (node) => {
node.src = candidateSrc
})
return node
})
// Collect the nodes to insert in place of the `@apply` rule. When a rule
// was used, we want to insert its children instead of the rule because we
// don't want the wrapping selector.
let newNodes: AstNode[] = []
for (let candidateNode of candidateAst) {
if (candidateNode.kind === 'rule') {
for (let child of candidateNode.nodes) {
newNodes.push(child)
}
} else {
newNodes.push(candidateNode)
}
}
return WalkAction.Replace(newNodes)
}
})
}
return features
}
Domain
Subdomains
Source
Frequently Asked Questions
What does substituteAtApply() do?
substituteAtApply() is a function in the tailwindcss codebase.
What does substituteAtApply() call?
substituteAtApply() calls 8 function(s): cloneAstNode, compileCandidates, get, resolveApplyDependencies, rule, segment, toCss, walk.
What calls substituteAtApply()?
substituteAtApply() is called by 4 function(s): buildPluginApi, createUtilitySignatureCache, createVariantSignatureCache, parseCss.
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free