Skip to content

Commit 9483bf8

Browse files
committed
feat: fix build
1 parent fd2b44d commit 9483bf8

13 files changed

Lines changed: 160 additions & 45 deletions

File tree

examples/plugin-a11y-checker/src/client/run-axe.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { DevToolsLogLevel } from '@vitejs/devtools-kit'
2-
import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client'
2+
import type { DockClientScriptContext } from '@vitejs/devtools-kit/client'
33
import axe from 'axe-core'
44

55
const SOURCE = 'a11y-checker'
6+
const SUMMARY_LOG_ID = 'a11y-checker-summary'
67

78
function impactToLevel(impact: string | undefined | null): DevToolsLogLevel {
89
switch (impact) {
@@ -17,8 +18,18 @@ function impactToLevel(impact: string | undefined | null): DevToolsLogLevel {
1718
}
1819
}
1920

20-
async function runA11yCheck(): Promise<void> {
21-
const rpc = await getDevToolsRpcClient()
21+
export default async function runA11yCheck(context: DockClientScriptContext): Promise<void> {
22+
const { rpc } = context
23+
24+
// Show loading state
25+
await rpc.call('devtoolskit:internal:logs:add', {
26+
id: SUMMARY_LOG_ID,
27+
message: 'Running accessibility audit...',
28+
level: 'info' as DevToolsLogLevel,
29+
category: 'a11y',
30+
status: 'loading',
31+
notify: true,
32+
}, SOURCE)
2233

2334
try {
2435
const results = await axe.run(document)
@@ -28,6 +39,7 @@ async function runA11yCheck(): Promise<void> {
2839
const firstNode = violation.nodes[0]
2940

3041
await rpc.call('devtoolskit:internal:logs:add', {
42+
id: `a11y-violation-${violation.id}`,
3143
message: violation.description,
3244
level,
3345
description: `${violation.help}\n\n${violation.helpUrl}`,
@@ -45,31 +57,29 @@ async function runA11yCheck(): Promise<void> {
4557
const violationCount = results.violations.length
4658
const passCount = results.passes.length
4759

60+
// Update the summary log (dedup by id)
4861
await rpc.call('devtoolskit:internal:logs:add', {
62+
id: SUMMARY_LOG_ID,
4963
message: violationCount > 0
5064
? `Found ${violationCount} violation${violationCount > 1 ? 's' : ''}, ${passCount} passed`
5165
: `All ${passCount} checks passed`,
5266
level: (violationCount > 0 ? 'warn' : 'success') as DevToolsLogLevel,
5367
category: 'a11y',
68+
status: 'idle',
5469
notify: true,
5570
autoDismiss: 4000,
5671
}, SOURCE)
5772
}
5873
catch (err) {
74+
// Update the summary log with error
5975
await rpc.call('devtoolskit:internal:logs:add', {
76+
id: SUMMARY_LOG_ID,
6077
message: 'A11y audit failed',
6178
level: 'error' as DevToolsLogLevel,
6279
description: String(err),
6380
category: 'a11y',
81+
status: 'idle',
6482
notify: true,
6583
}, SOURCE)
6684
}
6785
}
68-
69-
// Auto-execute after page load
70-
if (document.readyState === 'complete') {
71-
runA11yCheck()
72-
}
73-
else {
74-
window.addEventListener('load', () => runA11yCheck())
75-
}

examples/plugin-a11y-checker/src/node/plugin.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,32 @@ import fs from 'node:fs'
33
import { fileURLToPath } from 'node:url'
44
import { normalizePath } from 'vite'
55

6+
function resolveClientScript(): string {
7+
const distFromBundle = fileURLToPath(new URL('./client/run-axe.js', import.meta.url))
8+
const distFromSource = fileURLToPath(new URL('../../dist/client/run-axe.js', import.meta.url))
9+
return fs.existsSync(distFromBundle) ? distFromBundle : distFromSource
10+
}
11+
612
export function createA11yCheckerPlugin(): PluginWithDevTools {
713
return {
814
name: 'plugin-a11y-checker-devtools',
915
devtools: {
1016
setup(context) {
17+
const clientScript = resolveClientScript()
18+
19+
context.docks.register({
20+
type: 'action',
21+
id: 'a11y-checker',
22+
title: 'Run A11y Check',
23+
icon: 'ph:wheelchair-duotone',
24+
category: 'tools',
25+
action: {
26+
importFrom: `/@fs/${normalizePath(clientScript)}`,
27+
},
28+
})
29+
1130
context.logs.add({
12-
message: 'A11y Checker enabledwill run audit automatically',
31+
message: 'A11y Checker readyclick the icon to run an audit',
1332
level: 'info',
1433
notify: true,
1534
autoDismiss: 3000,
@@ -18,22 +37,6 @@ export function createA11yCheckerPlugin(): PluginWithDevTools {
1837
})
1938
},
2039
},
21-
transformIndexHtml() {
22-
const distFromBundle = fileURLToPath(new URL('./client/run-axe.js', import.meta.url))
23-
const distFromSource = fileURLToPath(new URL('../../dist/client/run-axe.js', import.meta.url))
24-
const clientScript = fs.existsSync(distFromBundle) ? distFromBundle : distFromSource
25-
26-
return [
27-
{
28-
tag: 'script',
29-
attrs: {
30-
src: `/@fs/${normalizePath(clientScript)}`,
31-
type: 'module',
32-
},
33-
injectTo: 'body',
34-
},
35-
]
36-
},
3740
}
3841
}
3942

packages/core/src/client/webcomponents/.generated/css.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

packages/core/src/client/webcomponents/components/ToastOverlay.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,14 @@ function openLogs(toastId: string) {
5151
@click="openLogs(toast.id)"
5252
>
5353
<DockIcon
54+
v-if="toast.entry.status !== 'loading'"
5455
:icon="levelIcons[toast.entry.level] || 'ph:info-duotone'"
5556
class="w-4 h-4 flex-none mt-0.5"
5657
/>
58+
<div
59+
v-else
60+
class="w-4 h-4 flex-none mt-0.5 border-2 border-current border-t-transparent rounded-full animate-spin op50"
61+
/>
5762
<div class="flex-1 min-w-0">
5863
<div class="text-sm font-medium truncate">
5964
{{ toast.entry.message }}

packages/core/src/client/webcomponents/components/ViewBuiltinLogs.vue

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ const allLabels = computed(() => {
6464
labels.add(label)
6565
}
6666
}
67-
return labels.toSorted()
67+
return Array.from(labels).sort()
6868
})
6969
7070
const filteredEntries = computed(() => {
@@ -191,12 +191,17 @@ const allLevels: DevToolsLogLevel[] = ['error', 'warn', 'info', 'success', 'debu
191191
@click="selectedId = selectedId === entry.id ? null : entry.id"
192192
>
193193
<DockIcon
194+
v-if="entry.status !== 'loading'"
194195
:icon="levelIcons[entry.level]"
195196
class="w-4 h-4 flex-none mt-0.5"
196197
:class="levelColors[entry.level]"
197198
/>
199+
<div
200+
v-else
201+
class="w-4 h-4 flex-none mt-0.5 border-2 border-current border-t-transparent rounded-full animate-spin op50"
202+
/>
198203
<div class="flex-1 min-w-0">
199-
<div class="truncate font-medium">
204+
<div class="truncate font-medium" :class="entry.status === 'loading' ? 'op60' : ''">
200205
{{ entry.message }}
201206
</div>
202207
<div class="flex items-center gap-2 mt-0.5">
@@ -226,12 +231,17 @@ const allLevels: DevToolsLogLevel[] = ['error', 'warn', 'info', 'success', 'debu
226231
<div v-if="selectedEntry" class="h-full of-y-auto border-l border-base p-4">
227232
<div class="flex items-start gap-2 mb-3">
228233
<DockIcon
234+
v-if="selectedEntry.status !== 'loading'"
229235
:icon="levelIcons[selectedEntry.level]"
230236
class="w-5 h-5 flex-none mt-0.5"
231237
:class="levelColors[selectedEntry.level]"
232238
/>
239+
<div
240+
v-else
241+
class="w-5 h-5 flex-none mt-0.5 border-2 border-current border-t-transparent rounded-full animate-spin op50"
242+
/>
233243
<div class="flex-1">
234-
<div class="font-medium text-base">
244+
<div class="font-medium text-base" :class="selectedEntry.status === 'loading' ? 'op60' : ''">
235245
{{ selectedEntry.message }}
236246
</div>
237247
<div v-if="selectedEntry.source" class="text-xs op50 mt-0.5">

packages/core/src/client/webcomponents/state/logs.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,32 @@ export function useLogs(context: DocksContext): Reactive<LogsState> {
2020
unreadCount: 0,
2121
})
2222

23+
const prevEntryMap = new Map<string, DevToolsLogEntry>()
24+
2325
async function updateLogs() {
2426
const logs = await context.rpc.call('devtoolskit:internal:logs:list')
25-
const prevCount = state.entries.length
26-
state.entries = logs
27-
const newCount = Math.max(0, logs.length - prevCount)
28-
state.unreadCount += newCount
27+
let newCount = 0
2928

30-
// Show toast notifications for new entries with notify flag
31-
if (newCount > 0) {
32-
const newEntries = logs.slice(logs.length - newCount)
33-
for (const entry of newEntries) {
29+
for (const entry of logs) {
30+
const prev = prevEntryMap.get(entry.id)
31+
if (!prev) {
32+
// New entry
33+
newCount++
3434
if (entry.notify)
3535
addToast(entry)
3636
}
37+
else if (entry.notify && entry !== prev && JSON.stringify(entry) !== JSON.stringify(prev)) {
38+
// Updated entry with notify flag — update the toast
39+
addToast(entry)
40+
}
3741
}
42+
43+
state.entries = logs
44+
state.unreadCount += newCount
45+
46+
prevEntryMap.clear()
47+
for (const entry of logs)
48+
prevEntryMap.set(entry.id, entry)
3849
}
3950

4051
context.rpc.client.register({

packages/core/src/client/webcomponents/state/setup-script.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@ export function executeSetupScript(
5656
entry: DevToolsDockUserEntry,
5757
context: DockClientScriptContext,
5858
): Promise<void> {
59-
if (_setupPromises.has(entry.id))
59+
// Actions should re-execute on every click; only cache non-action scripts
60+
if (entry.type !== 'action' && _setupPromises.has(entry.id))
6061
return _setupPromises.get(entry.id)!
6162
const promise = _executeSetupScript(entry, context)
62-
_setupPromises.set(entry.id, promise)
63+
if (entry.type !== 'action')
64+
_setupPromises.set(entry.id, promise)
6365
return promise
6466
}

packages/core/src/client/webcomponents/state/toasts.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,18 @@ export function useToasts(): Reactive<ToastItem[]> {
1515
}
1616

1717
export function addToast(entry: DevToolsLogEntry): void {
18-
// Avoid duplicates
19-
if (toasts.some(t => t.id === entry.id))
18+
// Dedup: update existing toast with same id
19+
const existing = toasts.find(t => t.id === entry.id)
20+
if (existing) {
21+
existing.entry = entry
22+
// Reset auto-dismiss timer
23+
const timer = timers.get(entry.id)
24+
if (timer)
25+
clearTimeout(timer)
26+
const timeout = entry.autoDismiss ?? 5000
27+
timers.set(entry.id, setTimeout(dismissToast, timeout, entry.id))
2028
return
29+
}
2130

2231
const item: ToastItem = { id: entry.id, entry }
2332
toasts.push(item)

packages/core/src/node/context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export async function createDevToolsContext(
9494
}, context.mode === 'build' ? 0 : 10)
9595

9696
logsHost.events.on('log:added', () => debouncedLogsUpdate())
97+
logsHost.events.on('log:updated', () => debouncedLogsUpdate())
9798
logsHost.events.on('log:removed', () => debouncedLogsUpdate())
9899
logsHost.events.on('log:cleared', () => debouncedLogsUpdate())
99100

packages/core/src/node/host-logs.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ export class DevToolsLogsHost implements DevToolsLogsHostType {
1515
) {}
1616

1717
add(input: DevToolsLogEntryInput): DevToolsLogEntry {
18+
// Dedup: if an entry with the same explicit id exists, update it instead
19+
if (input.id && this.entries.has(input.id)) {
20+
return this.update(input.id, input)!
21+
}
22+
1823
const entry: DevToolsLogEntry = {
1924
...input,
2025
id: input.id ?? nanoid(),
@@ -40,6 +45,39 @@ export class DevToolsLogsHost implements DevToolsLogsHostType {
4045
return entry
4146
}
4247

48+
update(id: string, patch: Partial<DevToolsLogEntryInput>): DevToolsLogEntry | undefined {
49+
const existing = this.entries.get(id)
50+
if (!existing)
51+
return undefined
52+
53+
const updated: DevToolsLogEntry = {
54+
...existing,
55+
...patch,
56+
id: existing.id,
57+
source: existing.source,
58+
timestamp: existing.timestamp,
59+
}
60+
61+
this.entries.set(id, updated)
62+
this.events.emit('log:updated', updated)
63+
64+
// Reset autoDelete timer if changed
65+
if (patch.autoDelete !== undefined) {
66+
const timer = this._autoDeleteTimers.get(id)
67+
if (timer) {
68+
clearTimeout(timer)
69+
this._autoDeleteTimers.delete(id)
70+
}
71+
if (patch.autoDelete) {
72+
this._autoDeleteTimers.set(id, setTimeout(() => {
73+
this.remove(id)
74+
}, patch.autoDelete))
75+
}
76+
}
77+
78+
return updated
79+
}
80+
4381
remove(id: string): void {
4482
const timer = this._autoDeleteTimers.get(id)
4583
if (timer) {

0 commit comments

Comments
 (0)