# Bundling URL: /runtime/guides/bundling `@taucad/runtime` ships dozens of static and dynamic assets — kernel WASM blobs, font atlases, transcoder plugins, middleware modules — and every consumer needs the same bundler invariants for them to resolve at runtime. One plugin carries them all: ```typescript import { tauRuntime } from '@taucad/runtime/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [tauRuntime()], }); ``` That single plugin: * Sets the [cross-origin-isolation](/runtime/guides/cross-origin-isolation) headers on dev and preview servers. * Keeps `.wasm` files out of the base64 inline path. * Forces `worker.format: 'es'` so worker bundles preserve `import.meta.url`. * Emits literal runtime assets in client and SSR builds. * Replaces dead Node builtin branches with actionable stubs in browser graphs. When any of these go missing, the runtime appears to load and then 404s on the first WASM fetch. One implementation covers Vite 7 and Vite 8; Webpack 5 and Parcel 2 recognize the same asset pattern but have no shipped plugin. ## How asset emission works [#how-asset-emission-works] Every WASM, font, and dynamically imported plugin chunk inside `@taucad/runtime` is referenced via the static `new URL(literal, import.meta.url)` pattern. Vite, Webpack 5, Parcel 2, and esbuild all treat it as a first-class asset reference: the bundler copies the file into `dist/`, hashes the filename, and rewrites the literal. Any deviation — a variable, a function call, a template expression — opts out of emission, and the file silently fails to ship. A workspace lint rule enforces the invariant inside `packages/runtime`. Kernels, middleware, and transcoders are declared in a worker-owned `defineRuntime(...)` entry: the worker entry value-imports the executable plugin factories, and client code imports only the runtime type. ## Common failure modes [#common-failure-modes] | Symptom | Likely cause | | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | | Worker entry missing from a production build | Reference the source entry with `new Worker(new URL('./runtime.worker.ts', import.meta.url), { type: 'module' })`. | | WASM blob inlined as a base64 data URL; worker fails on `instantiateStreaming` | Asset inlined under the default 4 KB threshold or a custom rule. `tauRuntime()` opts WASM out. | | `Cannot find module ./middleware/parameter-cache.js` | The bundler folded the runtime into one chunk; keep the runtime package external. | | `crossOriginIsolated === false`, `SharedArrayBuffer` undefined | Missing COI headers — see [Cross-Origin Isolation](/runtime/guides/cross-origin-isolation). | ## Opting out of the COI middleware [#opting-out-of-the-coi-middleware] When the host already sets COOP/COEP through Express or deployment headers, pass `tauRuntime({ crossOriginIsolation: false })` to disable only the bundled middleware. The standalone `crossOriginIsolation()` export stays available for hosts composing their own isolation layers. ## React Router [#react-router] Place Tau beside React Router's native Vite plugin — `tauRuntime()` returns a plugin array Vite flattens, so no spread is required: ```typescript import { reactRouter } from '@react-router/dev/vite'; import { tauRuntime } from '@taucad/runtime/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [tauRuntime(), reactRouter()], }); ``` The identical configuration covers React Router 7/Vite 7 and React Router 8/Vite 8. For SSR response headers, also apply `applyHandleRequestHeaders()` or `coiMiddleware()` in an Express host. ## Next.js [#nextjs] Wrap the application config once. The composer preserves application headers and bundler settings, adds the isolation headers, and configures browser-worker assets for both supported production bundlers: ```typescript import { withTauRuntime } from '@taucad/runtime/nextjs/config'; import type { NextConfig } from 'next'; export default withTauRuntime({ output: 'standalone', } satisfies NextConfig); ``` `withTauRuntime()` composes an existing `webpack` callback and `turbopack` object without inspecting the installed version. ## Electron [#electron] Wrap the ordinary electron-vite process config once. The adapter installs `tauRuntime()` in main and renderer, preserves preload isolation, and configures the native dependency externalizer. Make each utility a main input: ```typescript import { resolve } from 'node:path'; import { electronRuntimeConfig } from '@taucad/runtime/electron/vite'; export default electronRuntimeConfig({ main: { build: { rolldownOptions: { input: { index: resolve(import.meta.dirname, 'src/main/index.ts'), 'kernel-host': resolve(import.meta.dirname, 'src/tau/kernel-host.ts'), }, }, }, }, preload: {}, renderer: {}, }); ``` On Vite 7, use `rollupOptions`. Each input becomes `.js` beside `index.js`, where main resolves `join(import.meta.dirname, 'kernel-host.js'){:ts}`; shared modules are emitted once. `?modulePath` would build the utility separately, bundling them again. It fits only a lone utility sharing nothing with main; a runtime host always shares runtime code. ## Qualified framework versions [#qualified-framework-versions] | Framework | Maintained lane | Current lane | | ------------ | ------------------------ | ----------------------------- | | Electron | electron-vite 5 / Vite 7 | electron-vite 6 beta / Vite 8 | | React Router | React Router 7 / Vite 7 | React Router 8 / Vite 8 | | Next.js | Next.js 15 / Webpack | Next.js 16 / Turbopack | The helpers do not inspect versions; the installed framework selects its own supported path. See [Framework Integrations API](/runtime/api/frameworks) for the complete public types and process-boundary helpers. ## References [#references] * [docs/research/runtime-zero-config-bundling.md](https://github.com/taucad/tau/blob/main/docs/research/runtime-zero-config-bundling.md) * [Vite — Static Asset Handling](https://vite.dev/guide/assets.html)