Bun.build & OXC transform

How manic wires Bun.build entrypoints, oxc-transform via BunPlugin, hashing, and production minification.

Bun.build & OXC transform

Manic’s “bundler” is native Bun.build plus a thin oxc-transform adapter (packages/manic/src/cli/plugins/oxc.ts). There is no Rollup/Vite/Rspack layer — graphs, hashing, and I/O are Bun’s responsibility; JSX/TS stripping and JSX runtime injection are OXC’s.


oxcPlugin(isDev?)

Source: packages/manic/src/cli/plugins/oxc.ts

ConcernBehavior
Files matched*.ts, *.tsx, *.js, *.jsx outside node_modules
Production targetHard-coded es2022 when isDev === false
Dev targetmanic.config.tsoxc.target (default esnext)
JSXAutomatic runtime; development: isDev
Fast RefreshWhen isDev and oxc.refresh !== false, enables refresh + appends import.meta.hot accept shim for .tsx/.jsx
TypeScriptrewriteImportExtensions (default on), onlyRemoveTypeImports
SourcemapsEmitted only when isDev

getConfig() reads the cached merged ManicConfig — during manic build this is whatever loadConfig() resolved at build start.


Client bundle (target: browser)

Implemented in packages/manic/src/cli/commands/build.ts:

OptionValue
EntryResolved ./app/main via oxc-resolver (must exist as app/main.tsx / .jsx / etc.)
PluginsoxcPlugin() + bun-plugin-tailwind
defineprocess.env.NODE_ENV"production"
namingHashed entry, chunks/, assets/

Outputs drive HTML rewriting: the CLI finds the entry-point JS output and optional .css artifact, then patches app/index.html (or emits a minimal shell) into <outdir>/client/index.html.


API bundles (target: bun)

Skipped entirely when mode === 'frontend' or app/api is missing.

ConcernBehavior
Globapp/api/**/index.ts only — index.tsx is not picked up by this glob today
externalAll dependencies keys from root package.json
minifyfalse at bundle time — compression happens in the oxc-minify pass
NamingDerived from folder path → flat *.js files under <outdir>/api

Each folder’s index.ts is its own entrypoint, which keeps serverless/external deployments aligned with small per-route graphs.


Server bundle (target: bun)

  1. Resolve ./~manic with oxc-resolver.
  2. Read source text and rewrite the import … from './app/index.html' pattern into Bun.file("<outdir>/client/index.html").text() so production serves the built HTML string.
  3. Emit a temporary <outdir>/_entry.ts, Bun.build once with naming.entry = server.js, delete the temp file.

Server compilation uses oxcPlugin() with isDev falsyes2022 transforms.


Minification (oxc-minify)

After all three Bun.build graphs succeed, minifyDir walks **/*.js under:

  • <outdir>/client
  • <outdir>/api (if present)
  • <outdir> (covers server.js)

Each file is read as text and passed through minifySync with:

  • compress.target: 'es2022'
  • mangle: true
  • codegen.removeWhitespace: true

Runs Promise.all across files per directory sweep (three parallel top-level promises for client / api / root).


Plugin preload vs build

HookMechanism
preloadDynamically import()’d; if export is a BunPlugin, registered via Bun.plugin() before client Bun.build — affects resolution/transform for subsequent graphs
buildRuns after client bundle + baseline HTML write; receives emitClientFile, injectHtml, pageRoutes ( discoverRoutes() snapshot ), apiRoutes: []

See also

On this page