The compiler engine
How Panda compiles your styles — a Rust engine built on Oxc, exposed to both Node and the browser.
Panda's compiler is a Rust engine built on Oxc (opens in a new tab). It reads your source, pulls out the styles, and emits atomic CSS in cascade layers — the same model How Panda works describes, running on native code.
If you're coming from an earlier version, the pipeline is what changed:
before ts-morph walks a TypeScript AST → PostCSS generates the CSS
after Oxc parses each file once → native Rust emits the CSS
Your styles and your output don't change (see Upgrading to v2 for the handful of deliberate CSS differences). The machine underneath does.
Two bindings, one engine
The engine is a set of Rust crates. Two bindings wrap it, so the same code runs in both places:
@pandacss/compiler— a native binding (NAPI). The CLI and the Vite, webpack, and Rollup plugins use it.@pandacss/compiler-wasm— the same engine compiled to WASM, for the browser. The playground and editor tooling run on it.
Both talk to a filesystem trait rather than node:fs directly, which is what lets the engine compile to WASM at all.
Node and browser builds produce the same CSS.
One long-lived compiler
The entry point is createCompiler(config). It returns a stateful compiler you hold onto, not a one-shot function.
The expensive work — turning your config into utilities, conditions, recipes, and a resolved token dictionary — happens
once, when you create the compiler. The same instance is reused for every file you parse and every rebuild. You feed it
files as they change (parseFile, parseFiles, parseFileSource) and ask it for CSS; the config isn't recompiled each
time.
That's what makes panda dev cheap: the watcher re-parses only the files that changed and re-emits, against config
state that's already in memory.
The pipeline: extract, encode, emit
Data flows one way. Each stage is a separate crate, and dependencies point in the direction of the arrows — nothing downstream reaches back up.
source → extract → encode → emit → CSS
- Extract. Oxc parses each source file once and matches your style calls —
css(),cva(), patterns, JSX style props, and the rest. One parse per file, no TypeScript program in the hot path. Static values (including localconsts and imports from other files) are folded here. - Encode. Matched usage becomes atomic style records — one property/value/condition per atom, deduplicated.
- Emit. Atoms, recipes, and your static CSS become real CSS rules, grouped into
@layers and written natively. No PostCSS pass.
Codegen runs alongside this to write the styled-system output — the types and helpers you import. A façade crate owns
the multi-file build and watch state and wires the stages together; it's the single entry point both bindings talk to.
What you gain
You write the same Panda — static analysis of your source, atomic CSS, cascade layers, tokens, recipes, conditions. The compiled engine changes what that costs:
- One parse per file, and no
ts-morphAST to build and hold. - A smaller install, since the
ts-morph/ts-evaluatordependency tree is gone. - One shared compiler core behind the tooling. Diagnostics and lint read the same extraction the build does, rather than reimplementing it — see Diagnostics, the ESLint & oxlint plugin, and Editor & IDE tooling.
Still open: CSS optimization
The emitter writes CSS natively and minifies when you set minify: true, but full parity with LightningCSS
minification is still open. If you rely on the last few percent of that minified output, check the result before you
ship it.
See also
- How Panda works for the extraction model this runs.
- Upgrading to v2 for what else changes.