Open-Source Wikis

/

Tailwind CSS

/

Features

/

Source maps

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:

  1. Asking the compiler for a source map (compiler.buildSourceMap() or equivalent).
  2. Calling toSourceMap(...) to get the JSON form.
  3. Merging through downstream tools that also produce maps:
    • @tailwindcss/postcss preserves PostCSS's result.map and the compiler's map and lets PostCSS handle the final merge.
    • @tailwindcss/vite hands the map to Vite via the transform return value's map field. Vite merges with its own bundler map.
    • @tailwindcss/cli writes the map to disk when --map is given. The flag accepts true (sidecar .map file) or a string (custom path).
    • @tailwindcss/webpack returns 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 src field. Search for the relevant constructor in packages/tailwindcss/src/ast.ts and 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 from and sources based 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.

Source maps – Tailwind CSS wiki | Factory