Skip to content

feat(hub): let buildHub bake an already-assembled context (reuse in Vite DevTools) #347

Description

@antfubot

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:

  1. Kit-augmented context. It builds via createKitContext (wraps createHubContext) so the context carries Vite-specific slots (viteConfig/viteServer) that the integrations' setup(ctx) hooks read. buildHub's internal createHubContext(...) produces a plain hub context without them.
  2. Devframes arrive as Vite plugins, not a devframes array. Built-ins and user integrations are mounted by scanning viteConfig.plugins for a .devtools.setup hook and running it against the context — arbitrary Vite plugins, not DevframeDefinitions we can enumerate into buildHub({ devframes }).
  3. Capability gating happens Vite-side (shouldSkipSetupByCapabilities on plugin.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)

  1. Expose the post-mount baker as a public function that takes an existing, already-mounted DevframeHubContext. Something like bakeHubStatic(ctx, { outDir, base, ui, renderers, name, version, pretty }) that runs exactly the tail of buildHub (renderer manifest → UI artifacts → __client-imports.js__index.json → hub + per-frame __connection.jsoncollectStaticRpcDump + writeStaticRpcDump), without creating a context or mounting devframes. buildHub then becomes createHubContext + mountDevframes + bakeHubStatic(ctx, opts). This single change unblocks us — we stop shadowing the whole file and inherit format/behaviour fixes for free.

  2. Frame discovery from an externally-mounted context. The baker needs the installed frames + their bases to write __index.json and per-frame __connection.json. In buildHub these come from mountDevframes' return value and host.mountConnectionMeta. For a context mounted by the caller, please expose a public way to enumerate mounted frames/bases (e.g. ctx.frames, or via getInternalContext) 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.json and 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 for buildHub.)

  3. Materialize views.buildStaticDirs from the provided context. In build mode the kit host's mountStatic is a no-op ("baked into build output by createBuild"), so statics registered via views.hostStatic are only reachable through ctx.views.buildStaticDirs (each { baseUrl, source }, some remote-asset sources needing resolveStaticAssetsSource(...).materialize(dir)). buildHub copies statics as a side effect of its own H3 host's mount during mounting; the extracted baker should instead iterate the provided context's views.buildStaticDirs so an externally-mounted context's statics get copied.

  4. Don't unconditionally rm -rf outDir. buildHub does if (existsSync(outDir)) await fs.rm(outDir, { recursive: true }). When baking into an existing app build output (the @devframes/vite/hub build: true case, and our build.withApp), the devtools subtree lives alongside the app's own dist/. A clean?: boolean opt-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


Created with the help of an agent.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions