tailwindlabs/tailwindcss
Source maps
The compiler can produce a source map alongside the compiled CSS. Adapters then merge that with downstream maps (Lightning CSS minification, PostCSS plugins, Vite's bundler) to give the browser a chain that points back to the original @apply, @theme, or theme(...) call.
Two halves
| Half | Where |
|---|---|
| Generation in the compiler | packages/tailwindcss/src/source-maps/ |
| Plumbing through the adapters | packages/@tailwindcss-node/src/source-maps.ts, plus per-adapter glue |
Generation
packages/tailwindcss/src/source-maps/:
| File | Description |
|---|---|
source.ts |
The SourceLocation type and helpers — what a source position looks like inside the AST. |
line-table.ts |
Fast line-and-column lookup over the original input string (LineTable). Built once, reused for every position lookup. |
source-map.ts |
createSourceMap(...) walks the final AST, reads each node's src field, and produces a DecodedSourceMap in the format that magic-string/@jridgewell/remapping expects. |
translation-map.ts |
Translates positions between intermediate ASTs (e.g. when an @apply brings in declarations from another rule). |
visualize-source-map.ts |
A developer tool that prints a side-by-side ANSI diff of source and generated CSS with arrows drawing each mapping. Used by tests in visualize-source-map.test.ts. |
Every node in the compiler's AST has an optional src: SourceLocation | null field. When the parser builds a node it stores the original byte position; when the compiler clones or constructs new nodes during @apply substitution, candidate compilation, or @theme emission, it propagates the closest source location so the final output map can point back to something meaningful.
When a node was synthesized (e.g. a polyfill wrapper added by optimizeAst) and has no obvious source, the src field is null and that span doesn't appear in the source map.
Plumbing through adapters
packages/@tailwindcss-node/src/source-maps.ts provides toSourceMap(rawMap), which converts the compiler's DecodedSourceMap into a JSON-encoded string that fits into existing tool chains.
Each adapter is responsible for:
- Asking the compiler for a source map (
compiler.buildSourceMap()or equivalent). - Calling
toSourceMap(...)to get the JSON form. - Merging through downstream tools that also produce maps:
@tailwindcss/postcsspreserves PostCSS'sresult.mapand the compiler's map and lets PostCSS handle the final merge.@tailwindcss/vitehands the map to Vite via thetransformreturn value'smapfield. Vite merges with its own bundler map.@tailwindcss/cliwrites the map to disk when--mapis given. The flag acceptstrue(sidecar.mapfile) or a string (custom path).@tailwindcss/webpackreturns the map through Webpack's loader callback. Webpack merges through its own pipeline.
The merging is done through magic-string and @jridgewell/remapping, both via @tailwindcss/node (so the core compiler has no dependency on either).
Optimization step
When optimize: true is set, the build also passes through Lightning CSS, which produces its own source map. @tailwindcss/node merges the compiler map with the Lightning CSS map before returning, so adapters always see one combined map.
Tests
Source map behavior is tested at multiple levels:
source-map.test.ts(~2,000 lines) — direct tests over the compiler's output.translation-map.test.ts— tests for the AST-to-AST translation step.visualize-source-map.test.ts— golden-file tests that assert the visualization matches a snapshot.packages/@tailwindcss-node/src/source-maps.test.ts— tests the adapter-side merging.
The most recent commit on main (commit 1ca0aacd, 2026-04-30) is "Add source map visualization for tests" — making it easier to debug regressions in this area.
Common issues
- Missing line/column information. Usually means an AST node was synthesized without a
srcfield. Search for the relevant constructor inpackages/tailwindcss/src/ast.tsand propagate the location from the source node. - Off-by-one columns. The compiler uses byte offsets internally, but Vite/Webpack want UTF-16 code units. Conversion happens in
@tailwindcss/node. If you see an off-by-one, check whether the original input contained multibyte characters (emoji, accented characters) and whether the conversion ran. - Maps point to the wrong file. Adapters set
fromandsourcesbased on the resource path Vite/PostCSS gave them. If the file path looks wrong in DevTools, trace where the adapter is getting the path from — the issue is usually upstream.
For PostCSS-specific behavior see @tailwindcss/postcss. For Vite-specific behavior see @tailwindcss/vite.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.