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
Framework kits typically wrap this in a plugin shell. `@vitejs/devtools-kit`'s `createPluginFromDevframe` returns a Vite `Plugin` whose `devtools.setup` calls into `mountDevframe`.
45
45
46
+
### Connecting embedded SPAs
47
+
48
+
A mounted devframe's SPA loads in an iframe at its base (`/__<id>/`) and calls `connectDevframe()`, which fetches `./__connection.json` relative to that base. `mountDevframe` serves it there by calling the host's `mountConnectionMeta(base)` alongside `mountStatic`, so the SPA discovers the RPC/WS endpoint directly. Implement `mountConnectionMeta` on your `DevframeHost` to serve the same connection meta you expose at the hub's own base:
49
+
50
+
```ts
51
+
const host:DevframeHost= {
52
+
mountStatic(base, distDir) { /* serve files */ },
53
+
mountConnectionMeta(base) {
54
+
// serve `${base}__connection.json` → { backend: 'websocket', websocket: port }
55
+
},
56
+
resolveOrigin() { /* … */ },
57
+
getStorageDir(scope) { /* … */ },
58
+
}
59
+
```
60
+
61
+
Hosts that omit `mountConnectionMeta` fall back to same-origin window inheritance, which connects an embedded SPA only when it shares an origin with the hub UI.
62
+
63
+
### Bundled hosts (Next.js)
64
+
65
+
Dev servers with a module bundler (Next's Turbopack/webpack) statically analyse server imports. Plugin packages resolve their SPA dist with `new URL('../dist/...', import.meta.url)` and lazy-load node-side code — child processes, the native `node-pty` PTY backend — that resolves at runtime, not at bundle time. Load them with a dynamic `import()` carrying ignore comments so the bundler keeps them as a runtime Node import:
Each mounted SPA is served at `/__<id>/` and references its assets relatively (`./_next/…`, `./assets/…`). Disable the bundler's trailing-slash redirect so those paths resolve under the mount base:
78
+
79
+
```js
80
+
// next.config.mjs
81
+
exportdefault { skipTrailingSlashRedirect:true }
82
+
```
83
+
84
+
[`examples/minimal-next-devframe-hub/`](https://github.com/devframes/devframe/tree/main/examples/minimal-next-devframe-hub) is a working Next.js App Router host that mounts the built-in plugins this way.
85
+
46
86
### Duplicate devframes
47
87
48
88
When a devframe sharing an already-mounted `id` is mounted onto the same hub, its `duplicationStrategy` decides what happens. By default the first registration wins:
@@ -105,7 +145,12 @@ Plus broadcast notifications (`devframe:terminals:updated`, `devframe:messages:u
105
145
106
146
## Example
107
147
108
-
See [`examples/minimal-vite-devframe-hub/`](https://github.com/devframes/devframe/tree/main/examples/minimal-vite-devframe-hub) for a ~120-line Vite plugin that wires the hub end to end with a vanilla DOM UI. Every framework's hub host follows the same shape: a thin layer that adapts the framework's dev server to the hub.
148
+
Two minimal, copyable hubs mount every built-in plugin (git, terminals, code-server, inspect, a11y) behind an icon dock — the same shape [vite-devtools](https://github.com/vitejs/devtools) wears as the full Vite viewer, shrunk to the smallest thing you can build your own viewer from:
149
+
150
+
-[`examples/minimal-vite-devframe-hub/`](https://github.com/devframes/devframe/tree/main/examples/minimal-vite-devframe-hub) — a ~120-line Vite plugin host with a vanilla DOM UI.
151
+
-[`examples/minimal-next-devframe-hub/`](https://github.com/devframes/devframe/tree/main/examples/minimal-next-devframe-hub) — the same protocol hosted from a Next.js App Router app.
152
+
153
+
Every framework's hub host follows the same shape: a thin `DevframeHost` adapter over the framework's dev server, with the dock/commands/messages/terminals protocol unchanged above it.
A protocol-witness example. The `src/client/devframe/minimal-next-devframe-hub.ts` file wires `@devframes/hub` into a Next.js App Router app by lazily starting a side-car RPC/WS server from a Node route handler.
3
+
A tiny, copyable **vite-devtools-style hub on Next.js**. [vite-devtools](https://github.com/vitejs/devtools) is the full Vite viewer built on `@devframes/hub`; this example wears the same shape — an icon dock, an iframe stage, a subsystem drawer — but hosts it from a Next.js App Router app, lazily starting a side-car RPC/WS server from a Node route handler. It's the reference for bringing the same integrations to any non-Vite host.
4
+
5
+
`src/client/devframe/minimal-next-devframe-hub.ts` is the entire host.
4
6
5
7
## Run it
6
8
@@ -9,28 +11,29 @@ pnpm install
9
11
pnpm --filter minimal-next-devframe-hub dev
10
12
```
11
13
12
-
Open the printed URL. You should see:
14
+
Open the printed URL. The dock on the left lists every mounted tool with its icon:
15
+
16
+
-**Git**, **Terminals**, **Code Server**, **RPC & State Inspector**, **A11y Inspector** — the built-in plugins, each mounted with `mountDevframe`
17
+
-**Next Demo Tool** / **Next Demo Tool B** — two trivial static SPAs that show the bare mount path
13
18
14
-
- A status line showing the RPC backend
15
-
- A **Docks** list with hub built-ins and the mounted demo devframe
16
-
- A **Commands** list populated from server-side registrations
17
-
- A **Messages** list populated via `messages.add()` on the server
18
-
- A **Terminals** list, empty unless a devframe registers one
19
-
- A button that exercises `hub:commands:execute` by dispatching the sample ping command
19
+
Selecting a tool loads its SPA in the stage. The bottom drawer mirrors the hub's **Commands**, **Messages**, and **Terminals** subsystems, plus a button that dispatches a command through `hub:commands:execute`.
20
20
21
21
## What the example proves
22
22
23
-
-`createHubContext()` boots a hub without any Vite-specific code path
24
-
- A `DevframeHost` impl plugs Next host specifics into the hub uniformly
25
-
-`mountDevframe(ctx, def)` registers any `DevframeDefinition` as a dock
26
-
- The built-in `hub:commands:execute` RPC dispatches any registered server command, regardless of how the host was constructed
27
-
- The browser-side `connectDevframe({ baseURL: '/__hub/' })` discovers the WS endpoint via the Next route handler at `/__hub/__connection.json`
23
+
-`createHubContext()` boots a hub with no Vite-specific code path; a `DevframeHost` impl plugs Next specifics (static mounts, connection meta, storage, origin) in uniformly
24
+
-`mountDevframe(ctx, def)` registers any `DevframeDefinition` as a dock and serves both its SPA and its `__connection.json`, so the embedded SPA connects straight back to the hub
25
+
- The browser reads `devframe:docks` / `devframe:commands` shared state and dispatches commands over RPC — byte-for-byte the same protocol the Vite host speaks
26
+
27
+
## Hosting built-in plugins in a bundler
28
+
29
+
The plugins run node-side (child processes, the native `node-pty` PTY backend) and resolve their SPA dist via `new URL(..., import.meta.url)`. Next's bundler would try to inline that, so the host loads them through a bundler-ignored dynamic `import()` and sets `skipTrailingSlashRedirect` (see `next.config.mjs`) so each SPA's relative assets resolve under `/__<id>/`. This is the recipe for any bundled (webpack/Turbopack) host.
28
30
29
31
## Files
30
32
31
33
| File | Role |
32
34
|---|---|
33
-
|`src/client/devframe/minimal-next-devframe-hub.ts`| The Next host — creates hub context and side-car WS |
34
-
|`src/client/app/%5F_hub/%5F_connection.json/route.ts`| Connection-meta endpoint for `/__hub/__connection.json` that starts the singleton host |
35
-
|`src/client/devframe/demo-devframe.ts`| A sample `DevframeDefinition` that plugs into the host |
36
-
|`src/client/app/page.tsx`| The browser-side UI that consumes the hub protocol |
35
+
|`src/client/devframe/minimal-next-devframe-hub.ts`| The Next host — hub context, static-mount registry, side-car WS |
36
+
|`src/client/app/%5F_hub/%5F_connection.json/route.ts`| Boots the singleton host and serves `/__hub/__connection.json`|
37
+
|`src/client/app/%5F_[id]/[[...path]]/route.ts`| Serves each mounted SPA and its connection meta under `/__<id>/`|
38
+
|`src/client/app/page.tsx`| The browser UI that consumes the hub protocol |
39
+
|`src/client/app/icons.ts`| Offline Phosphor icons for the dock |
0 commit comments