Skip to content

Commit c549b68

Browse files
authored
fix(json-render-ui): stop leaking frontend packages as runtime deps (#254)
1 parent 9c13262 commit c549b68

10 files changed

Lines changed: 69 additions & 288 deletions

File tree

docs/guide/client-context.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Viewers with an HTML pipeline layer injection on top: `@vitejs/devtools` wraps t
3838
| `connect` | Options forwarded to `connectDevframe` when `rpc` is not supplied — pass `baseURL` to point at the hub's connection-meta mount (e.g. `/__hub/`). |
3939
| `clientType` | `'standalone'` (default) — the runtime owns the whole page (a hub UI). `'embedded'` — the runtime lives inside a user app alongside a panel. |
4040
| `loadClientScripts` | Import and run dock entries' client scripts. Default `true`. |
41-
| `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). |
41+
| `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). |
4242

4343
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.
4444

docs/guide/json-render.md

Lines changed: 28 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -124,37 +124,21 @@ path and the wiring helper, pulling in no Vue.
124124

125125
### Custom frontend
126126

127-
To render with your own client, supply the frontend lib and let devframe serve
128-
its SPA. Connect, read the view's shared state, and render it with
129-
`JsonRenderView`:
130-
131-
```ts
132-
import { JsonRenderView } from '@devframes/json-render-ui'
133-
import { connectDevframe } from 'devframe/client'
134-
import { createApp, h, shallowRef } from 'vue'
135-
136-
const rpc = await connectDevframe()
137-
const state = await rpc.sharedState.get('devframe:json-render:global:metrics', { initialValue: null })
138-
const spec = shallowRef(state.value())
139-
state.on('updated', () => {
140-
spec.value = state.value()
141-
})
142-
143-
createApp({
144-
render: () => h(JsonRenderView, {
145-
spec: spec.value,
146-
rpc,
147-
interactive: rpc.connectionMeta.backend !== 'static',
148-
}),
149-
}).mount('#app')
150-
```
127+
A custom frontend renders a view straight from its shared state: connect with
128+
`connectDevframe()`, read the view's state (keyed
129+
`devframe:json-render:<scope>:<id>`), subscribe to its `updated` events, and
130+
render each spec element with your own component registry. The renderer
131+
contract and the base catalog's per-component prop schemas live in the
132+
framework-neutral `@devframes/json-render` package, so a frontend in any
133+
framework implements the same spec — see [Build your own JSON-render
134+
frontend](./build-your-own-json-render-frontend) and the React renderer in the
135+
[Next hub example](/examples/hub-next).
151136

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

157-
### Consuming the reference frontend
141+
### The reference frontend
158142

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

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

176159
## Rendering inside a hub
177160

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

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

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

217203
const host = await createDevframeClientHost({
218-
renderers: { 'json-render': createJsonRenderDockRenderer() },
204+
renderers: { 'json-render': myJsonRenderDockRenderer },
219205
})
220206

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

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

255242
A frontend need not implement every component. When a spec references a
256243
component the active registry lacks, the renderer isolates that element behind a

