Skip to content

Commit 8a1e85e

Browse files
committed
feat(plugins): serve every builtin plugin's SPA from a lockstep assets package
Extends the deferred-assets pattern (piloted on plugin-inspect) to all remaining builtin plugins: og, data-inspector, code-server, assets, messages, terminals, a11y, and git. Each plugin's iframe SPA now ships in a lockstep `@devframes/plugin-<name>--assets` package instead of the node tarball — the Vite build (or git's Next export) emits into assets-pkg/dist, and cli.distDir points at a RemoteAssets declaration. Panel clientScript bundles (messages/terminals dist/client) and a11y's dist/inject agent stay in their node packages. Each plugin declares its `--assets` package as an optional peer dependency (so it resolves when a consumer installs it) alongside the dev-only workspace link (so resolveFrom finds the local build in the monorepo); this optional-peer declaration is also added to the inspect pilot. Wires per-plugin turbo outputs (assets-pkg/dist), knip overrides, and updates each plugin's test harness to resolve the SPA via resolveStaticAssetsSource.
1 parent 3687968 commit 8a1e85e

42 files changed

Lines changed: 522 additions & 98 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

knip.jsonc

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,42 @@
174174
"entry": [],
175175
"project": []
176176
},
177+
// Each plugin's lockstep `--assets` package is referenced only as a
178+
// runtime string (`${pkg.name}--assets` in `cli.distDir`), never imported,
179+
// so knip can't see the dev-only workspace link. A workspace config
180+
// replaces (not merges) the `plugins/*` glob's `entry`, so repeat it.
181+
"plugins/og": {
182+
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
183+
"ignoreDependencies": ["@devframes/plugin-og--assets"]
184+
},
185+
"plugins/og/assets-pkg": { "entry": [], "project": [] },
186+
"plugins/data-inspector": {
187+
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
188+
"ignoreDependencies": ["@devframes/plugin-data-inspector--assets"]
189+
},
190+
"plugins/data-inspector/assets-pkg": { "entry": [], "project": [] },
191+
"plugins/code-server": {
192+
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
193+
"ignoreDependencies": ["@devframes/plugin-code-server--assets"]
194+
},
195+
"plugins/code-server/assets-pkg": { "entry": [], "project": [] },
196+
"plugins/assets": {
197+
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
198+
"ignoreDependencies": ["@devframes/plugin-assets--assets"]
199+
},
200+
"plugins/assets/assets-pkg": { "entry": [], "project": [] },
201+
"plugins/messages": {
202+
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
203+
"ignoreDependencies": ["@devframes/plugin-messages--assets"]
204+
},
205+
"plugins/messages/assets-pkg": { "entry": [], "project": [] },
206+
"plugins/terminals": {
207+
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
208+
"ignoreDependencies": ["@devframes/plugin-terminals--assets"]
209+
},
210+
"plugins/terminals/assets-pkg": { "entry": [], "project": [] },
211+
"plugins/a11y/assets-pkg": { "entry": [], "project": [] },
212+
"plugins/git/assets-pkg": { "entry": [], "project": [] },
177213
"plugins/a11y": {
178214
// `storybook-solidjs-vite` (not an official `@storybook/*` framework
179215
// package) doesn't match knip's Storybook plugin trigger, so it never
@@ -190,15 +226,17 @@
190226
"src/{index,cli,vite,constants,types}.ts",
191227
"src/{client,node,rpc,inject,engine,registry}/index.ts",
192228
"src/**/*.stories.tsx"
193-
]
229+
],
230+
"ignoreDependencies": ["@devframes/plugin-a11y--assets"]
194231
},
195232
"plugins/git": {
196233
// The React client build's root sits at `src/client` (`next dev
197234
// src/client`), not the workspace root, so knip's default PostCSS
198235
// config glob (workspace-root `postcss.config.*`) misses it.
199236
"postcss": {
200237
"config": ["src/client/postcss.config.mjs"]
201-
}
238+
},
239+
"ignoreDependencies": ["@devframes/plugin-git--assets"]
202240
}
203241
}
204242
}

