diff --git a/docs/content/1.docs/3.api/3.utils/on-nuxt-ready.md b/docs/content/1.docs/3.api/3.utils/on-nuxt-ready.md new file mode 100644 index 0000000000..cf927b1584 --- /dev/null +++ b/docs/content/1.docs/3.api/3.utils/on-nuxt-ready.md @@ -0,0 +1,19 @@ +--- +title: "onNuxtReady" +description: The onNuxtReady composable allows running a callback after your app has finished initializing. +--- + +# `onNuxtReady` + +The `onNuxtReady` composable allows running a callback after your app has finished initializing. It is ideal for running code that should not block the initial rendering of your app. + +```ts +export default defineNuxtPlugin(() => { + onNuxtReady(async () => { + const myAnalyticsLibrary = await import('my-big-analytics-library') + // do something with myAnalyticsLibrary + }) +}) +``` + +It is 'safe' to run even after your app has initialized. In this case, then the code will be registered to run in the next idle callback. diff --git a/docs/content/1.docs/3.api/3.utils/set-layout.md b/docs/content/1.docs/3.api/3.utils/set-page-layout.md similarity index 100% rename from docs/content/1.docs/3.api/3.utils/set-layout.md rename to docs/content/1.docs/3.api/3.utils/set-page-layout.md diff --git a/packages/nuxt/src/app/compat/idle-callback.ts b/packages/nuxt/src/app/compat/idle-callback.ts new file mode 100644 index 0000000000..0d42eadcc9 --- /dev/null +++ b/packages/nuxt/src/app/compat/idle-callback.ts @@ -0,0 +1,16 @@ +// Polyfills for Safari support +// https://caniuse.com/requestidlecallback +export const requestIdleCallback: Window['requestIdleCallback'] = process.server + ? undefined as any + : (globalThis.requestIdleCallback || ((cb) => { + const start = Date.now() + const idleDeadline = { + didTimeout: false, + timeRemaining: () => Math.max(0, 50 - (Date.now() - start)) + } + return setTimeout(() => { cb(idleDeadline) }, 1) + })) + +export const cancelIdleCallback: Window['cancelIdleCallback'] = process.server + ? null as any + : (globalThis.cancelIdleCallback || ((id) => { clearTimeout(id) })) diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index 7b1e5821de..8567f09b7c 100644 --- a/packages/nuxt/src/app/components/nuxt-link.ts +++ b/packages/nuxt/src/app/components/nuxt-link.ts @@ -3,8 +3,10 @@ import type { RouteLocationRaw } from 'vue-router' import { hasProtocol } from 'ufo' import { preloadRouteComponents } from '../composables/preload' +import { onNuxtReady } from '../composables/ready' import { navigateTo, useRouter } from '../composables/router' import { useNuxtApp } from '../nuxt' +import { cancelIdleCallback, requestIdleCallback } from '../compat/idle-callback' const firstNonUndefined = (...args: (T | undefined)[]) => args.find(arg => arg !== undefined) @@ -42,23 +44,6 @@ export type NuxtLinkProps = { ariaCurrentValue?: string } -// Polyfills for Safari support -// https://caniuse.com/requestidlecallback -const requestIdleCallback: Window['requestIdleCallback'] = process.server - ? undefined as any - : (globalThis.requestIdleCallback || ((cb) => { - const start = Date.now() - const idleDeadline = { - didTimeout: false, - timeRemaining: () => Math.max(0, 50 - (Date.now() - start)) - } - return setTimeout(() => { cb(idleDeadline) }, 1) - })) - -const cancelIdleCallback: Window['cancelIdleCallback'] = process.server - ? null as any - : (globalThis.cancelIdleCallback || ((id) => { clearTimeout(id) })) - export function defineNuxtLink (options: NuxtLinkOptions) { const componentName = options.componentName || 'NuxtLink' @@ -197,7 +182,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) { let unobserve: Function | null = null onMounted(() => { const observer = useObserver() - function registerCallback () { + onNuxtReady(() => { idleId = requestIdleCallback(() => { if (el?.value?.tagName) { unobserve = observer!.observe(el.value, async () => { @@ -211,12 +196,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) { }) } }) - } - if (nuxtApp.isHydrating) { - nuxtApp.hooks.hookOnce('app:suspense:resolve', registerCallback) - } else { - registerCallback() - } + }) }) onBeforeUnmount(() => { if (idleId) { cancelIdleCallback(idleId) } diff --git a/packages/nuxt/src/app/composables/index.ts b/packages/nuxt/src/app/composables/index.ts index 251a09fd59..96b7aea648 100644 --- a/packages/nuxt/src/app/composables/index.ts +++ b/packages/nuxt/src/app/composables/index.ts @@ -10,6 +10,7 @@ export type { FetchResult, UseFetchOptions } from './fetch' export { useCookie } from './cookie' export type { CookieOptions, CookieRef } from './cookie' export { useRequestHeaders, useRequestEvent, setResponseStatus } from './ssr' +export { onNuxtReady } from './ready' export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, onBeforeRouteLeave, onBeforeRouteUpdate, setPageLayout, navigateTo, useRoute, useRouter } from './router' export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router' export { preloadComponents, prefetchComponents, preloadRouteComponents } from './preload' diff --git a/packages/nuxt/src/app/composables/ready.ts b/packages/nuxt/src/app/composables/ready.ts new file mode 100644 index 0000000000..6f649af169 --- /dev/null +++ b/packages/nuxt/src/app/composables/ready.ts @@ -0,0 +1,11 @@ +import { useNuxtApp } from '../nuxt' +import { requestIdleCallback } from '../compat/idle-callback' + +export const onNuxtReady = (callback: () => any) => { + const nuxtApp = useNuxtApp() + if (nuxtApp.isHydrating) { + nuxtApp.hooks.hookOnce('app:suspense:resolve', () => { requestIdleCallback(callback) }) + } else { + requestIdleCallback(callback) + } +} diff --git a/packages/nuxt/src/imports/presets.ts b/packages/nuxt/src/imports/presets.ts index f8102b2a20..04e992d6ae 100644 --- a/packages/nuxt/src/imports/presets.ts +++ b/packages/nuxt/src/imports/presets.ts @@ -37,6 +37,7 @@ const appPreset = defineUnimportPreset({ 'useRequestEvent', 'setResponseStatus', 'setPageLayout', + 'onNuxtReady', 'useRouter', 'useRoute', 'defineNuxtRouteMiddleware',