Skip to content

Commit a65a5e0

Browse files
committed
feat: automatically preload and preconnect relevant scripts
Closes #218
1 parent 5b61da0 commit a65a5e0

File tree

3 files changed

+44
-27
lines changed

3 files changed

+44
-27
lines changed

src/runtime/components/ScriptYouTubePlayer.vue

+3-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const { onLoaded, status } = useScriptYouTubePlayer({
5252
5353
const player: Ref<YT.Player | undefined> = ref()
5454
let clickTriggered = false
55-
if (props.trigger === 'mousedown') {
55+
if (props.trigger === 'mousedown' && trigger instanceof Promise) {
5656
trigger.then(() => {
5757
clickTriggered = true
5858
})
@@ -126,12 +126,14 @@ if (import.meta.server) {
126126
useHead({
127127
link: [
128128
{
129+
key: `nuxt-script-youtube-img`,
129130
rel: props.aboveTheFold ? 'preconnect' : 'dns-prefetch',
130131
href: 'https://i.ytimg.com',
131132
},
132133
props.aboveTheFold
133134
// we can preload the placeholder image
134135
? {
136+
key: `nuxt-script-youtube-img`,
135137
rel: 'preload',
136138
as: 'image',
137139
href: placeholder.value,

src/runtime/composables/useScript.ts

+21-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { UseScriptOptions, UseFunctionType, AsAsyncFunctionValues } from '@
33
import { resolveScriptKey } from 'unhead'
44
import { defu } from 'defu'
55
import { useScript as _useScript } from '@unhead/vue'
6-
import { injectHead, onNuxtReady, useNuxtApp, useRuntimeConfig, reactive } from '#imports'
6+
import { injectHead, onNuxtReady, useHead, useNuxtApp, useRuntimeConfig, reactive } from '#imports'
77
import type { NuxtDevToolsScriptInstance, NuxtUseScriptOptions } from '#nuxt-scripts'
88

99
function useNuxtScriptRuntimeConfig() {
@@ -25,11 +25,28 @@ export type UseScriptContext<T extends Record<symbol | string, any>> =
2525
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>> {
2626
input = typeof input === 'string' ? { src: input } : input
2727
options = defu(options, useNuxtScriptRuntimeConfig()?.defaultScriptOptions) as NuxtUseScriptOptions<T, U>
28-
29-
if (options.trigger === 'onNuxtReady')
28+
// browser hint optimizations
29+
const rel = options.trigger === 'onNuxtReady' ? 'preload' : 'preconnect'
30+
const id = resolveScriptKey(input) as keyof typeof nuxtApp._scripts
31+
if (options.trigger !== 'server' && (rel === 'preload' || !input.src.startsWith('/'))) {
32+
useHead({
33+
link: [
34+
{
35+
rel,
36+
as: rel === 'preload' ? 'script' : undefined,
37+
href: input.src,
38+
crossorigin: typeof input.crossorigin !== 'undefined' ? input.crossorigin : 'anonymous',
39+
key: `nuxt-script-${id}`,
40+
tagPriority: rel === 'preload' ? 'high' : 0,
41+
fetchpriority: 'low',
42+
},
43+
],
44+
})
45+
}
46+
if (options.trigger === 'onNuxtReady') {
3047
options.trigger = onNuxtReady
48+
}
3149
const nuxtApp = useNuxtApp()
32-
const id = resolveScriptKey(input) as keyof typeof nuxtApp._scripts
3350
nuxtApp.$scripts = nuxtApp.$scripts! || reactive({})
3451
const exists = !!(nuxtApp.$scripts as Record<string, any>)?.[id]
3552
if (import.meta.client) {

src/runtime/composables/useScriptTriggerElement.ts

+20-22
Original file line numberDiff line numberDiff line change
@@ -52,31 +52,29 @@ function useElementVisibilityPromise(element: MaybeComputedElementRef) {
5252
/**
5353
* Create a trigger for an element to load a script based on specific element events.
5454
*/
55-
export function useScriptTriggerElement(options: ElementScriptTriggerOptions): Promise<void> {
55+
export function useScriptTriggerElement(options: ElementScriptTriggerOptions): Promise<void> | 'onNuxtReady' {
5656
const { el, trigger } = options
57+
const triggers = (Array.isArray(options.trigger) ? options.trigger : [options.trigger]).filter(Boolean) as string[]
58+
if (!trigger || triggers.includes('immediate') || triggers.includes('onNuxtReady')) {
59+
return 'onNuxtReady'
60+
}
5761
if (import.meta.server || !el)
5862
return new Promise<void>(() => {})
59-
const triggers = (Array.isArray(options.trigger) ? options.trigger : [options.trigger]).filter(Boolean) as string[]
60-
if (el && triggers.some(t => ['visibility', 'visible'].includes(t)))
63+
if (triggers.some(t => ['visibility', 'visible'].includes(t)))
6164
return useElementVisibilityPromise(el)
62-
if (!trigger)
63-
return Promise.resolve()
64-
if (!triggers.includes('immediate')) {
6565
// TODO optimize this, only have 1 instance of intersection observer, stop on find
66-
return new Promise<void>((resolve, reject) => {
67-
const _ = useEventListener(
68-
typeof el !== 'undefined' ? (el as EventTarget) : document.body,
69-
triggers,
70-
() => {
71-
_()
72-
resolve()
73-
},
74-
{ once: true, passive: true },
75-
)
76-
tryOnScopeDispose(reject)
77-
}).catch(() => {
78-
// it's okay
79-
})
80-
}
81-
return Promise.resolve()
66+
return new Promise<void>((resolve, reject) => {
67+
const _ = useEventListener(
68+
typeof el !== 'undefined' ? (el as EventTarget) : document.body,
69+
triggers,
70+
() => {
71+
_()
72+
resolve()
73+
},
74+
{ once: true, passive: true },
75+
)
76+
tryOnScopeDispose(reject)
77+
}).catch(() => {
78+
// it's okay
79+
})
8280
}

0 commit comments

Comments
 (0)