tailwindlabs/tailwindcss
@tailwindcss/vite
Active contributors: Robin, Philipp, Jordan
Purpose
The Vite plugin. Lets you write import 'tailwindcss' in a CSS file and have Vite handle the rest, including HMR. Implementation: packages/@tailwindcss-vite/src/index.ts (~690 lines, a single file).
Directory layout
packages/@tailwindcss-vite/
├── package.json # peerDependencies: { vite: ^5 || ^6 || ^7 || ^8 }
├── README.md
├── tsconfig.json
├── tsup.config.ts
└── src/
└── index.ts # the entire plugin (~690 lines)Key abstractions
| Symbol | Description |
|---|---|
tailwindcss(opts): Plugin[] |
Default export. Returns an array of Vite plugins (pre, post, server, etc.). |
Root (internal class) |
Per-input-file state: cached compiler, scanner, mtimes, dependencies. |
createRoot(env, id) |
Construct a Root for a given file/environment. |
createCustomResolver(resolvers) |
Wraps Vite resolvers so @import and @plugin paths can use Vite aliases. |
How it integrates with Vite
The plugin is one factory that returns several Vite plugins because Vite splits responsibilities across the build lifecycle:
graph LR
PrePlugin[Pre plugin<br/>configResolved/configureServer]
TransformPlugin[Transform plugin<br/>load + transform]
PostPlugin[Post plugin<br/>renderChunk]
HMR[HMR plugin<br/>handleHotUpdate]
PrePlugin -->|share state| TransformPlugin
TransformPlugin --> PostPlugin
TransformPlugin -->|subscribe| HMRThe Root map is keyed by Vite environment (ssr, client, etc.) so SSR and client builds get separate caches. Each Root holds:
- The compiled
tailwindcss.compile(...)result. - An
@tailwindcss/oxideScanner. - An
mtimesmap of every file that contributes to the build (CSS imports, JS plugins, configs). - The set of accumulated candidates seen so far.
Pseudocode of the per-file lifecycle:
async function transform(src, id) {
if (!isCss(id)) return
if (specialQuery(id)) return // ?worker, ?raw, ?url, ...
const root = roots.get(env, id) ?? createRoot(env, id)
if (root.compiler === null) {
root.compiler = await compile(src, {
base: dirname(id),
loadModule, loadStylesheet,
// resolves through Vite aliases
})
}
// Update mtimes; if any changed, rebuild compiler
if (anyMtimeChanged(root)) root.compiler = await compile(src, ...)
root.scanner ??= new Scanner({ sources: root.compiler.sources })
for (const cand of root.scanner.scan()) root.candidates.add(cand)
let css = root.compiler.build([...root.candidates])
return shouldOptimize ? optimize(css, { minify }).code : css
}The actual implementation is more elaborate because:
- Vite has both an "old" pre-environment API and a new environment API. The plugin supports both.
- Vite distinguishes SSR vs client transform calls; the plugin maintains separate
Rootinstances per environment. - HMR has to know which CSS files to invalidate when a JS dependency changes — that's what
fullRebuildPathsand themtimestracking is for. - Asset URLs in the input CSS need to be rewritten through Vite's resolver (
createCustomResolver). - Inline styles (
?index=N.cssqueries) and special queries (?worker,?raw,?url,?commonjs-proxy) need to be skipped or handled differently.
Plugin options
type PluginOptions = {
optimize?: boolean | { minify?: boolean };
};Defaults: optimize is enabled when process.env.NODE_ENV === 'production'. Minify follows optimize. Both can be overridden.
File-watcher integration
Unlike the CLI, the plugin doesn't use @parcel/watcher. Vite's existing watcher (chokidar, in dev mode) drives invalidation through handleHotUpdate. The plugin reports fullRebuildPaths to Vite via the module graph so an edit to a JS plugin file invalidates the CSS module that loaded it.
Common pitfalls (and where they're handled)
- Aliases (
@/foo). Resolved viacreateCustomResolverwhich wraps Vite's resolver. The fix for@importand@pluginpaths landing under aliases shipped in v4.2.4 (PR #19947) and was reinforced by PR #19803 (v4.2.2). - Relative paths inside imported CSS. Resolved relative to the importing file, not the entry. Fix referenced in
CHANGELOG.mdforv4.xunder "Fix relative@importand@pluginpaths…" (PR #19965). ?index=N.cssinline styles. Skipped viaINLINE_STYLE_ID_REconstant.- Server-only modules scanned by client CSS. Skip the full reload (PR #19745,
v4.2.2).
Integration points
- Reads from
tailwindcss(not via@tailwindcss/node) for direct compile. - Reads from
@tailwindcss/nodeforenv,Instrumentation,optimize,compile,toSourceMap,clearRequireCache. - Reads from
@tailwindcss/oxidefor theScanner. - Hooks into Vite via the
Plugininterface only; no private internals are touched (other than reaching intoInternalResolveOptions, which the plugin documents as a known caveat).
Entry points for modification
- The transform entry point is the function returned for
transforminside one of the plugin objects intailwindcss(). Look for thetransform: async function(src, id) { ... }block. - HMR behavior is in the
handleHotUpdateand watcher integration sections. - Resolver overrides flow through
createCustomResolverand thecustomCssResolver/customJsResolvervariables insidecreateRoot.
For the equivalents on other bundlers see postcss and webpack.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.