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
4 changes: 2 additions & 2 deletions docs/adapters/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ outline: deep

Produces a self-contained static deploy of a devframe:

1. Copies the author's SPA dist (`cli.distDir` or `options.distDir`) into `<outDir>`.
1. Copies the author's SPA dist (`clientAssets` or `options.distDir`) into `<outDir>`.
2. Runs `setup(ctx)` with `mode: 'build'`.
3. Collects RPC dumps for every `'static'` function and any `'query'` function with `dump.inputs` / `snapshot: true`.
4. Writes `<outDir>/__connection.json` (`{ backend: 'static' }`) and sharded dump files under `<outDir>/__rpc-dump/` — both at the SPA root so the deployed client discovers them via relative paths from `document.baseURI`.
Expand All @@ -23,7 +23,7 @@ await createBuild(devframe, {
| Option | Default | Description |
|--------|---------|-------------|
| `outDir` | `dist-static` | Output directory. Cleared on each build. |
| `distDir` | `def.cli?.distDir` | Override the SPA dist directory (a local path or a [remote assets](/guide/client-assets) package, materialized in full at build time). |
| `distDir` | `def.clientAssets` (falls back to deprecated `def.cli?.distDir`) | Override the SPA dist directory (a local path or a [remote assets](/guide/client-assets) package, materialized in full at build time). |
| `pretty` | `false` | Pretty-print dump JSON (larger on disk). |

The resulting directory hosts on any static web server (`serve`, nginx, GitHub Pages, …). The client auto-detects `static` mode by resolving `./__connection.json` against `document.baseURI` and runs in read-only form.
Expand Down
6 changes: 3 additions & 3 deletions docs/adapters/cac.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { createCac } from 'devframe/adapters/cac'
const devframe = defineDevframe({
id: 'my-devframe',
name: 'My Devframe',
cli: { distDir: './client/dist' },
clientAssets: './client/dist',
setup(ctx) { /* register docks, RPC, etc. */ },
})

Expand Down Expand Up @@ -66,9 +66,9 @@ The `cli` property lets the caller add ad-hoc commands and flags right before `p
```ts
defineDevframe({
id: 'my-devframe',
clientAssets: './client/dist', // built SPA served as the UI
cli: {
command: 'my-devframe', // binary name; default: the id
distDir: './client/dist', // required for dev/build
port: 7777, // preferred port
portRange: [7777, 9000], // passed through to get-port-please
random: false, // passed through to get-port-please
Expand All @@ -87,7 +87,7 @@ defineDevframe({
})
```

`distDir` is the only required field; everything else has sensible defaults. The `configure` hook runs *before* the `configureCli` option passed to `createCac`, so the final tool author always has the last word on flags.
The top-level [`clientAssets`](/guide/client-assets) supplies the SPA the dev/build commands serve; everything under `cli` has sensible defaults. The `configure` hook runs *before* the `configureCli` option passed to `createCac`, so the final tool author always has the last word on flags.

## Headless logging

Expand Down
2 changes: 1 addition & 1 deletion docs/adapters/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ process.on('SIGINT', () => handle.close().then(() => process.exit(0)))
| `host` | `def.cli?.host ?? 'localhost'` | Bind host. |
| `port` | resolved via `resolveDevServerPort` | Port to listen on. |
| `flags` | `{}` | Parsed flag bag forwarded to `setup(ctx, { flags })`. |
| `distDir` | `def.cli?.distDir` | Required — throws when neither is set. |
| `distDir` | `def.clientAssets` (falls back to deprecated `def.cli?.distDir`) | SPA dist override. When unset the server runs in bridge mode (meta + WS only). |
| `basePath` | `resolveBasePath(def, 'standalone')` | Mount path override. |
| `app` | fresh h3 app | Pre-configured h3 app to mount onto (custom middleware, auth, extra static assets). |
| `openBrowser` | resolves from `flags.open` / `def.cli?.open` | Explicit on/off override. `false` disables; a string opens that relative path. |
Expand Down
8 changes: 3 additions & 5 deletions docs/errors/DF0059.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ A remote-assets source (`{ package, version }` passed where a static mount accep

```ts
defineDevframe({
cli: {
distDir: {
package: '@devframes/plugin-git-client',
version: '1.2.3',
},
clientAssets: {
package: '@devframes/plugin-git-client',
version: '1.2.3',
},
})
```
Expand Down
8 changes: 3 additions & 5 deletions docs/errors/DF0060.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ A file of a remote-assets source was requested that is neither in the locally in

```ts
defineDevframe({
cli: {
distDir: {
package: '@devframes/plugin-git-client',
version: '1.2.3',
},
clientAssets: {
package: '@devframes/plugin-git-client',
version: '1.2.3',
},
})
```
Expand Down
10 changes: 4 additions & 6 deletions docs/errors/DF0061.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ A remote-assets source found a locally installed copy of its assets package (res

```ts
defineDevframe({
cli: {
distDir: {
package: '@devframes/plugin-git-client',
version: '2.0.0',
resolveFrom: import.meta.url,
},
clientAssets: {
package: '@devframes/plugin-git-client',
version: '2.0.0',
resolveFrom: import.meta.url,
},
})
```
Expand Down
2 changes: 1 addition & 1 deletion docs/errors/DF0064.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ outline: deep

## Cause

A static build (`createBuild`) with a remote-assets `distDir` needs every asset file up front — the output must be self-contained. Materialization walks the provider's file listing and downloads each file, and one of those steps failed: the provider has no `listFiles` (custom providers may omit it), the listing request failed, or an individual file download errored.
A static build (`createBuild`) with remote-assets `clientAssets` needs every asset file up front — the output must be self-contained. Materialization walks the provider's file listing and downloads each file, and one of those steps failed: the provider has no `listFiles` (custom providers may omit it), the listing request failed, or an individual file download errored.

## Example

Expand Down
16 changes: 6 additions & 10 deletions docs/errors/DF0065.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ A remote-assets source's `package` and `version` are interpolated into CDN URLs

```ts
defineDevframe({
cli: {
distDir: {
package: '@devframes/plugin-git-client',
version: '../etc', // ✗ not a semver version
},
clientAssets: {
package: '@devframes/plugin-git-client',
version: '../etc', // ✗ not a semver version
},
})
```
Expand All @@ -31,11 +29,9 @@ Use a valid npm package name and an exact version:

```ts
defineDevframe({
cli: {
distDir: {
package: '@devframes/plugin-git-client',
version: '1.2.3',
},
clientAssets: {
package: '@devframes/plugin-git-client',
version: '1.2.3',
},
})
```
Expand Down
4 changes: 2 additions & 2 deletions docs/errors/DF8106.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ outline: deep

A mounted devframe's SPA loads in an iframe at its own base (e.g. `/__terminals/`) and calls `connectDevframe()`, which fetches `./__connection.json` relative to that base to discover the RPC/WebSocket endpoint. `ctx.install` serves that file at each base by calling the host's `mountConnectionMeta(base)` alongside `mountStatic`.

This diagnostic is reported when a devframe with a servable `cli.distDir` is mounted on a `DevframeHost` that does not implement `mountConnectionMeta`. The SPA's `./__connection.json` fetch then falls through to the host's HTML fallback, so the SPA cannot discover the endpoint and its panel stays empty or stuck loading — previously a silent failure.
This diagnostic is reported when a devframe with servable `clientAssets` is mounted on a `DevframeHost` that does not implement `mountConnectionMeta`. The SPA's `./__connection.json` fetch then falls through to the host's HTML fallback, so the SPA cannot discover the endpoint and its panel stays empty or stuck loading — previously a silent failure.

The SPA can still connect when it shares an origin with the hub UI, by inheriting the connection meta from the parent window. Cross-origin, sandboxed, or directly-opened iframes have no such parent to inherit from.

Expand All @@ -35,4 +35,4 @@ A static-snapshot host that bakes `__connection.json` into its served files can

## Source

- [`packages/hub/src/node/install-devframe.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/install-devframe.ts) — `ctx.install()` emits this when a devframe with a servable `distDir` is installed on a host lacking `mountConnectionMeta`.
- [`packages/hub/src/node/install-devframe.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/install-devframe.ts) — `ctx.install()` emits this when a devframe with servable `clientAssets` is installed on a host lacking `mountConnectionMeta`.
2 changes: 1 addition & 1 deletion docs/examples/json-render.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Package: `json-render` · frontend: **prebuilt `@devframes/json-render-ui/spa`**
- **Action bridge** — the `Refresh` button's `press` action is dispatched as an
RPC call of the same name; the handler bumps a counter and patches state, with
per-action loading and error surfacing.
- **Out-of-box SPA** — `createJsonRenderDevframe` points `cli.distDir` at the
- **Out-of-box SPA** — `createJsonRenderDevframe` points `clientAssets` at the
prebuilt `@devframes/json-render-ui/spa`, which discovers the view from the
view index and renders it — no client build in the example.
- **Static output** — `cli:build` snapshots the spec + state as a read-only
Expand Down
4 changes: 2 additions & 2 deletions docs/frameworks/nuxt.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export default defineNuxtConfig({

### Relationship to `createCac`

The bridge handles the **dev workflow**. Production deploys still go through `createCac` (or `createBuild`), which produces a static `__connection.json` + `__rpc-dump/` snapshot from `cli.distDir`:
The bridge handles the **dev workflow**. Production deploys still go through `createCac` (or `createBuild`), which produces a static `__connection.json` + `__rpc-dump/` snapshot from `clientAssets`:

```
my-tool/
Expand All @@ -110,7 +110,7 @@ my-tool/
│ └── app/ # Nuxt SPA — uses `@devframes/nuxt`
└── dist/
├── cli.mjs # bundled Node entry
└── public/ # Nuxt build output, pointed at by cli.distDir
└── public/ # Nuxt build output, pointed at by clientAssets
```

In dev (`nuxt dev`) the bridge is live. In production (`<your-cli> build`) the SPA loads the static dump.
Expand Down
2 changes: 1 addition & 1 deletion docs/frameworks/vite.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default defineConfig({

## `devframeVitePlugin` — static mount

Mounts `def.cli.distDir` at `options.base` (`/__<id>/` by default) with SPA fallback. No RPC server is started — useful when you only need the SPA bundle served from a known path. `distDir` may be a local directory or a [remote assets](/guide/client-assets) package.
Mounts `def.clientAssets` at `options.base` (`/__<id>/` by default) with SPA fallback. No RPC server is started — useful when you only need the SPA bundle served from a known path. `clientAssets` may be a local directory or a [remote assets](/guide/client-assets) package.

| Option | Default | Description |
|--------|---------|-------------|
Expand Down
53 changes: 42 additions & 11 deletions docs/guide/client-assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ outline: deep

# Client Assets

A devframe's UI is a built single-page app served as its client. `cli.distDir` tells devframe where those assets live — either a **local directory** bundled with your tool, or a **published npm package** fetched on demand.
A devframe's UI is a built single-page app served as its client. The top-level `clientAssets` field tells devframe where those assets live — either a **local directory** bundled with your tool, or a **published npm package** fetched on demand.

## Mounting a local build

The basic form points `cli.distDir` at the directory your SPA build produces. Resolve it from the module so it works from both source and the published package:
The basic form points `clientAssets` at the directory your SPA build produces. Resolve it from the module so it works from both source and the published package:

```ts
import { fileURLToPath } from 'node:url'
Expand All @@ -19,9 +19,7 @@ export default defineDevframe({
id: 'my-tool',
version: pkg.version,
packageName: pkg.name,
cli: {
distDir: fileURLToPath(new URL('../dist/spa', import.meta.url)),
},
clientAssets: fileURLToPath(new URL('../dist/spa', import.meta.url)),
setup(ctx) {
// …
},
Expand All @@ -30,20 +28,53 @@ export default defineDevframe({

devframe serves that directory with SPA fallback (an unknown path resolves to `index.html`, so client-side routing works) and no-store caching for dev. Build your SPA with a relative base (`vite: { base: './' }`) so the bundle is mount-path portable — it discovers its runtime base from `document.baseURI` and works at `/`, `/__my-tool/`, or any mount point without rewriting.

The [`dev`](/adapters/dev), [`build`](/adapters/build), and [Vite](/frameworks/vite) adapters all consume this same `distDir`.
The [`dev`](/adapters/dev), [`build`](/adapters/build), and [Vite](/frameworks/vite) adapters all consume this same `clientAssets`.

The earlier home for this value, `cli.distDir`, is deprecated but still read as a fallback when `clientAssets` is unset, so existing definitions keep working — move it up to the top level at your convenience.

## Programmatic hosting from `setup`

`clientAssets` is the declarative way to serve the tool's *primary* UI — the adapters resolve it and mount it at the base path for you. When you need to host assets yourself — mount a second static bundle at another path, decide the source at runtime, or serve extra directories alongside the main SPA — reach for `ctx.views.hostStatic` inside `setup`:

```ts
export default defineDevframe({
id: 'my-tool',
version: pkg.version,
packageName: pkg.name,
importMetaUrl: import.meta.url,
clientAssets: fileURLToPath(new URL('../dist/spa', import.meta.url)),
setup(ctx) {
// Serve an extra static bundle at a sibling base.
ctx.views.hostStatic(
'/docs/',
fileURLToPath(new URL('../dist/docs', import.meta.url)),
)

// A remote source works here too — same shape as `clientAssets`.
ctx.views.hostStatic('/legacy/', {
package: '@acme/my-tool-legacy-ui',
version: pkg.version,
})
},
})
```

`hostStatic(baseUrl, source, defaultResolveFrom?)` accepts the same `StaticAssetsSource` (a local directory or a remote declaration) as `clientAssets`. In `dev` mode it registers the middleware live; in `build` mode it copies the files into the static output, so a programmatically hosted bundle survives `createBuild` too. The optional `defaultResolveFrom` overrides the context's own `importMetaUrl` as the resolution base for a remote source — a hub mounting assets on behalf of a plugin passes that plugin's `importMetaUrl` so they resolve against its dependency graph.

Under the hood the adapters resolve `clientAssets` (falling back to the deprecated `cli.distDir`) with the exported `resolveClientAssets(def)` helper and hand it to the host's static mount — `hostStatic` is that same mechanism, exposed for your own bases.

## Remote assets

Instead of a directory, `cli.distDir` can name a **published npm package** that holds the built UI. The assets are then fetched on demand and cached locally, so the node package doesn't bundle its SPA — keeping the installed footprint small, since a plugin's UI is usually the bulk of its tarball.
Instead of a directory, `clientAssets` can name a **published npm package** that holds the built UI. The assets are then fetched on demand and cached locally, so the node package doesn't bundle its SPA — keeping the installed footprint small, since a plugin's UI is usually the bulk of its tarball.

Give `cli.distDir` a `RemoteAssets` object naming the package and exact version:
Give `clientAssets` a `RemoteAssets` object naming the package and exact version:

```ts
import type { RemoteAssets } from 'devframe'
import { defineDevframe } from 'devframe'
import pkg from '../package.json' with { type: 'json' }

const distDir: RemoteAssets = {
const clientAssets: RemoteAssets = {
package: '@acme/my-tool-assets',
version: pkg.version,
}
Expand All @@ -53,7 +84,7 @@ export default defineDevframe({
version: pkg.version,
packageName: pkg.name,
importMetaUrl: import.meta.url,
cli: { distDir },
clientAssets,
setup(ctx) {
// …
},
Expand Down Expand Up @@ -108,7 +139,7 @@ That page also posts its failure to `window.parent` (`DEVFRAME_REMOTE_ASSETS_ERR
A custom provider supplies the file URL, and optionally a file listing (used for correct 404s, SPA fallback, and static builds):

```ts
const distDir: RemoteAssets = {
const clientAssets: RemoteAssets = {
package: '@acme/my-tool-assets',
version: pkg.version,
provider: {
Expand Down
Loading
Loading