knip.jsonc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,11 @@
139139
"ignoreDependencies": ["@nuxt/schema"]
140140
},
141141
"packages/json-render-ui": {
142-
// `src/components/index.ts` is already picked up via
143-
// `tsdown.config.ts`; only `spa.ts`/`hub.ts` (node-safe entries) and
144-
// the prebuilt renderer module (built by its own Vite config, consumed
145-
// at runtime via the hub's renderer manifest) need declaring.
142+
// Published node-safe entries are `spa.ts`/`hub.ts`; the browser
143+
// renderer ships only as self-contained Vite bundles (the standalone
144+
// SPA and the prebuilt renderer module, consumed at runtime via the
145+
// hub's renderer manifest). `src/index.ts` stays as the source barrel
146+
// those Vite/Storybook builds resolve, so it's declared as an entry too.
146147
"entry": ["src/{index,spa,hub}.ts", "src/renderer-module/index.ts"],
147148
// The standalone SPA's own Vite config (`src/spa/vite.config.ts`)
148149
// mounts `unocss/vite` with no explicit config path, so UnoCSS

packages/json-render-ui/package.json

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,10 @@
2020
],
2121
"sideEffects": false,
2222
"exports": {
23-
".": "./dist/index.mjs",
24-
"./components": "./dist/components/index.mjs",
2523
"./hub": "./dist/hub.mjs",
2624
"./spa": "./dist/spa.mjs",
2725
"./package.json": "./package.json"
2826
},
29-
"types": "./dist/index.d.mts",
3027
"files": [
3128
"dist"
3229
],
@@ -42,22 +39,22 @@
4239
},
4340
"peerDependencies": {
4441
"@devframes/hub": "workspace:*",
45-
"@devframes/json-render": "workspace:*",
46-
"vue": "^3.5.0"
42+
"devframe": "workspace:*"
4743
},
4844
"peerDependenciesMeta": {
4945
"@devframes/hub": {
5046
"optional": true
47+
},
48+
"devframe": {
49+
"optional": true
5150
}
5251
},
53-
"dependencies": {
54-
"@json-render/vue": "catalog:frontend"
55-
},
5652
"devDependencies": {
5753
"@antfu/design": "catalog:frontend",
5854
"@devframes/hub": "workspace:*",
5955
"@devframes/json-render": "workspace:*",
6056
"@iconify-json/ph": "catalog:frontend",
57+
"@json-render/vue": "catalog:frontend",
6158
"@storybook/addon-docs": "catalog:storybook",
6259
"@storybook/vue3-vite": "catalog:storybook",
6360
"@unocss/preset-icons": "catalog:frontend",
Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,41 @@
11
import { defineConfig } from 'tsdown'
22

3-
// Browser-only library. Vue and the protocol package are peers, so they stay
4-
// external (the consuming app / hub host provides them). Components are plain
5-
// `ComponentFn` render functions in `.ts`, so no SFC compiler is needed.
3+
// Node-safe entries only. The browser renderer (Vue components, the upstream
4+
// renderer, the `@antfu/design` ports) ships exclusively as self-contained
5+
// Vite bundles — the standalone SPA (`src/spa/vite.config.ts`) and the hub
6+
// renderer module (`src/renderer-module/vite.config.ts`) — both of which
7+
// inline vue, `@json-render/vue`, and `@antfu/design` at build time, so no
8+
// frontend package leaks out as a runtime dependency.
9+
//
10+
// These two tsdown entries expose only path/registration helpers pointing at
11+
// those prebuilt bundles; their sole imports are node built-ins plus a couple
12+
// of type-only references. Keep those types external (`neverBundle`) so the
13+
// emitted `.d.mts` references the packages instead of inlining their whole
14+
// type graph.
615
export default defineConfig({
716
entry: {
8-
'index': 'src/index.ts',
9-
'components/index': 'src/components/index.ts',
10-
// Node-safe entry: exposes the prebuilt SPA path + a devframe wiring
11-
// helper. Imports no Vue / `@antfu/design`, only `node:url`.
12-
'spa': 'src/spa.ts',
17+
// Node-safe entry: the prebuilt SPA path + a devframe wiring helper.
18+
// Imports only `node:url` (plus a `devframe` type).
19+
spa: 'src/spa.ts',
1320
// Node-safe entry: the hub renderer-manifest registration pointing at the
1421
// prebuilt module in `dist/renderer/` (built by its own Vite config).
15-
'hub': 'src/hub.ts',
22+
hub: 'src/hub.ts',
1623
},
1724
outExtensions: () => ({ js: '.mjs', dts: '.d.mts' }),
1825
clean: true,
1926
tsconfig: '../../tsconfig.base.json',
2027
dts: true,
21-
platform: 'browser',
28+
platform: 'node',
2229
deps: {
23-
// Keep peers external; `@antfu/design` ships `.vue` source that the
24-
// consumer's Vite (with @vitejs/plugin-vue) compiles, so it must not be
25-
// bundled/parsed here.
26-
neverBundle: ['vue', '@antfu/design', /^@antfu\/design\//, '@devframes/json-render', '@devframes/json-render/core'],
30+
// Type-only references in these node entries — keep them external so the
31+
// `.d.mts` references each package rather than inlining its type graph.
32+
neverBundle: [
33+
'devframe',
34+
'@devframes/hub',
35+
'@devframes/hub/initiate',
36+
'@devframes/json-render',
37+
'@devframes/json-render/hub',
38+
'@devframes/json-render/core',
39+
],
2740
},
2841
})

pnpm-lock.yaml

Lines changed: 3 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/__snapshots__/tsnapi/@devframes/json-render-ui/components.snapshot.d.ts

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

tests/__snapshots__/tsnapi/@devframes/json-render-ui/components.snapshot.js

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

0 commit comments

Comments
 (0)