Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/guide/client-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Viewers with an HTML pipeline layer injection on top: `@vitejs/devtools` wraps t
| `connect` | Options forwarded to `connectDevframe` when `rpc` is not supplied — pass `baseURL` to point at the hub's connection-meta mount (e.g. `/__hub/`). |
| `clientType` | `'standalone'` (default) — the runtime owns the whole page (a hub UI). `'embedded'` — the runtime lives inside a user app alongside a panel. |
| `loadClientScripts` | Import and run dock entries' client scripts. Default `true`. |
| `renderers` | Dock renderers to register at boot, keyed by dock `type` (e.g. `{ 'json-render': createJsonRenderDockRenderer() }` from `@devframes/json-render-ui`). Local registrations take precedence over the hub's [renderer manifest](./hub-initiate#renderer-modules). |
| `renderers` | Dock renderers to register at boot, keyed by dock `type` (e.g. `{ 'json-render': myRenderer }` — any implementation of the dock-renderer contract the host bundles). Local registrations take precedence over the hub's [renderer manifest](./hub-initiate#renderer-modules). |

Boot the host once per page: a second boot replaces the published context and logs a warning. `dispose()` tears down its listeners and unpublishes the context it owns.

Expand Down
69 changes: 28 additions & 41 deletions docs/guide/json-render.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,37 +124,21 @@ path and the wiring helper, pulling in no Vue.

### Custom frontend

To render with your own client, supply the frontend lib and let devframe serve
its SPA. Connect, read the view's shared state, and render it with
`JsonRenderView`:

```ts
import { JsonRenderView } from '@devframes/json-render-ui'
import { connectDevframe } from 'devframe/client'
import { createApp, h, shallowRef } from 'vue'

const rpc = await connectDevframe()
const state = await rpc.sharedState.get('devframe:json-render:global:metrics', { initialValue: null })
const spec = shallowRef(state.value())
state.on('updated', () => {
spec.value = state.value()
})

createApp({
render: () => h(JsonRenderView, {
spec: spec.value,
rpc,
interactive: rpc.connectionMeta.backend !== 'static',
}),
}).mount('#app')
```
A custom frontend renders a view straight from its shared state: connect with
`connectDevframe()`, read the view's state (keyed
`devframe:json-render:<scope>:<id>`), subscribe to its `updated` events, and
render each spec element with your own component registry. The renderer
contract and the base catalog's per-component prop schemas live in the
framework-neutral `@devframes/json-render` package, so a frontend in any
framework implements the same spec — see [Build your own JSON-render
frontend](./build-your-own-json-render-frontend) and the React renderer in the
[Next hub example](/examples/hub-next).

In a **static** build the spec + state are snapshotted as a read-only render;
there is no live RPC, so the action bridge reports actions as unavailable and
`interactive: false` renders a static-output notice. Local state and bindings
still work.
there is no live RPC, so actions report as unavailable and a frontend shows a
static-output notice. Local state and bindings still work.

### Consuming the reference frontend
### The reference frontend

