|
| 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. |
0 commit comments