Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ export const alias = {
'@devframes/plugin-inspect/cli': p('inspect/src/cli.ts'),
'@devframes/plugin-inspect/vite': p('inspect/src/vite.ts'),
'@devframes/plugin-inspect': p('inspect/src/index.ts'),
'@devframes/a11y': p('a11y/src/devframe.ts'),
'@devframes/plugin-a11y/client': p('a11y/src/client/index.ts'),
'@devframes/plugin-a11y/node': p('a11y/src/node/index.ts'),
'@devframes/plugin-a11y/cli': p('a11y/src/cli.ts'),
'@devframes/plugin-a11y/vite': p('a11y/src/vite.ts'),
'@devframes/plugin-a11y': p('a11y/src/index.ts'),
}

// update tsconfig.base.json — CSS aliases exist for Vite resolution only;
Expand Down
2 changes: 1 addition & 1 deletion examples/minimal-next-devframe-hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"test": "vitest run --config vitest.config.ts"
},
"dependencies": {
"@devframes/a11y": "workspace:*",
"@devframes/hub": "workspace:*",
"@devframes/plugin-a11y": "workspace:*",
"@devframes/plugin-code-server": "workspace:*",
"@devframes/plugin-git": "workspace:*",
"@devframes/plugin-inspect": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const BUILTIN_PLUGIN_PACKAGES = [
'@devframes/plugin-terminals',
'@devframes/plugin-code-server',
'@devframes/plugin-inspect',
'@devframes/a11y',
'@devframes/plugin-a11y',
] as const

async function loadBuiltinPlugins(): Promise<DevframeDefinition[]> {
Expand Down
2 changes: 1 addition & 1 deletion examples/minimal-vite-devframe-hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"build": "vite build"
},
"dependencies": {
"@devframes/a11y": "workspace:*",
"@devframes/hub": "workspace:*",
"@devframes/plugin-a11y": "workspace:*",
"@devframes/plugin-code-server": "workspace:*",
"@devframes/plugin-git": "workspace:*",
"@devframes/plugin-inspect": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion examples/minimal-vite-devframe-hub/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import a11yDevframe from '@devframes/a11y'
import a11yDevframe from '@devframes/plugin-a11y'
import codeServerDevframe from '@devframes/plugin-code-server'
import gitDevframe from '@devframes/plugin-git'
import inspectDevframe from '@devframes/plugin-inspect'
Expand Down
28 changes: 16 additions & 12 deletions plugins/a11y/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# devframe-a11y-inspector
# @devframes/plugin-a11y

An accessibility inspector built on [devframe](../../packages/devframe). It runs
[axe-core](https://github.com/dequelabs/axe-core) against a host application,
Expand All @@ -15,8 +15,8 @@ Three pieces, two of them browser-side:
| Piece | Runs in | Role |
|-------|---------|------|
| **Agent** (`src/inject`) | the host app's page | runs axe-core, broadcasts the report, draws the highlight ring |
| **Panel** (`src/client`) | the devtools iframe | Solid SPA: lists violations, fires highlight/clear on hover |
| **Node** (`src/devframe.ts`, `src/rpc`) | the devframe backend | `get-config` RPC (impact taxonomy) — live in dev, baked in a static build |
| **Panel** (`src/spa`) | the devtools iframe | Solid SPA: lists violations, fires highlight/clear on hover |
| **Node** (`src/index.ts`, `src/node`, `src/rpc`) | the devframe backend | `get-config` RPC (impact taxonomy) — live in dev, baked in a static build |

The agent and panel talk over a same-origin
[`BroadcastChannel`](src/shared/protocol.ts), not the devframe RPC backend. That
Expand Down Expand Up @@ -55,12 +55,16 @@ pnpm -C plugins/a11y dev # panel only, at /__devframe-a11y-inspector/

## File map

| Path | Purpose |
|------|---------|
| `src/devframe.ts` | the `DevframeDefinition` consumed by every adapter |
| `src/rpc/` | `get-config` static RPC + the type-safe client registry |
| `src/shared/protocol.ts` | the agent ↔ panel `BroadcastChannel` contract |
| `src/inject/` | the host-page agent (axe scan, highlight overlay) → `dist/inject/inject.js` |
| `src/client/` | the Solid panel SPA → `dist/client` |
| `demo/` | same-origin host page + server (dev + static modes) |
| `tests/` | dev-server RPC + static-build dump |
| Path | Export | Purpose |
|------|--------|---------|
| `src/index.ts` | `.` | `createA11yDevframe()` + the default `DevframeDefinition` |
| `src/node/index.ts` | `/node` | `setupA11y(ctx)` — registers the RPC functions |
| `src/cli.ts` | `/cli` | `createA11yCli()` — backs the `devframe-a11y-inspector` bin |
| `src/vite.ts` | `/vite` | `a11yVitePlugin()` — mounts the panel into a Vite host |
| `src/client/index.ts` | `/client` | `connectA11y()` — typed browser RPC client wrapper |
| `src/rpc/` | — | `get-config` static RPC + the type-safe client registry |
| `src/shared/protocol.ts` | — | the agent ↔ panel `BroadcastChannel` contract |
| `src/inject/` | — | the host-page agent (axe scan, highlight overlay) → `dist/inject/inject.js` |
| `src/spa/` | — | the Solid panel SPA → `dist/spa` |
| `demo/` | — | same-origin host page + server (dev + static modes) |
| `tests/` | — | dev-server RPC + static-build dump |
5 changes: 2 additions & 3 deletions plugins/a11y/bin.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
#!/usr/bin/env node
import process from 'node:process'
import { createCli } from 'devframe/adapters/cli'
import devframe from './src/devframe.ts'
import { createA11yCli } from './dist/cli.mjs'

async function main() {
const cli = createCli(devframe)
const cli = createA11yCli()
await cli.parse()
}

Expand Down
6 changes: 3 additions & 3 deletions plugins/a11y/demo/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*
* Two modes prove the plugin works either way:
*
* node demo/server.mjs dev — live WebSocket RPC (`dist/client`)
* node demo/server.mjs dev — live WebSocket RPC (`dist/spa`)
* node demo/server.mjs build static — baked RPC dump, (`dist/static`)
*
* The scan/highlight loop is identical in both: it rides the BroadcastChannel,
Expand All @@ -28,15 +28,15 @@ import { mountStaticHandler } from 'devframe/utils/serve-static'
import { getPort } from 'get-port-please'
import { H3, toNodeHandler } from 'h3'
import { resolve } from 'pathe'
import devframe from '../src/devframe.ts'
import devframe from '../src/index.ts'

const HERE = fileURLToPath(new URL('.', import.meta.url))
const ROOT = resolve(HERE, '..')

const mode = process.argv[2] === 'build' ? 'build' : 'dev'
const basePath = devframe.basePath
const injectDir = resolve(ROOT, 'dist/inject')
const panelDir = mode === 'build' ? resolve(ROOT, 'dist/static') : resolve(ROOT, 'dist/client')
const panelDir = mode === 'build' ? resolve(ROOT, 'dist/static') : resolve(ROOT, 'dist/spa')

function requireBuilt(file, hint) {
if (!existsSync(file)) {
Expand Down
56 changes: 49 additions & 7 deletions plugins/a11y/package.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,74 @@
{
"name": "@devframes/a11y",
"name": "@devframes/plugin-a11y",
"type": "module",
"version": "0.5.2",
"private": true,
"description": "Devframe a11y inspector — runs axe-core against the host app, surfaces the violations through a Solid SPA, and highlights the offending element in the page on hover. Works in dev (WebSocket) and static build modes.",
"main": "src/devframe.ts",
"description": "Devframe a11y inspector — runs axe-core against the host app, surfaces the violations through a Solid panel, and highlights the offending element in the page on hover. Works in dev (WebSocket) and static build modes.",
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
"license": "MIT",
"homepage": "https://github.com/devframes/devframe#readme",
"repository": {
"directory": "plugins/a11y",
"type": "git",
"url": "git+https://github.com/devframes/devframe.git"
},
"bugs": "https://github.com/devframes/devframe/issues",
"keywords": [
"devframe",
"devframe-plugin",
"devtools",
"a11y",
"accessibility",
"axe-core"
],
"sideEffects": false,
"exports": {
".": "./dist/index.mjs",
"./client": "./dist/client/index.mjs",
"./cli": "./dist/cli.mjs",
"./node": "./dist/node/index.mjs",
"./vite": "./dist/vite.mjs",
"./package.json": "./package.json"
},
"types": "./dist/index.d.mts",
"bin": {
"devframe-a11y-inspector": "./bin.mjs"
},
"files": [
"bin.mjs",
"dist"
],
"scripts": {
"build": "vite build --config src/client/vite.config.ts && vite build --config src/inject/vite.config.ts",
"build:client": "vite build --config src/client/vite.config.ts",
"build": "tsdown && vite build --config src/spa/vite.config.ts && vite build --config src/inject/vite.config.ts",
"build:spa": "vite build --config src/spa/vite.config.ts",
"build:inject": "vite build --config src/inject/vite.config.ts",
"watch": "tsdown --watch",
"dev": "node bin.mjs",
"cli:build": "node bin.mjs build --out-dir dist/static",
"demo": "node demo/server.mjs",
"demo:build": "node demo/server.mjs build",
"typecheck": "tsc --noEmit",
"prepack": "pnpm run build",
"test": "vitest run"
},
"peerDependencies": {
"devframe": "workspace:*",
"vite": "^8.0.0"
},
"peerDependenciesMeta": {
"vite": {
"optional": true
}
},
"dependencies": {
"axe-core": "catalog:frontend",
"devframe": "workspace:*",
"solid-js": "catalog:frontend"
},
"devDependencies": {
"@internal/design": "workspace:*",
"devframe": "workspace:*",
"get-port-please": "catalog:deps",
"h3": "catalog:deps",
"tsdown": "catalog:build",
"unocss": "catalog:frontend",
"vite": "catalog:build",
"vite-plugin-solid": "catalog:build",
Expand Down
13 changes: 13 additions & 0 deletions plugins/a11y/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { CliHandle } from 'devframe/adapters/cli'
import { createCli } from 'devframe/adapters/cli'
import a11yDevframe from './index.ts'

/**
* Build the standalone CLI for the a11y inspector — backs the package `bin`
* (`devframe-a11y-inspector`) and `npx @devframes/plugin-a11y`. Wraps the
* default {@link createA11yDevframe} definition with devframe's
* `dev` / `build` / `spa` command shell.
*/
export function createA11yCli(): CliHandle {
return createCli(a11yDevframe)
}
16 changes: 16 additions & 0 deletions plugins/a11y/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { DevframeRpcClient, DevframeRpcClientOptions } from 'devframe/client'
import { connectDevframe } from 'devframe/client'

export type { DevframeRpcClient }
export type { Impact, ScanReport, Violation, ViolationNode } from '../shared/protocol.ts'

/**
* Connect to the a11y inspector's devframe backend. A thin, typed wrapper
* around devframe's {@link connectDevframe}; the panel derives its base from
* `document.baseURI`, so no options are required in the common case. The
* live scan/highlight loop itself rides a same-origin BroadcastChannel,
* independent of this connection.
*/
export function connectA11y(options?: DevframeRpcClientOptions): Promise<DevframeRpcClient> {
return connectDevframe(options)
}
23 changes: 0 additions & 23 deletions plugins/a11y/src/devframe.ts

This file was deleted.

66 changes: 66 additions & 0 deletions plugins/a11y/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { DevframeDefinition } from 'devframe/types'
import { fileURLToPath } from 'node:url'
import { defineDevframe } from 'devframe/types'
import pkg from '../package.json' with { type: 'json' }
import { setupA11y } from './node/index.ts'

/** Default devframe id — drives the standalone CLI command and the hosted mount path `/__<id>/`. */
const DEFAULT_ID = 'devframe-a11y-inspector'
const BASE_PATH = '/__devframe-a11y-inspector/'

// The Solid panel SPA is built (by Vite) into `dist/spa`. From both the
// source entry (`src/index.ts`, via the workspace alias) and the published
// entry (`dist/index.mjs`), `../dist/spa` resolves to `<pkg>/dist/spa`.
const distDir = fileURLToPath(new URL('../dist/spa', import.meta.url))

export interface A11yDevframeOptions {
/** Override the devframe id (and the default CLI command / mount path). */
id?: string
/** Override the display name shown in a host dock. */
name?: string
/** Override the dock icon. */
icon?: string
/**
* Override the mount path. Defaults to `/__devframe-a11y-inspector/` so the
* panel iframe shares an origin with the host page it scans.
*/
basePath?: string
/** Preferred standalone CLI port. */
port?: number
}

/**
* Build a {@link DevframeDefinition} for the a11y inspector. The same
* definition runs standalone (`/cli`, `/build`) and mounts into a host
* (`/vite`, hub). The panel talks to the in-page agent over a same-origin
* BroadcastChannel, so the scan/highlight loop works identically in dev
* (live WebSocket RPC) and in a baked static build.
*/
export function createA11yDevframe(options: A11yDevframeOptions = {}): DevframeDefinition {
const id = options.id ?? DEFAULT_ID
return defineDevframe({
id,
name: options.name ?? 'A11y Inspector',
version: pkg.version,
packageName: pkg.name,
homepage: pkg.homepage,
description: pkg.description,
icon: options.icon ?? 'ph:wheelchair-duotone',
basePath: options.basePath ?? BASE_PATH,
cli: {
command: id,
port: options.port ?? 9899,
distDir,
},
spa: { loader: 'none' },
setup(ctx) {
setupA11y(ctx)
},
})
}

/** The default a11y inspector devframe definition. */
const a11yDevframe: DevframeDefinition = createA11yDevframe()

export default a11yDevframe
export type { Impact, ScanReport, Violation, ViolationNode } from './shared/protocol.ts'
14 changes: 14 additions & 0 deletions plugins/a11y/src/node/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { DevframeNodeContext } from 'devframe/types'
import { serverFunctions } from '../rpc/index.ts'

/**
* Register the a11y inspector's RPC functions on a devframe node context.
* Called from the definition's `setup(ctx)` and reusable by host adapters
* that wire their own context.
*/
export function setupA11y(ctx: DevframeNodeContext): void {
for (const fn of serverFunctions)
ctx.rpc.register(fn)
}

export { serverFunctions }
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import solid from 'vite-plugin-solid'
import { alias } from '../../../../alias'

// `base: './'` + `<base href="./" />` keeps the bundle mount-path portable:
// the same `dist/client` works whether devframe serves it at `/` (standalone)
// the same `dist/spa` works whether devframe serves it at `/` (standalone)
// or `/__devframe-a11y-inspector/` (mounted in a hub). `connectDevframe`
// resolves its connection meta relative to `document.baseURI` to match.
export default defineConfig({
Expand All @@ -14,7 +14,7 @@ export default defineConfig({
resolve: { alias },
plugins: [solid(), UnoCSS()],
build: {
outDir: fileURLToPath(new URL('../../dist/client', import.meta.url)),
outDir: fileURLToPath(new URL('../../dist/spa', import.meta.url)),
emptyOutDir: true,
},
})
16 changes: 16 additions & 0 deletions plugins/a11y/src/vite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { DevframeVitePlugin, ViteDevBridgeOptions } from 'devframe/helpers/vite'
import { viteDevBridge } from 'devframe/helpers/vite'
import a11yDevframe from './index.ts'

export type { ViteDevBridgeOptions }

/**
* Mount the a11y inspector panel into an existing Vite dev server. In the
* default static-mount mode it serves the built panel at
* `/__devframe-a11y-inspector/`; pass `{ devMiddleware: true }` for the
* bridge mode where the host owns the SPA and devframe runs a side-car
* RPC + WS server.
*/
export function a11yVitePlugin(options?: ViteDevBridgeOptions): DevframeVitePlugin {
return viteDevBridge(a11yDevframe, options)
}
Loading
Loading