Skip to content

Commit 90d9fc2

Browse files
committed
docs: introduce Client Assets guide
Add a Client Assets guide covering how a devframe serves its built UI: mounting a local dist directory (the basic form) and, as a deferral, pointing cli.distDir at a published npm package (RemoteAssets) so the SPA is fetched on demand and cached locally. Covers resolution order, options, offline use, custom providers, and publishing. Cross-references the field from Devframe Definition, the build/dev adapters, and the Vite framework page.
1 parent 01acd22 commit 90d9fc2

5 files changed

Lines changed: 131 additions & 3 deletions

File tree

docs/.vitepress/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ function guideGroups(prefix: string) {
2323
items: [
2424
{ text: 'Introduction', link: `${prefix}/guide/` },
2525
{ text: 'Devframe Definition', link: `${prefix}/guide/devframe-definition` },
26+
{ text: 'Client Assets', link: `${prefix}/guide/client-assets` },
2627
{ text: 'Scoped Context', link: `${prefix}/guide/scoped-context` },
2728
{ text: 'Cross-Plugin Services', link: `${prefix}/guide/services` },
2829
{ text: 'RPC', link: `${prefix}/guide/rpc` },

docs/adapters/build.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ await createBuild(devframe, {
2323
| Option | Default | Description |
2424
|--------|---------|-------------|
2525
| `outDir` | `dist-static` | Output directory. Cleared on each build. |
26-
| `distDir` | `def.cli?.distDir` | Override the SPA dist directory. |
26+
| `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). |
2727
| `pretty` | `false` | Pretty-print dump JSON (larger on disk). |
2828

2929
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.

docs/frameworks/vite.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default defineConfig({
2626

2727
## `devframeVitePlugin` — static mount
2828

29-
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.
29+
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.
3030

3131
| Option | Default | Description |
3232
|--------|---------|-------------|

docs/guide/client-assets.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# Client Assets
6+
7+
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.
8+
9+
## Mounting a local build
10+
11+
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:
12+
13+
```ts
14+
import { fileURLToPath } from 'node:url'
15+
import { defineDevframe } from 'devframe'
16+
import pkg from '../package.json' with { type: 'json' }
17+
18+
export default defineDevframe({
19+
id: 'my-tool',
20+
version: pkg.version,
21+
packageName: pkg.name,
22+
cli: {
23+
distDir: fileURLToPath(new URL('../dist/spa', import.meta.url)),
24+
},
25+
setup(ctx) {
26+
//
27+
},
28+
})
29+
```
30+
31+
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.
32+
33+
The [`dev`](/adapters/dev), [`build`](/adapters/build), and [Vite](/frameworks/vite) adapters all consume this same `distDir`.
34+
35+
## Remote assets
36+
37+
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.
38+
39+
Give `cli.distDir` a `RemoteAssets` object naming the package and exact version:
40+
41+
```ts
42+
import type { RemoteAssets } from 'devframe'
43+
import { defineDevframe } from 'devframe'
44+
import pkg from '../package.json' with { type: 'json' }
45+
46+
const distDir: RemoteAssets = {
47+
package: '@acme/my-tool-assets',
48+
version: pkg.version,
49+
resolveFrom: import.meta.url,
50+
}
51+
52+
export default defineDevframe({
53+
id: 'my-tool',
54+
version: pkg.version,
55+
packageName: pkg.name,
56+
cli: { distDir },
57+
setup(ctx) {
58+
//
59+
},
60+
})
61+
```
62+
63+
The UI mounts as usual — the first request for each file is streamed from a CDN and written to a local cache; subsequent requests are served from disk.
64+
65+
### How assets resolve
66+
67+
For each request the source resolves in order:
68+
69+
1. **Locally installed package** — resolved from `resolveFrom` (`import.meta.url`). If `@acme/my-tool-assets` is installed next to your tool, it's served directly with no network. This is the offline path.
70+
2. **On-disk cache** — files already fetched, under the project's storage directory.
71+
3. **CDN back-proxy**[jsDelivr](https://www.jsdelivr.com/) by default, mirroring npm. Each file streams to the browser and is cached on the way past.
72+
73+
Exact-version URLs are immutable, so a cached file never goes stale.
74+
75+
### Options
76+
77+
| Field | Purpose |
78+
|-------|---------|
79+
| `package` | npm package holding the built assets. |
80+
| `version` | Exact version to serve — usually your tool's own `pkg.version`. |
81+
| `resolveFrom` | `import.meta.url` of the declaring module; enables the zero-network path from a locally installed copy. Omit to skip straight to cache + CDN. |
82+
| `path` | Subpath inside the package the assets live under. Defaults to `dist`. |
83+
| `provider` | `'jsdelivr'` (default), `'unpkg'`, or a custom provider for an internal mirror. |
84+
| `offline` | `true` serves only from a local install or the cache — never the network. |
85+
86+
`package` and `version` are interpolated into the CDN URL and cache path, so they're validated: an invalid npm name or non-exact version throws [`DF0065`](../errors/DF0065).
87+
88+
### Offline and air-gapped use
89+
90+
Remote assets are a convenience, not a hard network dependency. To run with no network, install the assets package explicitly — resolution step 1 then serves it locally:
91+
92+
```sh
93+
npm install @acme/my-tool-assets
94+
```
95+
96+
Set `offline: true` to guarantee the CDN is never contacted, or point `provider` at an internal npm mirror.
97+
98+
### Custom provider
99+
100+
A custom provider supplies the file URL, and optionally a file listing (used for correct 404s, SPA fallback, and static builds):
101+
102+
```ts
103+
const distDir: RemoteAssets = {
104+
package: '@acme/my-tool-assets',
105+
version: pkg.version,
106+
resolveFrom: import.meta.url,
107+
provider: {
108+
fileUrl: (name, version, file) =>
109+
`https://npm.internal.acme.com/${name}@${version}/${file}`,
110+
},
111+
}
112+
```
113+
114+
### Publishing the assets
115+
116+
The assets package is an ordinary npm package that ships the built UI under `path` (default `dist`) and exposes its `package.json` so `resolveFrom` can locate it:
117+
118+
```json
119+
{
120+
"name": "@acme/my-tool-assets",
121+
"version": "1.0.0",
122+
"exports": { "./package.json": "./package.json" },
123+
"files": ["dist"]
124+
}
125+
```
126+
127+
Keep its version in lockstep with the tool that declares it, so `version: pkg.version` always points at matching UI.

docs/guide/devframe-definition.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ defineDevframe({
201201
| Field | Type | Description |
202202
|-------|------|-------------|
203203
| `command` | `string` | Binary name surfaced in `--help`. Default: the definition's `id`. |
204-
| `distDir` | `string` | SPA dist directory. **Required** for `dev` / `build`. |
204+
| `distDir` | `string \| RemoteAssets` | SPA dist directory, or a [remote assets](./client-assets) package fetched on demand. **Required** for `dev` / `build`. |
205205
| `port` | `number` | Preferred port for the dev server. |
206206
| `portRange` | `[number, number]` | Port scan range, passed through to `get-port-please`. |
207207
| `random` | `boolean` | Prefer a random open port. |

0 commit comments

Comments
 (0)