Repository navigation
feat(hub): let buildHub bake an already-assembled context (reuse in Vite DevTools) #347
Description
What
buildHub (@devframes/hub/build, #339) bakes a whole static hub — but it owns the entire pipeline: it creates its own createHubContext({ mode: 'build' }), mounts devframes from a devframes/configure input, then does the baking (static-dir copy, renderer manifest, UI artifacts, __client-imports.js, __index.json, hub + per-frame __connection.json, and the RPC dump).
Vite DevTools (@vitejs/devtools + @vitejs/devtools-kit) can't feed it that way, so today it re-implements the whole baker by hand (packages/core/src/node/build-static.ts) and drifts out of sync with upstream — e.g. the 0.9.10 collectStaticRpcDump format change (files became { serialization, fnName, data } wrappers with per-file structured-clone/json encoding) silently broke our hand-rolled JSON.stringify(data) writer until we switched to the now-shared writeStaticRpcDump.
We'd like to reuse buildHub's baking instead of shadowing it. A few things block that.
Why buildHub's own-context model doesn't fit
Vite DevTools assembles its context differently from a plain hub:
- Kit-augmented context. It builds via
createKitContext(wrapscreateHubContext) so the context carries Vite-specific slots (viteConfig/viteServer) that the integrations'setup(ctx)hooks read.buildHub's internalcreateHubContext(...)produces a plain hub context without them. - Devframes arrive as Vite plugins, not a
devframesarray. Built-ins and user integrations are mounted by scanningviteConfig.pluginsfor a.devtools.setuphook and running it against the context — arbitrary Vite plugins, notDevframeDefinitions we can enumerate intobuildHub({ devframes }). - Capability gating happens Vite-side (
shouldSkipSetupByCapabilitiesonplugin.devtools.capabilities[mode]), before mount.
So we need to create + mount the context ourselves and hand the already-mounted context to the baker.
Requests (roughly in priority order)
-
Expose the post-mount baker as a public function that takes an existing, already-mounted
DevframeHubContext. Something likebakeHubStatic(ctx, { outDir, base, ui, renderers, name, version, pretty })that runs exactly the tail ofbuildHub(renderer manifest → UI artifacts →__client-imports.js→__index.json→ hub + per-frame__connection.json→collectStaticRpcDump+writeStaticRpcDump), without creating a context or mounting devframes.buildHubthen becomescreateHubContext+mountDevframes+bakeHubStatic(ctx, opts). This single change unblocks us — we stop shadowing the whole file and inherit format/behaviour fixes for free. -
Frame discovery from an externally-mounted context. The baker needs the installed frames + their bases to write
__index.jsonand per-frame__connection.json. InbuildHubthese come frommountDevframes' return value andhost.mountConnectionMeta. For a context mounted by the caller, please expose a public way to enumerate mounted frames/bases (e.g.ctx.frames, or viagetInternalContext) so the baker can emit the index + per-frame metas it did not mount itself. (Today our hand-rolled builder writes only a single hub__connection.jsonand no__index.json, so a frame SPA loaded standalone can't find the shared dump — the exact gap feat(hub): static hub builds with buildHub #339 fixed forbuildHub.) -
Materialize
views.buildStaticDirsfrom the provided context. In build mode the kit host'smountStaticis a no-op ("baked into build output by createBuild"), so statics registered viaviews.hostStaticare only reachable throughctx.views.buildStaticDirs(each{ baseUrl, source }, some remote-asset sources needingresolveStaticAssetsSource(...).materialize(dir)).buildHubcopies statics as a side effect of its own H3 host'smountduring mounting; the extracted baker should instead iterate the provided context'sviews.buildStaticDirsso an externally-mounted context's statics get copied. -
Don't unconditionally
rm -rfoutDir.buildHubdoesif (existsSync(outDir)) await fs.rm(outDir, { recursive: true }). When baking into an existing app build output (the@devframes/vite/hubbuild: truecase, and ourbuild.withApp), the devtools subtree lives alongside the app's owndist/. Aclean?: booleanopt-out (default keeping today's behaviour) would make it safe to point the baker at a shared directory.
Alternative (smaller scope)
If extracting a public baker is too big, publishing the internal helpers buildHub composes — mountDevframes, renderClientImportsModule, resolveRendererRegistrations, and the __index.json / connection-meta writers (currently private in the assemble chunk; only createHubContext and buildHub are public from @devframes/hub/node and @devframes/hub/build) — would at least let downstreams compose their own baker without copy-pasting upstream. The extracted bakeHubStatic(ctx, opts) is the cleaner outcome, though.
Context
buildHub:packages/hub/src/node/build.ts(from feat(hub): static hub builds with buildHub #339)- Downstream hand-rolled equivalent we want to delete:
vitejs/devtoolspackages/core/src/node/build-static.ts
Created with the help of an agent.
Activity