Skip to content

Client Assets ​

A devframe's UI is a built SPA; clientAssets says where it lives — a local directory or published npm package.

Mounting a local build ​

Point clientAssets at your SPA build directory, resolved from the module:

ts
import { fileURLToPath } from 'node:url'
import { defineDevframe } from 'devframe'
import pkg from '../package.json' with { type: 'json' }

export default defineDevframe({
  id: 'my-tool',
  version: pkg.version,
  packageName: pkg.name,
  clientAssets: fileURLToPath(new URL('../dist/spa', import.meta.url)),
  setup(ctx) {
    // …
  },
})

devframe serves it with SPA fallback (unknown paths → index.html) and no-store dev caching. Build the SPA with a relative base (vite: { base: './' }); it reads its runtime base from document.baseURI.

The dev, build, and Vite adapters share clientAssets; the deprecated cli.distDir is a fallback when it's unset.

Programmatic hosting from setup ​

To host assets beyond the primary UI, use ctx.views.hostStatic:

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?) takes clientAssets's StaticAssetsSource; dev registers middleware live, build copies into the static output.

Remote assets ​

Give clientAssets a RemoteAssets object naming a published npm package and exact version:

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

const clientAssets: RemoteAssets = {
  package: '@acme/my-tool-assets',
  version: pkg.version,
}

export default defineDevframe({
  id: 'my-tool',
  version: pkg.version,
  packageName: pkg.name,
  importMetaUrl: import.meta.url,
  clientAssets,
  setup(ctx) {
    // …
  },
})

The definition's importMetaUrl is the resolution base.

How assets resolve ​

Per request, resolution tries in order:

  1. Locally installed package — resolved from resolveFrom (default importMetaUrl); served with no network.
  2. On-disk cache — files already fetched, under the project's storage directory.
  3. CDN back-proxy — jsDelivr by default; exact-version URLs are immutable, so caches never stale.

Options ​

FieldPurpose
packagenpm package with the built assets.
versionExact version, usually your pkg.version.
resolveFromLocal-path resolution base. Defaults to importMetaUrl; null skips to cache + CDN.
pathSubpath the assets live under (default dist).
provider'jsdelivr' (default), 'unpkg', or a custom provider (internal mirror).
offlinetrue serves only from local install or cache, never network.

An invalid npm name or non-exact version throws DF0065.

Offline and air-gapped use ​

Install the assets package explicitly — step 1 serves it locally. Set offline: true to never contact the CDN, or point provider at a mirror:

sh
npm install @acme/my-tool-assets

When the assets can't be reached ​

A file absent from local install and cache, with the provider unreachable, raises DF0060; an HTML navigation gets a self-contained error page.

It also posts the failure to window.parent (DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE from devframe/constants, payload RemoteAssetsErrorMessage) for an embedding viewer like @devframes/hub-ui.

Custom provider ​

A custom provider supplies the file URL, optionally a listing:

ts
const clientAssets: RemoteAssets = {
  package: '@acme/my-tool-assets',
  version: pkg.version,
  provider: {
    fileUrl: (name, version, file) =>
      `https://npm.internal.acme.com/${name}@${version}/${file}`,
  },
}

Publishing the assets ​

An ordinary npm package ships the built UI under path (default dist) and exposes its package.json for the resolver:

json
{
  "name": "@acme/my-tool-assets",
  "version": "1.0.0",
  "exports": { "./package.json": "./package.json" },
  "files": ["dist"]
}

Keep its version in lockstep with the tool.

Released under the MIT License.