Skip to content

Commit aa3d2e1

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 63a4ee9 commit aa3d2e1

42 files changed

Lines changed: 517 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
@@ -183,6 +183,42 @@
183183
"entry": [],
184184
"project": []
185185
},
186+
// Each plugin's lockstep `--assets` package is referenced only as a
187+
// runtime string (`${pkg.name}--assets` in `cli.distDir`), never imported,
188+
// so knip can't see the dev-only workspace link. A workspace config
189+
// replaces (not merges) the `plugins/*` glob's `entry`, so repeat it.
190+
"plugins/og": {
191+
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
192+
"ignoreDependencies": ["@devframes/plugin-og--assets"]
193+
},
194+
"plugins/og/assets-pkg": { "entry": [], "project": [] },
195+
"plugins/data-inspector": {
196+
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
197+
"ignoreDependencies": ["@devframes/plugin-data-inspector--assets"]
198+
},
199+
"plugins/data-inspector/assets-pkg": { "entry": [], "project": [] },
200+
"plugins/code-server": {
201+
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
202+
"ignoreDependencies": ["@devframes/plugin-code-server--assets"]
203+
},
204+
"plugins/code-server/assets-pkg": { "entry": [], "project": [] },
205+
"plugins/assets": {
206+
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
207+
"ignoreDependencies": ["@devframes/plugin-assets--assets"]
208+
},
209+
"plugins/assets/assets-pkg": { "entry": [], "project": [] },
210+
"plugins/messages": {
211+
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
212+
"ignoreDependencies": ["@devframes/plugin-messages--assets"]
213+
},
214+
"plugins/messages/assets-pkg": { "entry": [], "project": [] },
215+
"plugins/terminals": {
216+
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
217+
"ignoreDependencies": ["@devframes/plugin-terminals--assets"]
218+
},
219+
"plugins/terminals/assets-pkg": { "entry": [], "project": [] },
220+
"plugins/a11y/assets-pkg": { "entry": [], "project": [] },
221+
"plugins/git/assets-pkg": { "entry": [], "project": [] },
186222
"plugins/a11y": {
187223
// `storybook-solidjs-vite` (not an official `@storybook/*` framework
188224
// package) doesn't match knip's Storybook plugin trigger, so it never
@@ -199,15 +235,17 @@
199235
"src/{index,cli,vite,constants,types}.ts",
200236
"src/{client,node,rpc,inject,engine,registry}/index.ts",
201237
"src/**/*.stories.tsx"
202-
]
238+
],
239+
"ignoreDependencies": ["@devframes/plugin-a11y--assets"]
203240
},
204241
"plugins/git": {
205242
// The React client build's root sits at `src/client` (`next dev
206243
// src/client`), not the workspace root, so knip's default PostCSS
207244
// config glob (workspace-root `postcss.config.*`) misses it.
208245
"postcss": {
209246
"config": ["src/client/postcss.config.mjs"]
210-
}
247+
},
248+
"ignoreDependencies": ["@devframes/plugin-git--assets"]
211249
}
212250
}
213251
}

