Repository navigation
Persist the ssr environment's transform results across restarts (OJ_SSR_TRANSFORM_CACHE=1, draft) - #317
Draft
bittermandel wants to merge 4 commits into
Draft
Persist the ssr environment's transform results across restarts (OJ_SSR_TRANSFORM_CACHE=1, draft)#317bittermandel wants to merge 4 commits into
bittermandel wants to merge 4 commits into
Conversation
…SR_TRANSFORM_CACHE=1)
…ode carries Vite's injected env
…orts, store-time dep hashes, file-list key
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft, default off. Adds
OJ_SSR_TRANSFORM_CACHE=1, which persists thessrenvironment's transform results in the cache root (ssr-transform-cache.json). On the next start it seeds Vite's module graph with them, so files whose bytes did not change skip every plugin transform. The open items below are why this is a draft.Why
On a Cloudflare-runner TanStack Start app (Lovable's
web/, about 3,400 SSR modules), the first request afterlisteningtakes about 13 s. Measured locally (laptop, warm caches,LOAD_DEV_SECRETS=false):sampleof the oj process during the first SSR: the plugin host'soj-js-enginethread is 100% busy for the whole window. workerd's main thread is 91% idle (kevent).fetchModulecalls in the plugin host: 11,050 calls for 3,357 distinct modules; 7,693 of them arecached: truere-validations.JSON.parse8%, JS about 55% (Babel parse and traverse from the TanStack code-splitter, import protection and server functions; Vite sourcemap combining; import analysis).fetchModuleresults from disk with no module graph (not shippable): first/healthwent from 17.6 s to 10.4 s.How
addWatchFile) are unchanged are installed withmoduleGraph._ensureEntryFromUrl(url, _, {id, meta})plusmod.transformResult.doTransformthen returns them without loading or transforming. Every import of a seeded entry gets a graph node (resolved withresolveUrlfirst, so a failed resolution never lands in Vite's url promise map) and edges both ways, all before any result is published, so a later edit to an unseeded import still reaches its seeded importers. If any of that fails, the snapshot is not used.import.meta.env, or whose transformed code carries Vite's injected env object, is stale when the resolved env changed. This doesn't propagate, because importers don't embed it.cfg.envholds the commit SHA and commit time, so a global env key would void the cache on every commit.\0) modules are never cached, nor are.json/.mapmodules (Vite's import analysis skips them, so their watched files are never recorded).transformwithorder: 'post': imports declared after import analysis never reach the graph.define, plugin names, thedeps_ssroptimizer metadata, the config file plusconfigFileDependencies, and a hash of the sorted file list under the root (excluding dot-dirs,node_modules,dist), so adding or removing a file invalidates the cache (95 ms for 43k files on Lovableweb/). An unreadable key input disables reuse.addWatchFiledeps), are taken when Vite stores the result (updateModuleTransformResult, which Vite skips for a transform that was invalidated mid-flight), not when the snapshot is written.fetchModulehas been quiet for 5 s after the first fetch, via tmp file and rename.OJ_NO_CACHEdisables the cache.Measured (same binary, flag on/off, alternating)
/health200, median (range)Re-measured on
87e18d0(n=5, alternating, same laptop, which ran about 1 s slower overall that hour): off 18.53 s (18.30–19.11), on 13.84 s (13.61–14.33).The table's timings are from
ab1604c;e17273dchanges only env detection, and the checks below were re-run on it. Seeding 4,297 modules takes about 380 ms, including parsing the 113 MB snapshot. Lovable devenv all-Ready, default profile, wherewebis the critical path: 28.29 s (28.0–28.51) off vs 22.72 s (22.66–23.23) on, n=3 each, measured with the same mechanism on an earlier build.Checks run on the app (re-run on
87e18d0; adding a file seeds 0, removing it again hits):GET /returns 200.VITE_GIT_BRANCH) re-transforms the 67 env-dependent modules, and the SSR page shows the new value.Open items (blockers for leaving draft)
loadreads later breaks when that transform is skipped (thewarm-transform-virtual.mjspattern).isSelfAccepting, accepted deps and exports,importedBindings,staticImportedUrls) are not restored on seeded nodes.e2e/ssr-transform-cache.mjs: a server module derived fromimport.meta.env.VITE_CACHE_MARKER. Prove a warm cache hit, change only.env, and require the new value, with an unchanged control module so disabling the cache can't pass the test. It needs the Cloudflare runner deps (OJ_E2E_CF_DEPS). Then the stateful-virtual producer case and a resolver that throws once during restoration (restore must seed 0 and the next request must still resolve).No overlap with #314, #315 or #316 (host liveness). #278 caps a different cache (
moduleInfoCache). #312 changes the dep prebundle path. Both change plugin-host bytes, which invalidates existing snapshots when they land.