`@devframes/json-render-ui` wraps `@antfu/design`'s Vue components directly
(`ActionButton`, `DisplayBadge`, `LayoutCard`, `FormTextInput`, `FormSwitch`,
Expand All @@ -164,14 +148,13 @@ the rest of the devframe surfaces. A few catalog components stay bespoke where
`@antfu/design` has no matching primitive — `Stack`, `Text`, `CodeBlock`, the
value-tree `Tree`, and the row-clickable/loadable `DataTable`.

A consuming Vite app therefore:

- installs `@antfu/design` (a peer dependency) and imports `@antfu/design/styles.css`;
- excludes it from dep pre-bundling so `@vitejs/plugin-vue` compiles its SFCs —
`optimizeDeps: { exclude: ['@antfu/design'] }`;
- composes the shared UnoCSS preset (`presetAnthonyDesign`) and safelists the
runtime-selected badge colors the base catalog can emit —
`safelist: ['badge-color-green', 'badge-color-amber', 'badge-color-red', 'badge-color-blue']`.
It ships as two self-contained prebuilt bundles — the standalone SPA
(`@devframes/json-render-ui/spa`) and the hub renderer module
(`@devframes/json-render-ui/hub`) — each inlining Vue, the upstream renderer,
and the compiled `@antfu/design` styles. A consuming app wires nothing and
pulls no frontend package into its own graph: the SPA is served verbatim as
`cli.distDir`, and the hub module is imported natively by the viewer from the
renderer manifest.

## Rendering inside a hub

Expand Down Expand Up @@ -207,15 +190,18 @@ registration for the type, a viewer shows its missing-renderer fallback
panel.

A host page that builds its own client can register a renderer **locally**
instead — it takes precedence over the manifest:
instead — it takes precedence over the manifest. The renderer is any
implementation of the `JsonRenderDockRenderer` contract the host bundles
itself (the [Next hub example](/examples/hub-next) registers a React one this
way):

```ts
// host page — a locally-bundled frontend wins over the manifest module
import { createDevframeClientHost } from '@devframes/hub/client'
import { createJsonRenderDockRenderer } from '@devframes/json-render-ui'
import { myJsonRenderDockRenderer } from './my-renderer'

const host = await createDevframeClientHost({
renderers: { 'json-render': createJsonRenderDockRenderer() },
renderers: { 'json-render': myJsonRenderDockRenderer },
})

// the viewer mounts the active dock into a container it owns
Expand Down Expand Up @@ -249,8 +235,9 @@ or a local registration at `createDevframeClientHost({ renderers })`.
`@devframes/json-render-ui` is the reference implementation, not a hard
dependency of the protocol; the hub acquires no Vue.

Within a frontend, the registry swaps too — pass a custom `registry` to
`createRenderer({ registry })` or `createJsonRenderDockRenderer({ registry })`.
Within a frontend, the component registry is pluggable too — an implementation
maps each catalog component type to its own component, so a frontend can render
a subset or theme the built-ins without touching the protocol.

A frontend need not implement every component. When a spec references a
component the active registry lacks, the renderer isolates that element behind a
Expand Down
9 changes: 5 additions & 4 deletions knip.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,11 @@
"ignoreDependencies": ["@nuxt/schema"]
},
"packages/json-render-ui": {
// `src/components/index.ts` is already picked up via
// `tsdown.config.ts`; only `spa.ts`/`hub.ts` (node-safe entries) and
// the prebuilt renderer module (built by its own Vite config, consumed
// at runtime via the hub's renderer manifest) need declaring.
// Published node-safe entries are `spa.ts`/`hub.ts`; the browser
// renderer ships only as self-contained Vite bundles (the standalone
// SPA and the prebuilt renderer module, consumed at runtime via the
// hub's renderer manifest). `src/index.ts` stays as the source barrel
// those Vite/Storybook builds resolve, so it's declared as an entry too.
"entry": ["src/{index,spa,hub}.ts", "src/renderer-module/index.ts"],
// The standalone SPA's own Vite config (`src/spa/vite.config.ts`)
// mounts `unocss/vite` with no explicit config path, so UnoCSS
Expand Down
13 changes: 5 additions & 8 deletions packages/json-render-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,10 @@
],
"sideEffects": false,
"exports": {
".": "./dist/index.mjs",
"./components": "./dist/components/index.mjs",
"./hub": "./dist/hub.mjs",
"./spa": "./dist/spa.mjs",
"./package.json": "./package.json"
},
"types": "./dist/index.d.mts",
"files": [
"dist"
],
Expand All @@ -42,22 +39,22 @@
},
"peerDependencies": {
"@devframes/hub": "workspace:*",
"@devframes/json-render": "workspace:*",
"vue": "^3.5.0"
"devframe": "workspace:*"
},
"peerDependenciesMeta": {
"@devframes/hub": {
"optional": true
},
"devframe": {
"optional": true
}
},
"dependencies": {
"@json-render/vue": "catalog:frontend"
},
"devDependencies": {
"@antfu/design": "catalog:frontend",
"@devframes/hub": "workspace:*",
"@devframes/json-render": "workspace:*",
"@iconify-json/ph": "catalog:frontend",
"@json-render/vue": "catalog:frontend",
"@storybook/addon-docs": "catalog:storybook",
"@storybook/vue3-vite": "catalog:storybook",
"@unocss/preset-icons": "catalog:frontend",
Expand Down
41 changes: 27 additions & 14 deletions packages/json-render-ui/tsdown.config.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
import { defineConfig } from 'tsdown'

// Browser-only library. Vue and the protocol package are peers, so they stay
// external (the consuming app / hub host provides them). Components are plain
// `ComponentFn` render functions in `.ts`, so no SFC compiler is needed.
// Node-safe entries only. The browser renderer (Vue components, the upstream
// renderer, the `@antfu/design` ports) ships exclusively as self-contained
// Vite bundles — the standalone SPA (`src/spa/vite.config.ts`) and the hub
// renderer module (`src/renderer-module/vite.config.ts`) — both of which
// inline vue, `@json-render/vue`, and `@antfu/design` at build time, so no
// frontend package leaks out as a runtime dependency.
//
// These two tsdown entries expose only path/registration helpers pointing at
// those prebuilt bundles; their sole imports are node built-ins plus a couple
// of type-only references. Keep those types external (`neverBundle`) so the
// emitted `.d.mts` references the packages instead of inlining their whole
// type graph.
export default defineConfig({
entry: {
'index': 'src/index.ts',
'components/index': 'src/components/index.ts',
// Node-safe entry: exposes the prebuilt SPA path + a devframe wiring
// helper. Imports no Vue / `@antfu/design`, only `node:url`.
'spa': 'src/spa.ts',
// Node-safe entry: the prebuilt SPA path + a devframe wiring helper.
// Imports only `node:url` (plus a `devframe` type).
spa: 'src/spa.ts',
// Node-safe entry: the hub renderer-manifest registration pointing at the
// prebuilt module in `dist/renderer/` (built by its own Vite config).
'hub': 'src/hub.ts',
hub: 'src/hub.ts',
},
outExtensions: () => ({ js: '.mjs', dts: '.d.mts' }),
clean: true,
tsconfig: '../../tsconfig.base.json',
dts: true,
platform: 'browser',
platform: 'node',
deps: {
// Keep peers external; `@antfu/design` ships `.vue` source that the
// consumer's Vite (with @vitejs/plugin-vue) compiles, so it must not be
// bundled/parsed here.
neverBundle: ['vue', '@antfu/design', /^@antfu\/design\//, '@devframes/json-render', '@devframes/json-render/core'],
// Type-only references in these node entries — keep them external so the
// `.d.mts` references each package rather than inlining its type graph.
neverBundle: [
'devframe',
'@devframes/hub',
'@devframes/hub/initiate',
'@devframes/json-render',
'@devframes/json-render/hub',
'@devframes/json-render/core',
],
},
})
7 changes: 3 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

This file was deleted.

This file was deleted.

Loading
Loading