tailwindlabs/tailwindcss
There are two independent input streams that converge at `build()`. Stream one is CSS: a stylesheet string is parsed by `CSS.parse`, `@import`s are inlined, at-rules are walked to populate a `Theme`, a `Utilities` registry, and a `Variants` registry, and the result is a `DesignSystem`. Stream two is candidates: the Rust `Scanner` walks the filesystem (gitignore-aware), pre-processes files by extension, runs byte-level state machines to extract class-name-shaped strings, and returns a sorted `Vec<String>` across the NAPI boundary. `build(candidates)` compiles those strings against the design system into AST nodes, splices them into the `@tailwind utilities` placeholder node, runs `optimizeAst`, and prints CSS via `toCss`. State ownership is explicit: `Theme` owns variables, `DesignSystem` owns caches, and the Rust `Scanner` owns file/mtime/candidate sets.
- Input boundary one: `compile(css, opts)` calls `CSS.parse(css, { from })` then `parseCss`, which first wraps the AST in `contextNode({ base }, ast)` and resolves `@import`s through `substituteAtImports` before any at-rule walking happens.
- `@theme` declarations are drained into a `Theme` map keyed by unescaped custom-property name, carrying `ThemeOptions` bit flags (`INLINE`, `REFERENCE`, `DEFAULT`, `STATIC`, `USED`) and a source location; `@keyframes` inside `@theme` are pulled out into `theme.addKeyframes` and re-hoisted as `atRoot` nodes.
- The first `@theme` rule is replaced by a `:root, :host` style rule whose body is filled in *after* the compat hooks run, so JS-config theme extensions land in the same emitted variable block.
- `@source` at-rules are split at parse time: `inline(...)` forms are brace-expanded into `inlineCandidates` (or `ignoredCandidates` when negated, which seed `designSystem.invalidCandidates`), while path forms become `{ base, pattern, negated }` entries returned as `compiler.sources`.
- Every integration builds the scanner source list with the same three-branch rule: `root === 'none'` → no sources, `root === null` → `{ base, pattern: '**/*' }`, else the explicit `source(…)` root, concatenated with `compiler.sources`.
- Input boundary two: `Scanner::scan()` calls `discover_sources()` (a `WalkBuilder` walk that populates `files`, `dirs`, `extensions`, and `mtimes`), then `extract_candidates()`, and returns a `par_sort_unstable`-sorted candidate vector.
- File bytes are transformed before extraction: `pre_process_input(content, extension)` rewrites dialect-specific syntax (e.g. Pug/Haml/Slim shorthand) into something the generic extractor can read, then `Extractor::extract` runs a `CssVariableMachine` and a `CandidateMachine` over the bytes, emitting `Extracted::Candidate` and `Extracted::CssVariable`.
- CSS files found during the walk are routed separately: `extract_candidates` calls `extract_css_variables` on them so that `--my-var` references in stylesheets count as used variables, distinct from utility candidates.
- Candidate strings beginning with `--` are not compiled as utilities at all — `build()` routes them to `designSystem.theme.markUsedVariable(candidate)`, which is how theme variables referenced only from markup survive tree-shaking.
- `compileCandidates` parses each raw candidate into `Candidate[]`, compiles each to AST nodes via `designSystem.compileAstNodes`, and sorts the result by a BigInt variant bitmask first, then by `GLOBAL_PROPERTY_ORDER` index, then by property count, then alphabetically — this is what produces Tailwind's stable output ordering.
- Output boundary: `build()` mutates `utilitiesNode.nodes` in place, runs `optimizeAst(ast, designSystem, opts.polyfills)`, and `compile()`'s wrapper only re-runs `toCss` when the returned AST object identity changed — an explicit identity-based memoization.
- Polyfills are a caller-controlled bitfield (`Polyfills.AtProperty`, `Polyfills.ColorMix`, `Polyfills.All`); the PostCSS plugin masks out `AtProperty` for `.module.css` files because the emitted global `*` rules break CSS Modules purity checks.
- The PostCSS integration converts between AST dialects on both ends — `postCssAstToCssAst(root)` on the way in and `cssAstToPostCssAst(postcss, tailwindCssAst, root.source)` on the way out — and caches the converted PostCSS root so unchanged builds clone rather than re-convert.
- Asset URL rewriting is an opt-in transform in the IO layer, not the core: `@tailwindcss/node`'s `loadStylesheet` wrapper calls `rewriteUrls({ css, root, base })` only when `shouldRewriteUrls` is set, which Vite always sets and PostCSS gates behind `transformAssetUrls`.