Skip to content

Commit 06e02b8

Browse files
committed
feat(json-render): ship out-of-box SPA and unsupported-component placeholder
Serve a JSON-render UI with no client build: `@devframes/json-render-ui` now ships a prebuilt standalone SPA under `dist/spa`, exposed through a node-safe `./spa` entry (`jsonRenderSpaDir` + `createJsonRenderDevframe`). The SPA discovers views from a new view index the node factory maintains (`JSON_RENDER_INDEX_KEY`), so an app publishes a view and it renders — one view full-bleed, several under the shared segmented switcher, each labelled by an optional per-view `title`. Let any frontend render a spec even when it doesn't implement every component: the renderer isolates an element whose type is absent from the active registry behind a placeholder (showing the type and a gist of its prop keys) and warns, while the rest of the view renders. Migrate `examples/minimal-json-render` onto the prebuilt SPA, dropping its hand-authored client and Vue/Vite build. Co-authored-by: an agent
1 parent 5d82369 commit 06e02b8

38 files changed

Lines changed: 508 additions & 156 deletions

alias.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export const alias = {
5151
'@devframes/json-render/node': r('json-render/src/node/index.ts'),
5252
'@devframes/json-render': r('json-render/src/index.ts'),
5353
'@devframes/json-render-ui/components': r('json-render-ui/src/components/index.ts'),
54+
'@devframes/json-render-ui/spa': r('json-render-ui/src/spa.ts'),
5455
'@devframes/json-render-ui': r('json-render-ui/src/index.ts'),
5556
'minimal-json-render/dashboard': fileURLToPath(new URL('./examples/minimal-json-render/src/dashboard.ts', import.meta.url)),
5657
'@devframes/plugin-code-server/client': p('code-server/src/client/index.ts'),

docs/examples/minimal-json-render.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ outline: deep
55
# minimal-json-render
66

77
A standalone devframe that serves a **JSON-render view**: the server authors an
8-
`@json-render/core` spec once, and the browser renders it with the official
9-
`@devframes/json-render-ui` Vue frontend.
8+
`@json-render/core` spec once, and the prebuilt `@devframes/json-render-ui` SPA
9+
renders it — the app ships no client build of its own.
1010

11-
Package: `minimal-json-render` · framework: **Vue + Vite**
11+
Package: `minimal-json-render` · frontend: **prebuilt `@devframes/json-render-ui/spa`**
1212

1313
## What it shows
1414

@@ -20,17 +20,16 @@ Package: `minimal-json-render` · framework: **Vue + Vite**
2020
- **Action bridge** — the `Refresh` button's `press` action is dispatched as an
2121
RPC call of the same name; the handler bumps a counter and patches state, with
2222
per-action loading and error surfacing.
23-
- **Standalone rendering**the app supplies the frontend lib
24-
(`@devframes/json-render-ui`); devframe serves the SPA, which subscribes to
25-
the view's shared state and renders it with `JsonRenderView`.
23+
- **Out-of-box SPA**`createJsonRenderDevframe` points `cli.distDir` at the
24+
prebuilt `@devframes/json-render-ui/spa`, which discovers the view from the
25+
view index and renders it — no client build in the example.
2626
- **Static output**`cli:build` snapshots the spec + state as a read-only
2727
render; the action bridge reports actions as unavailable (no live RPC).
2828

2929
## Run it
3030

3131
```sh
3232
pnpm --filter minimal-json-render dev # CLI dev server (live RPC)
33-
pnpm --filter minimal-json-render build # build the Vue client
3433
pnpm --filter minimal-json-render cli:build # static deploy → dist/static
3534
```
3635

docs/guide/json-render.md

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,43 @@ validation.
8989

9090
## Rendering standalone
9191

92-
The app supplies the frontend lib and devframe serves its SPA. Connect, read the
93-
view's shared state, and render it with `JsonRenderView`:
92+
### Out-of-box SPA (no client build)
93+
94+
`@devframes/json-render-ui/spa` ships a prebuilt renderer, so an app serves a
95+
JSON-render UI without authoring or building any client. Wrap the definition
96+
with `createJsonRenderDevframe` — it points `cli.distDir` at the shipped SPA
97+
(`jsonRenderSpaDir`) and sets `spa.loader: 'none'`:
98+
99+
```ts
100+
import { createJsonRenderDevframe } from '@devframes/json-render-ui/spa'
101+
import { createJsonRenderView } from '@devframes/json-render/node'
102+
103+
export default createJsonRenderDevframe({
104+
id: 'my-app',
105+
name: 'My App',
106+
version,
107+
packageName,
108+
homepage,
109+
description,
110+
cli: { command: 'my-app', port: 9800 },
111+
setup(ctx) {
112+
createJsonRenderView(ctx, { id: 'main', title: 'Dashboard', spec })
113+
},
114+
})
115+
```
116+
117+
The SPA discovers views from the **view index** (`JSON_RENDER_INDEX_KEY`), a
118+
shared state the node factory maintains as views are created and disposed. It
119+
renders a single view full-bleed, or presents multiple views under the shared
120+
segmented switcher, labelling each with its `title` (defaulting to the view id).
121+
The `@devframes/json-render-ui/spa` entry is node-safe — it exposes only the
122+
asset path and the wiring helper, pulling in no Vue.
123+
124+
### Custom frontend
125+
126+
To render with your own client, supply the frontend lib and let devframe serve
127+
its SPA. Connect, read the view's shared state, and render it with
128+
`JsonRenderView`:
94129

95130
```ts
96131
import { JSON_RENDER_UPSTREAM_VERSION } from '@devframes/json-render'
@@ -185,5 +220,11 @@ A third party replaces the whole registry — pass a custom `registry` to
185220
`@devframes/json-render-ui` is the reference implementation, not a hard
186221
dependency of the protocol; the hub acquires no Vue.
187222

223+
A frontend need not implement every component. When a spec references a
224+
component the active registry lacks, the renderer isolates that element behind a
225+
placeholder — showing the component type and a gist of its prop keys (`{ label,
226+
onPress }`) — and logs a `console.warn`, while the rest of the view renders
227+
normally.
228+
188229
See the [`minimal-json-render` example](/examples/minimal-json-render) for a
189230
runnable end-to-end app.

examples/minimal-json-render/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ dashboard** that exercises **every base-catalog component** — `Stack`, `Card`,
1919
`Save` sends the bound form values as action params and the server writes the
2020
name into the `KeyValueTable`. Each is dispatched as an RPC call of the same
2121
name, with per-action loading and error surfacing.
22-
- **`@devframes/json-render-ui`** — the SPA (`src/client`) subscribes to the
23-
view's shared state and renders it with `JsonRenderView`. The app supplies the
24-
frontend lib; devframe serves the SPA.
22+
- **`@devframes/json-render-ui/spa`** — the prebuilt out-of-box SPA. This example
23+
has **no client build**: `createJsonRenderDevframe(...)` points `cli.distDir` at
24+
the shipped renderer, which discovers the view from the JSON-render view index
25+
and renders it with `JsonRenderView`.
2526

2627
## Run
2728

2829
```sh
2930
pnpm --filter minimal-json-render dev # live dev server (http://localhost:9877/__minimal-json-render/)
30-
pnpm --filter minimal-json-render build # build the SPA
3131
node bin.mjs build --out-dir dist/static # static snapshot (read-only; actions render disabled)
3232
```
3333

examples/minimal-json-render/package.json

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,17 @@
1414
"minimal-json-render": "./bin.mjs"
1515
},
1616
"scripts": {
17-
"build": "vite build --config src/client/vite.config.ts",
1817
"dev": "node bin.mjs",
1918
"cli:build": "node bin.mjs build --out-dir dist/static",
2019
"typecheck": "tsc --noEmit"
2120
},
2221
"dependencies": {
23-
"@antfu/design": "catalog:frontend",
2422
"@devframes/json-render": "workspace:*",
2523
"@devframes/json-render-ui": "workspace:*",
2624
"cac": "catalog:deps",
27-
"devframe": "workspace:*",
28-
"vue": "catalog:frontend"
25+
"devframe": "workspace:*"
2926
},
3027
"devDependencies": {
31-
"@vitejs/plugin-vue": "catalog:build",
32-
"get-port-please": "catalog:deps",
33-
"unocss": "catalog:frontend",
34-
"vite": "catalog:build"
28+
"get-port-please": "catalog:deps"
3529
}
3630
}

examples/minimal-json-render/src/client/main.ts

Lines changed: 0 additions & 54 deletions
This file was deleted.

examples/minimal-json-render/src/client/vite.config.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

examples/minimal-json-render/src/dashboard.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export const dashboardSpec: DevframeJsonRenderSpec = {
127127
* view handle so callers can build a dock ref from it.
128128
*/
129129
export function createDashboardView(ctx: DevframeNodeContext): JsonRenderView {
130-
const view = createJsonRenderView(ctx, { id: VIEW_ID, spec: dashboardSpec })
130+
const view = createJsonRenderView(ctx, { id: VIEW_ID, title: 'Dashboard', spec: dashboardSpec })
131131

132132
// The action bridge dispatches a spec action as an RPC call of the same
133133
// name. `Refresh` re-samples the metrics.

examples/minimal-json-render/src/devframe.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,26 @@
1-
import { fileURLToPath } from 'node:url'
2-
import { defineDevframe } from 'devframe/types'
1+
import { createJsonRenderDevframe } from '@devframes/json-render-ui/spa'
32
import pkg from '../package.json' with { type: 'json' }
43
import { createDashboardView } from './dashboard.ts'
54

6-
const BASE_PATH = '/__minimal-json-render/'
7-
const distDir = fileURLToPath(new URL('../dist/client', import.meta.url))
8-
9-
export default defineDevframe({
5+
// `createJsonRenderDevframe` presets `spa.loader: 'none'` and points
6+
// `cli.distDir` at the prebuilt `@devframes/json-render-ui` SPA, so this
7+
// example serves the out-of-box renderer with no client build of its own.
8+
export default createJsonRenderDevframe({
109
id: 'minimal-json-render',
1110
name: 'Minimal JSON-Render',
1211
version: pkg.version,
1312
packageName: pkg.name,
1413
homepage: pkg.homepage,
1514
description: pkg.description,
1615
icon: 'ph:layout-duotone',
17-
basePath: BASE_PATH,
16+
basePath: '/__minimal-json-render/',
1817
cli: {
1918
command: 'minimal-json-render',
2019
port: 9877,
21-
distDir,
2220
// Single-user localhost demo — skip the trust handshake so the served SPA
2321
// can call the action RPCs without an OTP round-trip.
2422
auth: false,
2523
},
26-
spa: { loader: 'none' },
2724
setup(ctx) {
2825
createDashboardView(ctx)
2926
},

examples/minimal-json-render/src/shared.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
// Shared constants used by both the node devframe and the browser SPA.
1+
// Shared constants for the node devframe. The prebuilt SPA discovers the view
2+
// from the JSON-render view index, so no client-side view id/key is needed.
23

34
/** Author-supplied, stable view id. */
45
export const VIEW_ID = 'dashboard'
56

6-
/**
7-
* The view's shared-state key. `createJsonRenderView` derives it as
8-
* `devframe:json-render:<scope>:<id>`; the base context's scope is `global`.
9-
* The SPA subscribes to this key directly. (A view's serializable
10-
* `JsonRenderViewRef` carries the same `stateKey`.)
11-
*/
12-
export const STATE_KEY = `devframe:json-render:global:${VIEW_ID}`
13-
147
// Spec action names dispatched by the bridge — each is an RPC method the server
158
// registers.
169
export const REFRESH_ACTION = 'minimal-json-render:refresh'

0 commit comments

Comments
 (0)