Skip to content

Commit

Permalink
fix(scripts): safer and more powerful proxying
Browse files Browse the repository at this point in the history
  • Loading branch information
harlan-zw committed May 1, 2024
1 parent 9591240 commit 9c73bbd
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 11 deletions.
25 changes: 15 additions & 10 deletions packages/unhead/src/composables/useScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function useScript<T extends Record<symbol | string, any>>(_input: UseScr
}
})

const proxyApi = (!head.ssr && options?.use?.()) || {} as T
const proxy = { value: (!head.ssr && options?.use?.()) || {} as T }
const loadPromise = new Promise<T>((resolve, reject) => {
const _ = head.hooks.hook('script:updated', ({ script }) => {
if (script.id === id && (script.status === 'loaded' || script.status === 'error')) {
Expand All @@ -53,7 +53,7 @@ export function useScript<T extends Record<symbol | string, any>>(_input: UseScr
_()
}
})
}).then(api => Object.assign(proxyApi, api))
}).then(api => (proxy.value = api))
const script: ScriptInstance<T> = {
id,
status: 'awaitingLoad',
Expand Down Expand Up @@ -95,20 +95,25 @@ export function useScript<T extends Record<symbol | string, any>>(_input: UseScr
trigger(script.load)

// 3. Proxy the script API
const instance = new Proxy<T>(proxyApi, {
get(_, fn) {
const instance = new Proxy<{ value: T }>(proxy, {
get({ value: _ }, k) {
const $script = Object.assign(loadPromise, script)
const stub = options.stub?.({ script: $script, fn })
const stub = options.stub?.({ script: $script, fn: k })
if (stub)
return stub
// $script is stubbed by abstraction layers
if (fn === '$script')
if (k === '$script')
return $script
const exists = fn in _
head.hooks.callHook('script:instance-fn', { script, fn, exists: fn in _ })
const exists = k in _
head.hooks.callHook('script:instance-fn', { script, fn: k, exists: k in _ })
return exists
? Reflect.get(_, fn)
: (...args: any[]) => loadPromise.then(api => Reflect.apply(api[fn], api, args))
? Reflect.get(_, k)
: (...args: any[]) => loadPromise.then((api) => {
const _k = Reflect.get(api, k)
return typeof _k === 'function'
? Reflect.apply(api[k], api, args)
: _k
})
},
}) as any as T & { $script: ScriptInstance<T> & Promise<T> }
// 4. Providing a unique context for the script
Expand Down
2 changes: 1 addition & 1 deletion test/unhead/dom/useScript.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('dom useScript', () => {

let calledFn
const hookPromise = new Promise<void>((resolve) => {
head.hooks.hook('script:instance-fn', ({ script, fn, args }) => {
head.hooks.hook('script:instance-fn', ({ script, fn }) => {
if (script.id === instance.$script.id) {
calledFn = fn
resolve()
Expand Down

0 comments on commit 9c73bbd

Please sign in to comment.