@wyw-in-js/babel-preset
Babel preset that bridges classic Babel build pipelines to WyW-in-JS's Oxc-based zero-runtime CSS-in-JS transform.
Repository Health
Technical Analysis
@wyw-in-js/babel-preset is a Babel preset that bridges classic Babel-based build pipelines to WyW-in-JS’s newer Oxc-powered transform engine. Instead of running zero-runtime CSS-in-JS extraction directly inside Babel’s plugin visitor, it serializes the file’s code and plugin options, spawns a synchronous child process that runs the real @wyw-in-js/transform API, and splices the resulting AST and metadata back into the Babel file — so libraries built on wyw-in-js (Linaria, Griffel-style processors, dx-styles) keep working under toolchains still configured through .babelrc or babel.config.js.
The package is explicitly a compatibility shim: it emits a one-time deprecation warning pointing users toward bundler-native integrations (Vite, Webpack, esbuild, Rollup, Turbopack) or the async transform() API for new projects, and exists primarily to keep existing Babel-driven setups working while teams migrate off Linaria or older wyw-in-js versions.
What You Get
- A Babel preset (
wywInJS) you drop intopresetsalongside existing Babel config - A synchronous bridge to
@wyw-in-js/transform’s Oxc-backed extraction, run out-of-process viaspawnSync - Automatic serialization validation that throws a clear, path-specific error when plugin options contain functions, symbols, or other non-serializable values
- A one-time deprecation warning nudging you toward bundler-native integrations or the
transform()API for new setups
Common Use Cases
- Migrating off Linaria’s Babel plugin without immediately rewriting bundler config
- Legacy Jest/CRA-style toolchains that only understand Babel presets, not bundler plugin hooks
- Validating the Oxc transform’s output before switching to dedicated Vite/Webpack/esbuild loaders
- Keeping CI pipelines pinned to Babel-based builds working while planning a cutover to native bundler integrations
Under The Hood
Architecture
The package is a thin adapter: index.ts exports a Babel preset function that, gated by babel.caller(isEnabled), registers a single plugin whose real work happens entirely in its pre(file) hook. That hook builds a transform payload (encoding function/symbol-valued eval.globals through a dedicated codec in globals.ts), validates it is recursively JSON-serializable, and hands it to a synchronous child process (spawnSync) running a standalone runner script that calls the real transform() API from @wyw-in-js/transform inside a fresh cache collection. The parent process then re-parses the returned code with Babel’s own parseSync, splices the new AST into the file, and reshapes the transform’s metadata into Babel’s expected shape. There is no plugin composition beyond this single wrapped plugin — correctness rests entirely on the serialize → subprocess → deserialize → AST-splice round trip.
Tech Stack
TypeScript, ESM-only (Node >=22 engine), built via a repo-wide Oxc/esbuild script and tsc for declaration files, orchestrated by Turborepo across a Bun-managed monorepo. Direct runtime dependencies are minimal and precise: @babel/core for AST/transform types plus workspace siblings @wyw-in-js/shared and @wyw-in-js/transform (which itself pulls in oxc-parser, oxc-resolver, oxc-transform, happy-dom, cosmiconfig, and stylis). Linting runs through a shared @wyw-in-js/eslint-config/library preset, and CI runs on Bun with a frozen lockfile install, build, and ESLint pass.
Code Quality
Tests live in a single __tests__/index.test.ts file using Jest-style APIs, covering deprecation-warning throttling and function-valued config loading through the sync runner, and reuse fixtures from the sibling transform package rather than duplicating a test CSS processor. Error handling is explicit and user-facing throughout — non-serializable options and subprocess/JSON failures all raise descriptive, [wyw-in-js]-prefixed errors that name the exact option path — and TypeScript runs in strict mode via project references split between library and spec configs. No swallowed errors were found.
What Makes It Unique The package’s one genuinely interesting idea is architectural: rather than reimplementing the Oxc-based transform as an in-process synchronous Babel visitor, it spawns a real child process and does a full serialize/deserialize round trip, including a bespoke encode/decode scheme that lets function and symbol values cross that boundary by stringifying and re-evaluating function sources. That’s a legitimate answer to a real sync/async impedance mismatch, but the package is explicit that it is a deprecated stopgap rather than forward-looking design — it exists to unblock Babel-only pipelines during migration, not to introduce new capability.