Skip to content

Commit

Permalink
feat: support event deduping
Browse files Browse the repository at this point in the history
  • Loading branch information
harlan-zw committed Jan 9, 2025
1 parent debcd1d commit 11005df
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 8 deletions.
13 changes: 10 additions & 3 deletions packages/scripts/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ export interface ScriptInstance<T extends BaseScriptApi> {
proxy: AsVoidFunctions<T>
instance?: T
id: string
status: UseScriptStatus
status: Readonly<UseScriptStatus>
entry?: ActiveHeadEntry<any>
load: () => Promise<T>
warmup: (rel: WarmupStrategy) => ActiveHeadEntry<any>
remove: () => boolean
setupTriggerHandler: (trigger: UseScriptOptions['trigger']) => void
// cbs
onLoaded: (fn: (instance: T) => void | Promise<void>) => void
onError: (fn: (err?: Error) => void | Promise<void>) => void
onLoaded: (fn: (instance: T) => void | Promise<void>, options?: EventHandlerOptions) => void
onError: (fn: (err?: Error) => void | Promise<void>, options?: EventHandlerOptions) => void
/**
* @internal
*/
Expand Down Expand Up @@ -66,6 +66,13 @@ export interface ScriptInstance<T extends BaseScriptApi> {
}
}

export interface EventHandlerOptions {
/**
* Used to dedupe the event, allowing you to have an event run only a single time.
*/
key?: string
}

export type RecordingEntry =
| { type: 'get', key: string | symbol, args?: any[], value?: any }
| { type: 'apply', key: string | symbol, args: any[] }
Expand Down
19 changes: 14 additions & 5 deletions packages/scripts/src/useScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
Head,
} from '@unhead/schema'
import type {
EventHandlerOptions,
ScriptInstance,
UseFunctionType,
UseScriptContext,
Expand Down Expand Up @@ -52,7 +53,15 @@ export function useScript<T extends Record<symbol | string, any> = Record<symbol
})

const _cbs: ScriptInstance<T>['_cbs'] = { loaded: [], error: [] }
const _registerCb = (key: 'loaded' | 'error', cb: any) => {
const _uniqueCbs: Set<string> = new Set<string>()
const _registerCb = (key: 'loaded' | 'error', cb: any, options?: EventHandlerOptions) => {
if (options?.key) {
const key = `${options?.key}:${options.key}`
if (_uniqueCbs.has(key)) {
return
}
_uniqueCbs.add(key)
}
if (_cbs[key]) {
const i: number = _cbs[key].push(cb)
return () => _cbs[key]?.splice(i - 1, 1)
Expand Down Expand Up @@ -160,11 +169,11 @@ export function useScript<T extends Record<symbol | string, any> = Record<symbol
_registerCb('loaded', cb)
return loadPromise
},
onLoaded(cb: (instance: T) => void | Promise<void>) {
return _registerCb('loaded', cb)
onLoaded(cb: (instance: T) => void | Promise<void>, options?: EventHandlerOptions) {
return _registerCb('loaded', cb, options)
},
onError(cb: (err?: Error) => void | Promise<void>) {
return _registerCb('error', cb)
onError(cb: (err?: Error) => void | Promise<void>, options?: EventHandlerOptions) {
return _registerCb('error', cb, options)
},
setupTriggerHandler(trigger: UseScriptOptions['trigger']) {
if (script.status !== 'awaitingLoad') {
Expand Down
50 changes: 50 additions & 0 deletions packages/scripts/test/unit/events.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// @vitest-environment jsdom

import { describe, it } from 'vitest'
import { createHeadWithContext } from '../../../../test/util'
import { useScript } from '../../src/useScript'

describe('useScript events', () => {
it('simple', async () => {
createHeadWithContext()
const instance = useScript('/script.js', {
trigger: 'server',
})
expect(await new Promise<true>((resolve) => {
instance.status = 'loaded'
instance.onLoaded(() => {
resolve(true)
})
})).toBeTruthy()
})
it('dedupe', async () => {
createHeadWithContext()
const instance = useScript('/script.js', {
trigger: 'server',
})
const calls: any[] = []
instance.onLoaded(() => {
calls.push('a')
}, {
key: 'once',
})
instance.onLoaded(() => {
calls.push('b')
}, {
key: 'once',
})
instance.status = 'loaded'
await new Promise<void>((resolve) => {
instance.onLoaded(() => {
calls.push('c')
resolve()
})
})
expect(calls).toMatchInlineSnapshot(`
[
"a",
"c",
]
`)
})
})

0 comments on commit 11005df

Please sign in to comment.