Skip to content

Commit

Permalink
feat: automatically preload and preconnect relevant scripts
Browse files Browse the repository at this point in the history
Closes #218
  • Loading branch information
harlan-zw committed Sep 3, 2024
1 parent 5b61da0 commit a65a5e0
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 27 deletions.
4 changes: 3 additions & 1 deletion src/runtime/components/ScriptYouTubePlayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const { onLoaded, status } = useScriptYouTubePlayer({
const player: Ref<YT.Player | undefined> = ref()
let clickTriggered = false
if (props.trigger === 'mousedown') {
if (props.trigger === 'mousedown' && trigger instanceof Promise) {
trigger.then(() => {
clickTriggered = true
})
Expand Down Expand Up @@ -126,12 +126,14 @@ if (import.meta.server) {
useHead({
link: [
{
key: `nuxt-script-youtube-img`,
rel: props.aboveTheFold ? 'preconnect' : 'dns-prefetch',
href: 'https://i.ytimg.com',
},
props.aboveTheFold
// we can preload the placeholder image
? {
key: `nuxt-script-youtube-img`,
rel: 'preload',
as: 'image',
href: placeholder.value,
Expand Down
25 changes: 21 additions & 4 deletions src/runtime/composables/useScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { UseScriptOptions, UseFunctionType, AsAsyncFunctionValues } from '@
import { resolveScriptKey } from 'unhead'
import { defu } from 'defu'
import { useScript as _useScript } from '@unhead/vue'
import { injectHead, onNuxtReady, useNuxtApp, useRuntimeConfig, reactive } from '#imports'
import { injectHead, onNuxtReady, useHead, useNuxtApp, useRuntimeConfig, reactive } from '#imports'
import type { NuxtDevToolsScriptInstance, NuxtUseScriptOptions } from '#nuxt-scripts'

function useNuxtScriptRuntimeConfig() {
Expand All @@ -25,11 +25,28 @@ export type UseScriptContext<T extends Record<symbol | string, any>> =
export function useScript<T extends Record<symbol | string, any> = Record<symbol | string, any>, U = Record<symbol | string, any>>(input: UseScriptInput, options?: NuxtUseScriptOptions<T, U>): UseScriptContext<UseFunctionType<NuxtUseScriptOptions<T, U>, T>> {
input = typeof input === 'string' ? { src: input } : input
options = defu(options, useNuxtScriptRuntimeConfig()?.defaultScriptOptions) as NuxtUseScriptOptions<T, U>

if (options.trigger === 'onNuxtReady')
// browser hint optimizations
const rel = options.trigger === 'onNuxtReady' ? 'preload' : 'preconnect'
const id = resolveScriptKey(input) as keyof typeof nuxtApp._scripts
if (options.trigger !== 'server' && (rel === 'preload' || !input.src.startsWith('/'))) {
useHead({
link: [
{
rel,
as: rel === 'preload' ? 'script' : undefined,
href: input.src,
crossorigin: typeof input.crossorigin !== 'undefined' ? input.crossorigin : 'anonymous',
key: `nuxt-script-${id}`,
tagPriority: rel === 'preload' ? 'high' : 0,
fetchpriority: 'low',
},
],
})
}
if (options.trigger === 'onNuxtReady') {
options.trigger = onNuxtReady
}
const nuxtApp = useNuxtApp()
const id = resolveScriptKey(input) as keyof typeof nuxtApp._scripts
nuxtApp.$scripts = nuxtApp.$scripts! || reactive({})
const exists = !!(nuxtApp.$scripts as Record<string, any>)?.[id]
if (import.meta.client) {
Expand Down
42 changes: 20 additions & 22 deletions src/runtime/composables/useScriptTriggerElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,31 +52,29 @@ function useElementVisibilityPromise(element: MaybeComputedElementRef) {
/**
* Create a trigger for an element to load a script based on specific element events.
*/
export function useScriptTriggerElement(options: ElementScriptTriggerOptions): Promise<void> {
export function useScriptTriggerElement(options: ElementScriptTriggerOptions): Promise<void> | 'onNuxtReady' {
const { el, trigger } = options
const triggers = (Array.isArray(options.trigger) ? options.trigger : [options.trigger]).filter(Boolean) as string[]
if (!trigger || triggers.includes('immediate') || triggers.includes('onNuxtReady')) {
return 'onNuxtReady'
}
if (import.meta.server || !el)
return new Promise<void>(() => {})
const triggers = (Array.isArray(options.trigger) ? options.trigger : [options.trigger]).filter(Boolean) as string[]
if (el && triggers.some(t => ['visibility', 'visible'].includes(t)))
if (triggers.some(t => ['visibility', 'visible'].includes(t)))
return useElementVisibilityPromise(el)
if (!trigger)
return Promise.resolve()
if (!triggers.includes('immediate')) {
// TODO optimize this, only have 1 instance of intersection observer, stop on find
return new Promise<void>((resolve, reject) => {
const _ = useEventListener(
typeof el !== 'undefined' ? (el as EventTarget) : document.body,
triggers,
() => {
_()
resolve()
},
{ once: true, passive: true },
)
tryOnScopeDispose(reject)
}).catch(() => {
// it's okay
})
}
return Promise.resolve()
return new Promise<void>((resolve, reject) => {
const _ = useEventListener(
typeof el !== 'undefined' ? (el as EventTarget) : document.body,
triggers,
() => {
_()
resolve()
},
{ once: true, passive: true },
)
tryOnScopeDispose(reject)
}).catch(() => {
// it's okay
})
}

0 comments on commit a65a5e0

Please sign in to comment.