Skip to content

Commit e6d7c24

Browse files
committed
fix(a11y): resolve the client-script bundle via the package export
tsdown hoists the module computing a11yClientScriptBundlePath into a shared chunk at the dist root, where the import.meta.url-relative path escapes the package (plugins/dist/...), so a hub consuming the built package mounted no page script. Resolving the package's own ./client-script export is depth-independent; the relative path stays as the unbuilt-bundle fallback.
1 parent 57070d1 commit e6d7c24

1 file changed

Lines changed: 130 additions & 11 deletions

File tree

‎plugins/a11y/src/node/index.ts‎

Lines changed: 130 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,135 @@
1-
import type { DevframeNodeContext } from 'devframe'
2-
import type { A11yRuntimeConfig } from '../rpc/functions/get-config.ts'
3-
import { buildServerFunctions, serverFunctions } from '../rpc/index.ts'
1+
import type { DevframeDefinition, RemoteAssets } from 'devframe'
2+
import { createRequire } from 'node:module'
3+
import { fileURLToPath } from 'node:url'
4+
import { defineDevframe } from 'devframe'
5+
import pkg from '../../package.json' with { type: 'json' }
6+
import { setupA11y } from './setup.ts'
7+
8+
/** Default devframe id, driving the standalone CLI command and the hosted mount path `/__<id>/`. */
9+
const DEFAULT_ID = 'devframes_plugin_a11y'
10+
const BASE_PATH = '/__devframes_plugin_a11y/'
11+
12+
// The Solid panel SPA ships in the lockstep `@devframes/plugin-a11y--assets`
13+
// package, served on demand through devframe's remote-assets back-proxy. The
14+
// definition's `importMetaUrl` (below) supplies the default `resolveFrom`, so a
15+
// locally installed copy (a workspace link here) is served with zero network.
16+
// The client-script bundle (`dist/client-script`, below) stays here.
17+
const distDir: RemoteAssets = {
18+
package: `${pkg.name}--assets`,
19+
version: pkg.version,
20+
}
21+
22+
/**
23+
* Absolute path to the built **client script** module (`dist/client-script/index.mjs`),
24+
* the dock client script the client runtime imports into the host page to scan
25+
* it (its default export boots the script; importing it does too).
26+
*
27+
* The definition already declares this as its dock `clientScript`, so a hub
28+
* serves it with no host wiring. Exported for hosts that mount the module
29+
* themselves (e.g. via `/@fs/` under Vite). Requires the built bundle
30+
* (`pnpm -C plugins/a11y build`).
31+
*
32+
* Resolved through the package's own `./client-script` export, since a
33+
* bundler may hoist this module into a shared chunk whose on-disk depth
34+
* differs from the entry's, breaking an `import.meta.url`-relative path.
35+
* The relative fallback covers an unbuilt bundle (resolution then fails),
36+
* where downstream `existsSync` checks degrade gracefully.
37+
*/
38+
export const a11yClientScriptBundlePath: string = resolveClientScriptBundle()
39+
40+
function resolveClientScriptBundle(): string {
41+
try {
42+
return createRequire(import.meta.url).resolve(`${pkg.name}/client-script`)
43+
}
44+
catch {
45+
return fileURLToPath(new URL('../../dist/client-script/index.mjs', import.meta.url))
46+
}
47+
}
48+
49+
/** @deprecated Renamed; use {@link a11yClientScriptBundlePath}. */
50+
export const a11yPageScriptBundlePath: string = a11yClientScriptBundlePath
51+
52+
/** @deprecated Renamed; use {@link a11yClientScriptBundlePath}. */
53+
export const a11yAgentBundlePath: string = a11yClientScriptBundlePath
54+
55+
export interface A11yDevframeOptions {
56+
/** Override the devframe id (and the default CLI command / mount path). */
57+
id?: string
58+
/** Override the display name shown in a host dock. */
59+
name?: string
60+
/** Override the dock icon. */
61+
icon?: string
62+
/**
63+
* Override the mount path. Defaults to `/__devframes_plugin_a11y/` so the
64+
* panel iframe shares an origin with the host page it scans.
65+
*/
66+
basePath?: string
67+
/** Preferred standalone CLI port. */
68+
port?: number
69+
/**
70+
* Rescan on debounced user interaction (mouse/keyboard/touch), on top of the
71+
* DOM MutationObserver. Default `true`.
72+
*/
73+
autoScan?: boolean
74+
/** Log newly-appeared violations to the browser console. Default `true`. */
75+
logIssues?: boolean
76+
/**
77+
* Auto-pin all of a route's violations the first time it's scanned.
78+
* Default `false`.
79+
*/
80+
defaultHighlight?: boolean
81+
/** axe-core configuration. */
82+
axe?: {
83+
/** Rule tags to run (defaults to the broadened WCAG 2.0–2.2 + best-practice set). */
84+
tags?: string[]
85+
/** Extra axe `run` options merged over the defaults. */
86+
runOptions?: Record<string, unknown>
87+
}
88+
}
489

590
/**
6-
* Register the a11y inspector's RPC functions on a devframe node context.
7-
* Called from the definition's `setup(ctx)` and reusable by host adapters
8-
* that wire their own context. Author options (auto-scan, logging, axe tags,
9-
* default-highlight) are surfaced through the `get-config` function.
91+
* Build a {@link DevframeDefinition} for the a11y inspector. The same
92+
* definition runs standalone (`/cli`, `/build`) and mounts into a host
93+
* (`/vite`, hub). The panel talks to the page script over the in-page channel
94+
* (`devframe/in-page-channel`), so the scan/highlight loop works identically in dev
95+
* (live WebSocket RPC) and in a baked static build.
96+
*
97+
* @experimental This plugin is experimental and may change without a major
98+
* version bump until it stabilizes.
1099
*/
11-
export function setupA11y(ctx: DevframeNodeContext, options: A11yRuntimeConfig = {}): void {
12-
for (const fn of buildServerFunctions(options))
13-
ctx.rpc.register(fn)
100+
export function createA11yDevframe(options: A11yDevframeOptions = {}): DevframeDefinition {
101+
const id = options.id ?? DEFAULT_ID
102+
return defineDevframe({
103+
id,
104+
name: options.name ?? 'A11y Inspector',
105+
version: pkg.version,
106+
packageName: pkg.name,
107+
importMetaUrl: import.meta.url,
108+
homepage: pkg.homepage,
109+
description: pkg.description,
110+
icon: options.icon ?? 'ph:person-simple-circle-duotone',
111+
basePath: options.basePath ?? BASE_PATH,
112+
/** Declare the page script by path; the hub serves it with no host wiring. */
113+
dock: {
114+
category: '~builtin',
115+
clientScript: { importFrom: a11yClientScriptBundlePath },
116+
},
117+
cli: {
118+
command: id,
119+
port: options.port ?? 9899,
120+
distDir,
121+
},
122+
setup(ctx) {
123+
setupA11y(ctx, {
124+
dockId: id,
125+
autoScan: options.autoScan,
126+
logIssues: options.logIssues,
127+
defaultHighlight: options.defaultHighlight,
128+
axe: options.axe,
129+
})
130+
},
131+
})
14132
}
15133

16-
export { serverFunctions }
134+
export default createA11yDevframe
135+
export type { Impact, ScanReport, Violation, ViolationNode } from '../shared/protocol.ts'

0 commit comments

Comments
 (0)