oj README

The project's README from github.com/lovablelabs/oj. As one markdown file: README.md.

OJ logo
OJ

OJ is a Rust-native build tool for React apps.

It optimizes for memory and cold start, where running many builds (CI, agents, multi-tenant) under Vite gets expensive. OJ is meant to run real production React apps without changes to their source.

Server rendering

There is an SSR mode: oj dev --ssr src/entry-server.tsx in dev, and oj build --ssr for production. It streams the HTML out with renderToReadableStream instead of buffering, and the client hydrates through the normal dev pipeline, so Fast Refresh and HMR keep working over a server-rendered page.

In dev the server modules run in a small persistent Node process (a module runner using vm.SourceTextModule) that re-evaluates only what changed, instead of rebuilding a bundle per request.

Plugins

Vite/Rollup-style plugins run through a persistent Node plugin host. Drop an oj.plugins.mjs at the app root that default-exports a plugin array, or let oj read an app's vite.config.{ts,js,mjs} and pick up its plugins array directly.

A TypeScript config (including one that imports local .ts files) is loaded via Vite's own config loader when Vite is installed, or bundled with the app's esbuild otherwise. From a vite.config oj also adopts the app's base, server.port/host, define, and resolve.alias for any field its own config leaves unset, alias entries resolve alongside tsconfig paths, in both oj dev and oj build.

TanStack Start

Point oj at a TanStack Start app and it runs the framework directly, no source changes. It detects Start from the app itself:

oj dev web                                       # dev server for the ./web Start app
oj build web                                     # production build

Dev serves file-based routes with streaming SSR and client hydration, keeps Fast Refresh working, runs server functions in the module runner, and applies the app's vite.config plugins (React and the app's own). oj build emits a Node server.mjs, a Cloudflare worker.mjs (nodejs_compat), content-hashed client assets, and prerendered routes. oj's own docs site (www/) is a TanStack Start app built by oj and served from a Cloudflare Worker.

Quickstart

Install the CLI from crates.io, then run oj in any app:

cargo install oj --locked                       # install the `oj` CLI
oj dev                                           # dev server for the current app on :5199
oj build                                         # production build into ./dist

Or with Nix:

nix run github:lovablelabs/oj -- dev

Supported targets

oj embeds V8, so it builds on the targets rusty_v8 ships prebuilt static libs for: Linux glibc x64/arm64, Linux musl x64/arm64, macOS x64/arm64, and Windows MSVC x64/arm64.

Benchmarks

Generated fanout-10 React component trees, measured save-to-paint with Playwright against Vite 8.2.1 (Rolldown-based) on an M-series Mac, in both its default dev mode (vite) and its experimental bundled dev mode (vite-fbm). p50/p95 over 5 cold+warm restart cycles and 10 HMR edits.

1,000 components (p50/p95):

tool cold start warm start reload HMR server RSS
oj --bundle 311/315ms 240/243ms 41/42ms 56/57ms 43MB
oj (unbundled) 434/582ms 352/354ms 170/173ms 56/59ms 43MB
vite 701/734ms 653/659ms 172/176ms 55/73ms 434MB
vite-fbm 326/344ms 334/338ms 49/52ms 55/58ms 361MB

5,000 components (p50/p95):

tool cold start warm start reload HMR server RSS
oj --bundle 750/783ms 588/604ms 132/133ms 59/61ms 75MB
oj (unbundled) 1281/1402ms 1058/1080ms 744/839ms 60/63ms 65MB
vite 2649/2656ms 2424/2532ms 758/807ms 56/147ms 949MB
vite-fbm 927/970ms 965/973ms 174/177ms 59/80ms 976MB

10,000 components (p50/p95):

tool cold start warm start reload HMR server RSS
oj --bundle 1315/1333ms 1129/1442ms 232/237ms 67/72ms 115MB
oj (unbundled) 2492/2543ms 2068/2303ms 1523/1687ms 67/176ms 94MB
vite 5693/6085ms 5304/6289ms 1781/1820ms 59/64ms 1552MB
vite-fbm 1528/2225ms 1482/2176ms 302/414ms 59/65ms 1751MB

Bundle-mode oj wins cold start, warm start, and reload against Vite's default dev at every size (4-8x at 10k), and now also beats Vite's experimental bundled dev on all three at every size (cold, warm, and reload). HMR is a wash across all four (within ~10ms). oj's decisive, consistent win is memory: 43-115MB against Vite's 361MB-1.75GB, an 8-15x gap that widens with app size.

Production builds (oj build vs vite build) land at parity: same engine (Rolldown), byte-identical output sizes.

Reference reading

Misc

