analyze() — tailwindcss Function Reference
Architecture documentation for the analyze() function in analyze.ts from the tailwindcss codebase.
Entity Profile
Dependency Diagram
graph TD 4d72f15c_1184_4396_0a3a_6a120fe36ae0["analyze()"] 0ee8556f_4c1e_0020_388f_ac4745b8d237["loadSync()"] 4d72f15c_1184_4396_0a3a_6a120fe36ae0 -->|calls| 0ee8556f_4c1e_0020_388f_ac4745b8d237 8d7f2d47_f12b_2c42_06a9_f13327e1726e["resolveCssId()"] 4d72f15c_1184_4396_0a3a_6a120fe36ae0 -->|calls| 8d7f2d47_f12b_2c42_06a9_f13327e1726e 98270aaa_96c5_20ee_11bb_e7e96422cda4["error()"] 4d72f15c_1184_4396_0a3a_6a120fe36ae0 -->|calls| 98270aaa_96c5_20ee_11bb_e7e96422cda4 f0667321_097b_1dca_a21c_a0c318a6236a["highlight()"] 4d72f15c_1184_4396_0a3a_6a120fe36ae0 -->|calls| f0667321_097b_1dca_a21c_a0c318a6236a a4d70660_306b_669c_4b1c_828588c6371c["relative()"] 4d72f15c_1184_4396_0a3a_6a120fe36ae0 -->|calls| a4d70660_306b_669c_4b1c_828588c6371c 4cd99e59_ac1e_2a1f_0946_33cc1afd2532["get()"] 4d72f15c_1184_4396_0a3a_6a120fe36ae0 -->|calls| 4cd99e59_ac1e_2a1f_0946_33cc1afd2532 2a20ea29_c850_cd61_5600_aeebbe3dda66["segment()"] 4d72f15c_1184_4396_0a3a_6a120fe36ae0 -->|calls| 2a20ea29_c850_cd61_5600_aeebbe3dda66 07b8d21a_ae23_4142_b433_7694e785c6a7["analyzeImportPaths()"] 4d72f15c_1184_4396_0a3a_6a120fe36ae0 -->|calls| 07b8d21a_ae23_4142_b433_7694e785c6a7 77abe8a1_95ef_fa17_b8b8_8defdf785383["ancestors()"] 4d72f15c_1184_4396_0a3a_6a120fe36ae0 -->|calls| 77abe8a1_95ef_fa17_b8b8_8defdf785383 style 4d72f15c_1184_4396_0a3a_6a120fe36ae0 fill:#6366f1,stroke:#818cf8,color:#fff
Relationship Graph
Source Code
packages/@tailwindcss-upgrade/src/codemods/css/analyze.ts lines 10–300
export async function analyze(stylesheets: Stylesheet[]) {
let isIgnored = await isGitIgnored()
let processingQueue: (() => Promise<Result>)[] = []
let stylesheetsByFile = new DefaultMap<string, Stylesheet | null>((file) => {
// We don't want to process ignored files (like node_modules)
try {
if (isIgnored(file)) {
return null
}
} catch {
// If the file is not part of the current working directory (which can
// happen if you import `tailwindcss` and it's loading a shared file from
// pnpm) then this will throw.
return null
}
try {
let sheet = Stylesheet.loadSync(file)
// Mutate incoming stylesheets to include the newly discovered sheet
stylesheets.push(sheet)
// Queue up the processing of this stylesheet
processingQueue.push(() => processor.process(sheet.root, { from: sheet.file! }))
return sheet
} catch {
return null
}
})
// Step 1: Record which `@import` rules point to which stylesheets
// and which stylesheets are parents/children of each other
let processor = postcss([
{
postcssPlugin: 'mark-import-nodes',
AtRule: {
import(node) {
// Find what the import points to
let id = node.params.match(/['"](.*?)['"]/)?.[1]
if (!id) return
let basePath = node.source?.input.file
? path.dirname(node.source.input.file)
: process.cwd()
// Resolve the import to a file path
let resolvedPath: string | false = false
try {
// We first try to resolve the file as relative to the current file
// to mimic the behavior of `postcss-import` since that's what was
// used to resolve imports in Tailwind CSS v3.
if (id[0] !== '.') {
try {
resolvedPath = resolveCssId(`./${id}`, basePath)
} catch {}
}
if (!resolvedPath) {
resolvedPath = resolveCssId(id, basePath)
}
} catch (err) {
// Import is a URL, we don't want to process these, but also don't
// want to show an error message for them.
if (id.startsWith('http://') || id.startsWith('https://') || id.startsWith('//')) {
return
}
// Something went wrong, we can't resolve the import.
error(
`Failed to resolve import: ${highlight(id)} in ${highlight(relative(node.source?.input.file!, basePath))}. Skipping.`,
{ prefix: '↳ ' },
)
return
}
if (!resolvedPath) return
// Find the stylesheet pointing to the resolved path
let stylesheet = stylesheetsByFile.get(resolvedPath)
// If it _does not_ exist in stylesheets we don't care and skip it
// this is likely because its in node_modules or a workspace package
// that we don't want to modify
if (!stylesheet) return
// Mark the import node with the ID of the stylesheet it points to
// We will use these later to build lookup tables and modify the AST
node.raws.tailwind_destination_sheet_id = stylesheet.id
let parent = node.source?.input.file
? stylesheetsByFile.get(node.source.input.file)
: undefined
let layers: string[] = []
for (let part of segment(node.params, ' ')) {
if (!part.startsWith('layer(')) continue
if (!part.endsWith(')')) continue
layers.push(part.slice(6, -1).trim())
}
// Connect sheets together in a dependency graph
if (parent) {
let meta = { layers }
stylesheet.parents.add({ item: parent, meta })
parent.children.add({ item: stylesheet, meta })
}
},
},
},
])
// Seed the map with all the known stylesheets, and queue up the processing of
// each incoming stylesheet.
for (let sheet of stylesheets) {
if (sheet.file) {
stylesheetsByFile.set(sheet.file, sheet)
processingQueue.push(() => processor.process(sheet.root, { from: sheet.file ?? undefined }))
}
}
// Process all the stylesheets from step 1
while (processingQueue.length > 0) {
let task = processingQueue.shift()!
await task()
}
// ---
let commonPath = process.cwd()
function pathToString(path: StylesheetConnection[]) {
let parts: string[] = []
for (let connection of path) {
if (!connection.item.file) continue
let filePath = connection.item.file.replace(commonPath, '')
let layers = connection.meta.layers.join(', ')
if (layers.length > 0) {
parts.push(`${filePath} (layers: ${layers})`)
} else {
parts.push(filePath)
}
}
return parts.join(' <- ')
}
let lines: string[] = []
for (let sheet of stylesheets) {
if (!sheet.file) continue
let { convertiblePaths, nonConvertiblePaths } = sheet.analyzeImportPaths()
let isAmbiguous = convertiblePaths.length > 0 && nonConvertiblePaths.length > 0
if (!isAmbiguous) continue
sheet.canMigrate = false
let filePath = sheet.file.replace(commonPath, '')
for (let path of convertiblePaths) {
lines.push(`- ${filePath} <- ${pathToString(path)}`)
}
for (let path of nonConvertiblePaths) {
lines.push(`- ${filePath} <- ${pathToString(path)}`)
}
}
if (lines.length === 0) {
let tailwindRootLeafs = new Set<Stylesheet>()
for (let sheet of stylesheets) {
// If the current file already contains `@config`, then we can assume it's
// a Tailwind CSS root file.
sheet.root.walkAtRules('config', () => {
sheet.isTailwindRoot = true
return false
})
if (sheet.isTailwindRoot) continue
// If an `@tailwind` at-rule, or `@import "tailwindcss"` is present,
// then we can assume it's a file where Tailwind CSS might be configured.
//
// However, if 2 or more stylesheets exist with these rules that share a
// common parent, then we want to mark the common parent as the root
// stylesheet instead.
sheet.root.walkAtRules((node) => {
if (
node.name === 'tailwind' ||
(node.name === 'import' && node.params.match(/^["']tailwindcss["']/)) ||
(node.name === 'import' && node.params.match(/^["']tailwindcss\/.*?["']$/))
) {
sheet.isTailwindRoot = true
tailwindRootLeafs.add(sheet)
}
})
}
// Only a single Tailwind CSS root file exists, no need to do anything else.
if (tailwindRootLeafs.size <= 1) {
return
}
// Mark the common parent as the root file
{
// Group each sheet from tailwindRootLeafs by their common parent
let commonParents = new DefaultMap<Stylesheet, Set<Stylesheet>>(() => new Set<Stylesheet>())
// Seed common parents with leafs
for (let sheet of tailwindRootLeafs) {
commonParents.get(sheet).add(sheet)
}
// If any 2 common parents come from the same tree, then all children of
// parent A and parent B will be moved to the parent of parent A and
// parent B. Parent A and parent B will be removed.
let repeat = true
repeat: while (repeat) {
repeat = false
for (let [sheetA, childrenA] of commonParents) {
for (let [sheetB, childrenB] of commonParents) {
if (sheetA === sheetB) continue
// Ancestors from self to root. Reversed order so we find the
// nearest common parent first
//
// Including self because if you compare a sheet with its parent,
// then the parent is still the common sheet between the two. In
// this case, the parent is the root file.
let ancestorsA = [sheetA].concat(Array.from(sheetA.ancestors()).reverse())
let ancestorsB = [sheetB].concat(Array.from(sheetB.ancestors()).reverse())
for (let parentA of ancestorsA) {
for (let parentB of ancestorsB) {
if (parentA !== parentB) continue
// Found the parent
let parent = parentA
commonParents.delete(sheetA)
commonParents.delete(sheetB)
for (let child of childrenA) {
commonParents.get(parent).add(child)
}
for (let child of childrenB) {
commonParents.get(parent).add(child)
}
// Found a common parent between sheet A and sheet B. We can
// stop looking for more common parents between A and B, and
// continue with the next sheet.
repeat = true
continue repeat
}
}
}
}
}
// Mark the common parent as the Tailwind CSS root file, and remove the
// flag from each leaf.
for (let [parent, children] of commonParents) {
parent.isTailwindRoot = true
for (let child of children) {
if (parent === child) continue
child.isTailwindRoot = false
}
}
return
}
}
{
let error = `You have one or more stylesheets that are imported into a utility layer and non-utility layer.\n`
error += `We cannot convert stylesheets under these conditions. Please look at the following stylesheets:\n`
throw new Error(error + lines.join('\n'))
}
}
Domain
Subdomains
Calls
Source
Frequently Asked Questions
What does analyze() do?
analyze() is a function in the tailwindcss codebase.
What does analyze() call?
analyze() calls 9 function(s): analyzeImportPaths, ancestors, error, get, highlight, loadSync, relative, resolveCssId, and 1 more.
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free