In this article, we analyze error thrown in substituteAtApply. This error is about circular dependency detected.
walk(rule.nodes, (child) => {
if (child !== node) return
throw new Error(
`You cannot \`@apply\` the \`${candidate}\` utility here because it creates a circular dependency.`,
)
})
This is a high level overview of the code around this error.
walk — recursive function:
Let’s begin with walk:
export function walk(
ast: AstNode[],
visit: (
node: AstNode,
utils: {
parent: AstNode | null
replaceWith(newNode: AstNode | AstNode[]): void
context: Record<string, string>
},
) => void | WalkAction,
parent: AstNode | null = null,
context: Record<string, string> = {},
) {
for (let i = 0; i < ast.length; i++) {
let node = ast[i]
// We want context nodes to be transparent in walks. This means that
// whenever we encounter one, we immediately walk through its children and
// furthermore we also don't update the parent.
if (node.kind === 'context') {
walk(node.nodes, visit, parent, { …context, …node.context })
continue
}
let status = visit(node, {
parent,
replaceWith(newNode) {
ast.splice(i, 1, …(Array.isArray(newNode) ? newNode : [newNode]))
// We want to visit the newly replaced node(s), which start at the
// current index (i). By decrementing the index here, the next loop
// will process this position (containing the replaced node) again.
i -
},
context,
}) ?? WalkAction.Continue
// Stop the walk entirely
if (status === WalkAction.Stop) return
// Skip visiting the children of this node
if (status === WalkAction.Skip) continue
if (node.kind === 'rule') {
walk(node.nodes, visit, node, context)
}
}
}
walk is a recursive function located in ast.ts.
It calls itself recursively when node.kind === ‘context’
or when node.kind === ‘rule’
, breaking condition is based on status
// Stop the walk entirely
if (status === WalkAction.Stop) return
// Skip visiting the children of this node
if (status === WalkAction.Skip) continue
Now let’s zoom out a bit and study the code in the vicinity of walk function in apply.ts
// Verify that we don't have any circular dependencies by verifying that
// the current node does not appear in the new nodes.
walk(newNodes, (child) => {
if (child !== node) return
// At this point we already know that we have a circular dependency.
//
// Figure out which candidate caused the circular dependency. This will
// help to create a useful error message for the end user.
for (let candidate of candidates) {
let selector = `.${escape(candidate)}`
for (let rule of candidateAst) {
if (rule.kind !== 'rule') continue
if (rule.selector !== selector) continue
walk(rule.nodes, (child) => {
if (child !== node) return
throw new Error(
`You cannot \`@apply\` the \`${candidate}\` utility here because it creates a circular dependency.`,
)
})
}
}
})
TailwindCSS authors have added explaining comments across the codebase where required or it makes sense to provide additional context
with comments.
About me:
Hey, my name is Ramu Narasinga. I study large open-source projects and create content about their codebase architecture and best practices, sharing it through articles, videos.
I am open to work on an interesting project. Send me an email at ramu.narasinga@gmail.com
My Github - https://github.com/ramu-narasinga
My website - https://ramunarasinga.com
My Youtube channel - https://www.youtube.com/@thinkthroo
Learning platform - https://thinkthroo.com
Codebase Architecture - https://app.thinkthroo.com/architecture
Best practices - https://app.thinkthroo.com/best-practices
Production-grade projects - https://app.thinkthroo.com/production-grade-projects
Top comments (0)