cargo run -p oj -- dev                            # dev server for ./playground on :5199
cargo run -p oj -- dev --bundle                   # registry-runtime bundle mode
cargo run -p oj -- dev --ssr src/entry-server.tsx # streaming SSR + hydration
cargo run -p oj -- build playground               # production build into playground/dist
cargo test --workspace                            # rust unit tests
node --test e2e/unit/*.test.mjs                   # js unit tests (adapter helpers)
node e2e/run.mjs                                  # browser e2e suite (add --bundle for bundle mode)
node e2e/ssr-dev.mjs                              # SSR dev e2e, e2e/ssr-prod.mjs for the built server
node e2e/start.mjs                                # tanstack start integration (see e2e/fixtures/start-app)
node e2e/dep-optimize.mjs                         # dependency pre-bundle + cjs interop integration
node e2e/assets.mjs                               # asset url imports + new URL(import.meta.url)
node e2e/dynamic-import.mjs                       # dynamic import with variables (glob switch)
node e2e/wasm.mjs                                 # wasm ?init instantiation (dev + build)
node e2e/query-assets.mjs                         # ?url/?raw/?inline/?init in bundle mode
node e2e/rolldown-options.mjs                     # build.rollupOptions filenames + external
node e2e/preprocessors.mjs                        # less + stylus css (installs both, then dev+build)
node e2e/assets-inline.mjs                        # assetsInlineLimit: small assets become data uris
node e2e/config-proxy.mjs                         # adopt vite.config server.proxy + ignored-config warnings
node e2e/proxy-regex-context.mjs                  # server.proxy "^..." regex contexts (path + query), like Vite
node e2e/config-function.mjs                      # oj.config function form ({ command, mode }) => config
node e2e/build-target-raw-inline.mjs              # build.target downleveling + ?raw/?inline in build
node e2e/manual-chunks.mjs                        # rollupOptions output.manualChunks vendor splitting
node e2e/svgr.mjs                                 # svg as react component (?react), installs react
node e2e/worker-modes.mjs                         # ?worker in dev, bundle, and production build
node e2e/html-entry.mjs                           # relative index.html script entry (src="src/x")
node e2e/svelte.mjs                               # svelte 5 components in dev, bundle, and build
node e2e/build-mode.mjs                           # build --mode (import.meta.env.MODE + .env.<mode>)
node e2e/hmr-protocol.mjs                         # hmr client derives wss (behind https proxy)
node e2e/hmr-overlay-recovery.mjs                 # startup compile error: buffered overlay, reload on first update
node e2e/hmr-json-update.mjs                      # editing an imported .json hot-updates its boundary (?t= stamp)
node e2e/hmr-unresolved-import.mjs                # missing ./import fails with an overlay; creating the file recovers
node e2e/host-binding.mjs                         # dev/preview --host + server.host bind all interfaces
node e2e/plugin-ws.mjs                             # plugin server.ws custom events (send/on) round-trip
node e2e/plugin-ws-execute.mjs                     # post -> ws broadcast -> client reply -> collect (bridge execute)
node e2e/plugin-middleware.mjs                     # configureServer post body forwarding + transformIndexHtml
node e2e/hmr-gate.mjs                              # hmr gate holds updates until POST /__hmr_flush
node e2e/config-flag.mjs                           # oj dev --config <path> loads an override config
node e2e/config-wrapper.mjs                        # vite.config that calls an external defineConfig wrapper
node e2e/awkward-paths.mjs                         # percent-encoded filenames served, traversal contained
node bench/generate.mjs 1000                      # generate a benchmark app (then npm i inside it)
node bench/run.mjs 1000                           # p50/p95 benchmark vs vite
node bench/card.mjs                               # render bench/card.html to oj-benchmarks.png

Testing

cargo test --workspace                            # unit + integration suites
node e2e/run.mjs                                  # the end-to-end suite

The suite is organized by failure mode rather than by module ~ adversarial input, boundary shapes, contention, injected faults, and properties that hold for every input ~ and docs/development/testing.md describes the layers, the fuzz targets, and the behaviours that are deliberate boundaries rather than gaps.

License

MIT, see LICENSE.

OJ was created by Raphael Amorim and has since migrated to Lovable, which now maintains the project.

oj (lovablelabs/oj)

Searching Database…

606 ·GitHub
这是什么?OJ 是一个用 Rust 编写的 React 项目构建工具——提供类似 Vite 的开发服务器和打包器,针对内存占用和冷启动速度做了优化,专为需要频繁跑构建的场景设计(CI、编程智能体、多租户环境等)。
它是用来替代 Vite 的,还是运行时仍然离不开 Vite?它是一个独立的替代品,不是 Vite 插件:把 oj 指向现有的 React 项目即可直接运行,无需改动源码,如果项目里有 vite.config 它也会自动读取。生产环境构建产物由 Rolldown 生成,也就是 Vite 本身使用的同一套引擎。
怎么在我现有的 React 项目上试用它?从 crates.io 安装 CLI(用 Nix 也行),然后在项目目录里运行 oj。不需要改任何代码——它会自动识别你现有的配置。
我的 Vite 配置和插件还能继续用吗?大部分可以——oj 能读取 vite.config.{ts,js,mjs} 并直接使用其中的 plugins 数组,你也可以在项目根目录放一个 oj.plugins.mjs。对于自身配置未设置的字段,它还会沿用 base、server.port/host、define 和 resolve.alias。
它支持 macOS、Linux 和 Windows 吗?三个平台都支持,覆盖 x64 和 arm64 架构。由于 oj 内嵌了 V8,它只能在 rustyv8 提供预编译静态库的平台上构建:Linux 的 glibc 和 musl、macOS,以及 Windows MSVC。
Vite 已经有了,为什么还要做这个?用 Vite 频繁跑大量构建——CI 任务、编程智能体、多租户环境——在内存和冷启动时间上开销很大。OJ 正是针对这两项开销做优化,同时保留 Vite 的插件模型和构建产物。
开发服务器在多次修改之间是怎么保持快的?在开发模式下,服务端模块运行在一个小型的常驻 Node 进程里,只重新执行发生改动的部分,而不是每次请求都重新打包。
HMR 还像 Vite 那么快吗?是的——基准测试显示,oj 与 Vite 的两种开发模式在 HMR 速度上基本持平,在测试的所有项目规模下差距都在 10ms 左右以内。
它现在能用于实际项目吗?项目自称仍处于实验阶段,但它的设计目标就是原封不动地运行真实的生产环境 React 应用,而且由于生产构建同样使用 Rolldown 引擎,构建产物与 Vite 完全逐字节一致。
What are similar projects?rolldown, router, oxc, esbuild, vite, rollup, rspack, nx
Size
7.1 MB
Stars
606
Age
2mo
Commits · 90 Days