|
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 | +} |
4 | 89 |
|
5 | 90 | /** |
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. |
10 | 99 | */ |
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 | + }) |
14 | 132 | } |
15 | 133 |
|
16 | | -export { serverFunctions } |
| 134 | +export default createA11yDevframe |
| 135 | +export type { Impact, ScanReport, Violation, ViolationNode } from '../shared/protocol.ts' |
0 commit comments