plugins/a11y/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,6 @@ pnpm -C plugins/a11y dev # from source: same, at /__devframes_plugin_a11
122122
| `src/rpc/` || `get-config` static RPC + the type-safe client registry |
123123
| `src/shared/protocol.ts` || the agent ↔ panel `BroadcastChannel` contract |
124124
| `src/inject/` || the host-page agent (axe scan, highlight overlay, hub messages mirror) → `dist/inject/inject.js` |
125-
| `src/spa/` || the Solid panel SPA → `dist/spa` |
125+
| `src/spa/` || the Solid panel SPA → `assets-pkg/dist` (ships in `@devframes/plugin-a11y--assets`) |
126126
| `demo/` || same-origin host page + server (dev + static modes) |
127127
| `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.8",
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": "turbo run build --filter=@devframes/plugin-a11y"
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,
@@ -36,7 +36,7 @@ const devframe = createA11yDevframe()
3636
const mode = process.argv[2] === 'build' ? 'build' : 'dev'
3737
const basePath = devframe.basePath
3838
const injectDir = resolve(ROOT, 'dist/inject')
39-
const panelDir = mode === 'build' ? resolve(ROOT, 'dist/static') : resolve(ROOT, 'dist/spa')
39+
const panelDir = mode === 'build' ? resolve(ROOT, 'dist/static') : resolve(ROOT, 'assets-pkg/dist')
4040

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

plugins/a11y/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,14 @@
5252
"test": "vitest run"
5353
},
5454
"peerDependencies": {
55+
"@devframes/plugin-a11y--assets": "workspace:*",
5556
"devframe": "workspace:*",
5657
"vite": "^8.0.0"
5758
},
5859
"peerDependenciesMeta": {
60+
"@devframes/plugin-a11y--assets": {
61+
"optional": true
62+
},
5963
"vite": {
6064
"optional": true
6165
}
@@ -65,6 +69,7 @@
6569
},
6670
"devDependencies": {
6771
"@antfu/design": "catalog:frontend",
72+
"@devframes/plugin-a11y--assets": "workspace:*",
6873
"axe-core": "catalog:frontend",
6974
"colorjs.io": "catalog:frontend",
7075
"devframe": "workspace:*",

plugins/a11y/src/index.ts

Lines changed: 10 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,15 @@ 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 here.
15+
const distDir: RemoteAssets = {
16+
package: `${pkg.name}--assets`,
17+
version: pkg.version,
18+
resolveFrom: import.meta.url,
19+
}
1520

1621
/**
1722
* 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: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
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 createA11yDevframe from '../src/index'
1516

16-
const HERE = fileURLToPath(new URL('.', import.meta.url))
17-
const SPA_DIST = resolve(HERE, '../dist/spa')
1817
const devframe = createA11yDevframe()
1918

20-
/** Loud failure if the Solid panel hasn't been built — tests serve `dist/spa`. */
19+
/** Resolve the Solid panel SPA to a local dir — the workspace-linked `--assets` package in dev. */
20+
function localSpaDir(): string {
21+
const resolved = resolveStaticAssetsSource(devframe.cli!.distDir!, resolve(os.tmpdir(), 'devframes_plugin_a11y-test'))
22+
if (typeof resolved !== 'string')
23+
throw new TypeError('[devframes_plugin_a11y] client SPA missing — run `pnpm -C plugins/a11y run build` first.')
24+
return resolved
25+
}
26+
27+
/** Loud failure if the Solid panel hasn't been built — tests serve the client SPA. */
2128
export function assertClientBuilt(): void {
22-
if (!existsSync(path.join(SPA_DIST, 'index.html'))) {
29+
if (!existsSync(path.join(localSpaDir(), 'index.html'))) {
2330
throw new Error(
24-
'[devframes_plugin_a11y] dist/spa missing — run `pnpm -C plugins/a11y run build` first.',
31+
'[devframes_plugin_a11y] client SPA missing — run `pnpm -C plugins/a11y run build` first.',
2532
)
2633
}
2734
}
@@ -37,9 +44,7 @@ export interface InspectorServer extends StartedServer {
3744
* ws transport tests.
3845
*/
3946
export async function startInspectorServer(): Promise<InspectorServer> {
40-
const distDir = devframe.cli!.distDir!
41-
if (typeof distDir !== 'string')
42-
throw new TypeError('these tests serve the local dist directory — build the SPA first')
47+
const distDir = localSpaDir()
4348
const basePath = devframe.basePath!
4449
const host = '127.0.0.1'
4550
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.8",
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": "turbo run build --filter=@devframes/plugin-assets"
29+
}
30+
}

plugins/assets/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,14 @@
4747
"typecheck": "tsc --noEmit"
4848
},
4949
"peerDependencies": {
50+
"@devframes/plugin-assets--assets": "workspace:*",
5051
"devframe": "workspace:*",
5152
"vite": "^7.0.0 || ^8.0.0"
5253
},
5354
"peerDependenciesMeta": {
55+
"@devframes/plugin-assets--assets": {
56+
"optional": true
57+
},
5458
"vite": {
5559
"optional": true
5660
}
@@ -66,6 +70,7 @@
6670
},
6771
"devDependencies": {
6872
"@antfu/design": "catalog:frontend",
73+
"@devframes/plugin-assets--assets": "workspace:*",
6974
"@devframes/vite": "workspace:*",
7075
"@iconify-json/ph": "catalog:frontend",
7176
"@storybook/addon-a11y": "catalog:storybook",

0 commit comments

Comments
 (0)