plugins/a11y/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,6 @@ pnpm -C plugins/a11y dev # from source: same, at /__devframes_plugin_a11
123123
| `src/rpc/` || `get-config` static RPC + the type-safe client registry |
124124
| `src/shared/protocol.ts` || the agent ↔ panel `BroadcastChannel` contract |
125125
| `src/inject/` || the host-page agent (axe scan, highlight overlay, hub messages mirror) → `dist/inject/inject.js` |
126-
| `src/spa/` || the Solid panel SPA → `dist/spa` |
126+
| `src/spa/` || the Solid panel SPA → `assets-pkg/dist` (ships in `@devframes/plugin-a11y--assets`) |
127127
| `demo/` || same-origin host page + server (dev + static modes) |
128128
| `tests/` || dev-server RPC + static-build dump |
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "@devframes/plugin-a11y--assets",
3+
"type": "module",
4+
"version": "0.9.0-beta.4",
5+
"description": "Prebuilt browser assets (SPA) for @devframes/plugin-a11y, served on demand through devframe's remote-assets back-proxy.",
6+
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
7+
"license": "MIT",
8+
"homepage": "https://github.com/devframes/devframe#readme",
9+
"repository": {
10+
"directory": "plugins/a11y/assets-pkg",
11+
"type": "git",
12+
"url": "git+https://github.com/devframes/devframe.git"
13+
},
14+
"bugs": "https://github.com/devframes/devframe/issues",
15+
"keywords": [
16+
"devframe",
17+
"devframe-plugin",
18+
"devtools",
19+
"client-assets"
20+
],
21+
"exports": {
22+
"./package.json": "./package.json"
23+
},
24+
"files": [
25+
"dist"
26+
],
27+
"scripts": {
28+
"prepack": "pnpm --filter @devframes/plugin-a11y run build"
29+
}
30+
}

plugins/a11y/demo/server.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*
1212
* Two modes prove the plugin works either way:
1313
*
14-
* node demo/server.mjs dev — live WebSocket RPC (`dist/spa`)
14+
* node demo/server.mjs dev — live WebSocket RPC (`assets-pkg/dist`)
1515
* node demo/server.mjs build static — baked RPC dump, (`dist/static`)
1616
*
1717
* The scan/highlight loop is identical in both: it rides the BroadcastChannel,
@@ -35,7 +35,7 @@ const ROOT = resolve(HERE, '..')
3535
const mode = process.argv[2] === 'build' ? 'build' : 'dev'
3636
const basePath = devframe.basePath
3737
const injectDir = resolve(ROOT, 'dist/inject')
38-
const panelDir = mode === 'build' ? resolve(ROOT, 'dist/static') : resolve(ROOT, 'dist/spa')
38+
const panelDir = mode === 'build' ? resolve(ROOT, 'dist/static') : resolve(ROOT, 'assets-pkg/dist')
3939

