You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Fixed
fs.watch scales to thousands of watchers (a plugin watching each file of a large build-output tree used to wedge the plugin host before the server ever listened). Three independent costs made it quadratic, and each now follows what Node/libuv and chokidar do on the same APIs: matching runs on its own dispatch thread, so the notify backend thread only forwards events and fs.watch registration can never stall behind matching (libuv does no per-handle work on its watcher threads); an event visits only the watchers indexed on its path, an ancestor of it, or the same file, instead of every watcher by opening files (libuv filters its shared FSEvents stream per handle the same way); and on macOS, where notify rebuilds its FSEvents stream on every watch() call, a watched file registers its parent directory, deduplicated, exactly as chokidar's fsevents handler does, so a directory of watched files costs one registration.
Changed
Start dev serves the client bundle with an ETag from each chunk's content hash (a reload revalidates to a 304 instead of re-downloading), immutable caching for its hashed chunks, and gzip for browsers that accept it. On a 2000-component app the entry goes from 2.7 MB to about 340 KB on the wire.
The dev server's module 304s follow If-None-Match the way HTTP defines it: a list of tags, *, and weak (W/) tags all match, where only the exact single tag did before.
Dependency pre-bundling runs on rolldown, the only engine, as in Vite 8 (which removed its esbuild optimizer): a Vite 8 app pre-bundles with the rolldown its vite declares, any other app with the rolldown vendored with oj, then one it installs itself. Vite 8 apps ship no esbuild, so until now their deps were never pre-bundled and every dependency file was its own request. optimizeDeps.esbuildOptions stays honored the way Vite 8 maps it onto the rolldown run.
Dependency discovery is on by default, as in Vite: oj crawls the app from its entries and pre-bundles every dependency it imports. optimizeDeps.noDiscovery: true limits pre-bundling to optimizeDeps.include. The default entries follow Vite's computeEntries: optimizeDeps.entries, else the config's build.rollupOptions.input, else every html file under the root (node_modules and build.outDir ignored), with <script type="module"> srcs and inline module scripts both scanned; before, only root index.html script srcs were. import.meta.glob patterns are expanded during the scan, as Vite's scanner does, so a dependency imported only by a glob-matched module is still pre-bundled.
With a rolldown pre-bundle, the dependency scan resolves imports through the app's plugins first (with scan: true, like Vite), so an import only a plugin can resolve is still discovered, and optimizeDeps.rolldownOptions applies to the scan and the bundle as in Vite (its plugins join the scan). Dependency bundles define process.env.NODE_ENV as NODE_ENV || mode, like Vite, instead of always development. The scan runs under the optimizer's budget (OJ_OPTIMIZE_TIMEOUT, 120 s default), not the per-plugin-hook one, and the include list resolves through the plugins even with noDiscovery.
What a rolldown pre-bundle leaves external is written as the URL oj serves it at: a dependency's CSS and assets, optimizeDeps.excluded packages, and new URL("./file", import.meta.url) references. Node builtins become Vite's browser-external stub and a missing optional peer dependency throws only when evaluated, both inside the bundle. A require() of an excluded package goes through an import facade, as in Vite, and a dependency entry that ships JSX in a .js file switches the bundle to parse .js as JSX instead of failing it. The pre-bundle cache is keyed on the app's plugin names too, since the scan resolves through them. A bare import that resolves to a non-JS file (a css-main package) is externalized by its resolved type, the externalized asset list matches Vite's (.vtt, .pdf, .txt, ...), the import.meta.url rewrite skips matches inside strings and comments and honors /* @vite-ignore */, and an optimizeDeps.include entry that does not resolve is dropped with a warning.
Runtime dep discovery, as in Vite: a bare import the pre-bundle missed (a package installed mid-session, or a module only reachable through a runtime-built import()) registers when the dev server first serves it and is rewritten to its future optimized URL immediately; requests for that URL wait for the debounced re-optimization to commit. A commit that left previously served entries byte-identical keeps every URL valid and reloads nothing; one that changed them moves all URLs and compile caches to a bumped version and reloads the page once, and a request still carrying the old version gets a 504, like Vite's outdated-dep handling. Discoveries persist in the manifest for the next boot. When no rolldown is found at all, discovery turns itself off and every dependency serves per-file, as a bundler-less app always did.
Removed
OJ_OPTIMIZE_SCAN. Discovery is the default; optimizeDeps.noDiscovery turns it off.