4040
function requireBuilt(file, hint) {
4141
if (!existsSync(file)) {

plugins/a11y/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,14 @@
5353
"test": "vitest run"
5454
},
5555
"peerDependencies": {
56+
"@devframes/plugin-a11y--assets": "workspace:*",
5657
"devframe": "workspace:*",
5758
"vite": "^8.0.0"
5859
},
5960
"peerDependenciesMeta": {
61+
"@devframes/plugin-a11y--assets": {
62+
"optional": true
63+
},
6064
"vite": {
6165
"optional": true
6266
}
@@ -67,6 +71,7 @@
6771
},
6872
"devDependencies": {
6973
"@antfu/design": "catalog:frontend",
74+
"@devframes/plugin-a11y--assets": "workspace:*",
7075
"axe-core": "catalog:frontend",
7176
"colorjs.io": "catalog:frontend",
7277
"devframe": "workspace:*",

plugins/a11y/src/index.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { DevframeDefinition } from 'devframe'
1+
import type { DevframeDefinition, RemoteAssets } from 'devframe'
22
import { fileURLToPath } from 'node:url'
33
import { defineDevframe } from 'devframe'
44
import pkg from '../package.json' with { type: 'json' }
@@ -8,10 +8,16 @@ import { setupA11y } from './node/index.ts'
88
const DEFAULT_ID = 'devframes_plugin_a11y'
99
const BASE_PATH = '/__devframes_plugin_a11y/'
1010

11-
// The Solid panel SPA is built (by Vite) into `dist/spa`. From both the
12-
// source entry (`src/index.ts`, via the workspace alias) and the published
13-
// entry (`dist/index.mjs`), `../dist/spa` resolves to `<pkg>/dist/spa`.
14-
const distDir = fileURLToPath(new URL('../dist/spa', import.meta.url))
11+
// The Solid panel SPA ships in the lockstep `@devframes/plugin-a11y--assets`
12+
// package, served on demand through devframe's remote-assets back-proxy;
13+
// `resolveFrom` serves a locally installed copy (a workspace link here) with
14+
// zero network. The host-page agent bundle (`dist/inject`, below) stays in
15+
// this node package.
16+
const distDir: RemoteAssets = {
17+
package: `${pkg.name}--assets`,
18+
version: pkg.version,
19+
resolveFrom: import.meta.url,
20+
}
1521

1622
/**
1723
* Absolute path to the built in-page **agent** module (`dist/inject/inject.js`)

plugins/a11y/src/spa/vite.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default defineConfig({
1414
resolve: { alias },
1515
plugins: [solid(), UnoCSS()],
1616
build: {
17-
outDir: fileURLToPath(new URL('../../dist/spa', import.meta.url)),
17+
outDir: fileURLToPath(new URL('../../assets-pkg/dist', import.meta.url)),
1818
emptyOutDir: true,
1919
},
2020
})

plugins/a11y/tests/_utils.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,32 @@
11
import type { StartedServer } from 'devframe/internal'
22
import { existsSync } from 'node:fs'
3+
import os from 'node:os'
34
import path from 'node:path'
45
import process from 'node:process'
5-
import { fileURLToPath } from 'node:url'
66
import { DEVFRAME_CONNECTION_META_FILENAME } from 'devframe/constants'
77
import { createH3DevframeHost } from 'devframe/internal'
88
import { createHostContext } from 'devframe/node'
9+
import { resolveStaticAssetsSource } from 'devframe/utils/remote-assets'
910
import { mountStaticHandler } from 'devframe/utils/serve-static'
1011
import { getPort } from 'get-port-please'
1112
import { H3 } from 'h3'
1213
import { resolve } from 'pathe'
1314
import { serveTestContext } from '../../../tests/helpers/serve-test-context'
1415
import devframe from '../src/index'
1516

16-
const HERE = fileURLToPath(new URL('.', import.meta.url))
17-
const SPA_DIST = resolve(HERE, '../dist/spa')
17+
/** Resolve the Solid panel SPA to a local dir — the workspace-linked `--assets` package in dev. */
18+
function localSpaDir(): string {
19+
const resolved = resolveStaticAssetsSource(devframe.cli!.distDir!, resolve(os.tmpdir(), 'devframes_plugin_a11y-test'))
20+
if (typeof resolved !== 'string')
21+
throw new TypeError('[devframes_plugin_a11y] client SPA missing — run `pnpm -C plugins/a11y run build` first.')
22+
return resolved
23+
}
1824

19-
/** Loud failure if the Solid panel hasn't been built — tests serve `dist/spa`. */
25+
/** Loud failure if the Solid panel hasn't been built — tests serve the client SPA. */
2026
export function assertClientBuilt(): void {
21-
if (!existsSync(path.join(SPA_DIST, 'index.html'))) {
27+
if (!existsSync(path.join(localSpaDir(), 'index.html'))) {
2228
throw new Error(
23-
'[devframes_plugin_a11y] dist/spa missing — run `pnpm -C plugins/a11y run build` first.',
29+
'[devframes_plugin_a11y] client SPA missing — run `pnpm -C plugins/a11y run build` first.',
2430
)
2531
}
2632
}
@@ -36,9 +42,7 @@ export interface InspectorServer extends StartedServer {
3642
* ws transport tests.
3743
*/
3844
export async function startInspectorServer(): Promise<InspectorServer> {
39-
const distDir = devframe.cli!.distDir!
40-
if (typeof distDir !== 'string')
41-
throw new TypeError('these tests serve the local dist directory — build the SPA first')
45+
const distDir = localSpaDir()
4246
const basePath = devframe.basePath!
4347
const host = '127.0.0.1'
4448
const port = await getPort({ host, random: true })
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "@devframes/plugin-assets--assets",
3+
"type": "module",
4+
"version": "0.9.0-beta.4",
5+
"description": "Prebuilt browser assets (SPA) for @devframes/plugin-assets, served on demand through devframe's remote-assets back-proxy.",
6+
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
7+
"license": "MIT",
8+
"homepage": "https://github.com/devframes/devframe#readme",
9+
"repository": {
10+
"directory": "plugins/assets/assets-pkg",
11+
"type": "git",
12+
"url": "git+https://github.com/devframes/devframe.git"
13+
},
14+
"bugs": "https://github.com/devframes/devframe/issues",
15+
"keywords": [
16+
"devframe",
17+
"devframe-plugin",
18+
"devtools",
19+
"client-assets"
20+
],
21+
"exports": {
22+
"./package.json": "./package.json"
23+
},
24+
"files": [
25+
"dist"
26+
],
27+
"scripts": {
28+
"prepack": "pnpm --filter @devframes/plugin-assets run build"
29+
}
30+
}

plugins/assets/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,14 @@
4848
"typecheck": "tsc --noEmit"
4949
},
5050
"peerDependencies": {
51+
"@devframes/plugin-assets--assets": "workspace:*",
5152
"devframe": "workspace:*",
5253
"vite": "^7.0.0 || ^8.0.0"
5354
},
5455
"peerDependenciesMeta": {
56+
"@devframes/plugin-assets--assets": {
57+
"optional": true
58+
},
5559
"vite": {
5660
"optional": true
5761
}
@@ -69,6 +73,7 @@
6973
},
7074
"devDependencies": {
7175
"@antfu/design": "catalog:frontend",
76+
"@devframes/plugin-assets--assets": "workspace:*",
7277
"@iconify-json/ph": "catalog:frontend",
7378
"@storybook/addon-a11y": "catalog:storybook",
7479
"@storybook/addon-docs": "catalog:storybook",

0 commit comments

Comments
 (0)