mirror of
https://github.com/nuxt/nuxt.git
synced 2024-12-04 19:37:18 +00:00
166 lines
5.2 KiB
TypeScript
166 lines
5.2 KiB
TypeScript
import { hasProtocol, joinURL, withoutTrailingSlash } from 'ufo'
|
|
import { parse } from 'devalue'
|
|
import { useHead } from '@unhead/vue'
|
|
import { getCurrentInstance, onServerPrefetch, reactive } from 'vue'
|
|
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
|
|
import type { NuxtPayload } from '../nuxt'
|
|
|
|
import { useRoute } from './router'
|
|
import { getAppManifest, getRouteRules } from './manifest'
|
|
|
|
// @ts-expect-error virtual import
|
|
import { appId, appManifest, multiApp, payloadExtraction, renderJsonPayloads } from '#build/nuxt.config.mjs'
|
|
|
|
interface LoadPayloadOptions {
|
|
fresh?: boolean
|
|
hash?: string
|
|
}
|
|
|
|
/** @since 3.0.0 */
|
|
export async function loadPayload (url: string, opts: LoadPayloadOptions = {}): Promise<Record<string, any> | null> {
|
|
if (import.meta.server || !payloadExtraction) { return null }
|
|
const payloadURL = await _getPayloadURL(url, opts)
|
|
const nuxtApp = useNuxtApp()
|
|
const cache = nuxtApp._payloadCache = nuxtApp._payloadCache || {}
|
|
if (payloadURL in cache) {
|
|
return cache[payloadURL]
|
|
}
|
|
cache[payloadURL] = isPrerendered(url).then((prerendered) => {
|
|
if (!prerendered) {
|
|
cache[payloadURL] = null
|
|
return null
|
|
}
|
|
return _importPayload(payloadURL).then((payload) => {
|
|
if (payload) { return payload }
|
|
|
|
delete cache[payloadURL]
|
|
return null
|
|
})
|
|
})
|
|
return cache[payloadURL]
|
|
}
|
|
/** @since 3.0.0 */
|
|
export function preloadPayload (url: string, opts: LoadPayloadOptions = {}): Promise<void> {
|
|
const nuxtApp = useNuxtApp()
|
|
const promise = _getPayloadURL(url, opts).then((payloadURL) => {
|
|
nuxtApp.runWithContext(() => useHead({
|
|
link: [
|
|
{ rel: 'modulepreload', href: payloadURL },
|
|
],
|
|
}))
|
|
})
|
|
if (import.meta.server) {
|
|
onServerPrefetch(() => promise)
|
|
}
|
|
return promise
|
|
}
|
|
|
|
// --- Internal ---
|
|
|
|
const filename = renderJsonPayloads ? '_payload.json' : '_payload.js'
|
|
async function _getPayloadURL (url: string, opts: LoadPayloadOptions = {}) {
|
|
const u = new URL(url, 'http://localhost')
|
|
if (u.host !== 'localhost' || hasProtocol(u.pathname, { acceptRelative: true })) {
|
|
throw new Error('Payload URL must not include hostname: ' + url)
|
|
}
|
|
const config = useRuntimeConfig()
|
|
const hash = opts.hash || (opts.fresh ? Date.now() : config.app.buildId)
|
|
const cdnURL = config.app.cdnURL
|
|
const baseOrCdnURL = cdnURL && await isPrerendered(url) ? cdnURL : config.app.baseURL
|
|
return joinURL(baseOrCdnURL, u.pathname, filename + (hash ? `?${hash}` : ''))
|
|
}
|
|
|
|
async function _importPayload (payloadURL: string) {
|
|
if (import.meta.server || !payloadExtraction) { return null }
|
|
const payloadPromise = renderJsonPayloads
|
|
? fetch(payloadURL).then(res => res.text().then(parsePayload))
|
|
: import(/* webpackIgnore: true */ /* @vite-ignore */ payloadURL).then(r => r.default || r)
|
|
|
|
try {
|
|
return await payloadPromise
|
|
} catch (err) {
|
|
console.warn('[nuxt] Cannot load payload ', payloadURL, err)
|
|
}
|
|
return null
|
|
}
|
|
/** @since 3.0.0 */
|
|
export async function isPrerendered (url = useRoute().path) {
|
|
// Note: Alternative for server is checking x-nitro-prerender header
|
|
if (!appManifest) { return !!useNuxtApp().payload.prerenderedAt }
|
|
url = withoutTrailingSlash(url)
|
|
const manifest = await getAppManifest()
|
|
if (manifest.prerendered.includes(url)) {
|
|
return true
|
|
}
|
|
const rules = await getRouteRules(url)
|
|
return !!rules.prerender && !rules.redirect
|
|
}
|
|
|
|
let payloadCache: NuxtPayload | null = null
|
|
|
|
/** @since 3.4.0 */
|
|
export async function getNuxtClientPayload () {
|
|
if (import.meta.server) {
|
|
return null
|
|
}
|
|
if (payloadCache) {
|
|
return payloadCache
|
|
}
|
|
|
|
const el = multiApp ? document.querySelector(`[data-nuxt-data="${appId}"]`) as HTMLElement : document.getElementById('__NUXT_DATA__')
|
|
if (!el) {
|
|
return {} as Partial<NuxtPayload>
|
|
}
|
|
|
|
const inlineData = await parsePayload(el.textContent || '')
|
|
|
|
const externalData = el.dataset.src ? await _importPayload(el.dataset.src) : undefined
|
|
|
|
payloadCache = {
|
|
...inlineData,
|
|
...externalData,
|
|
...(multiApp ? window.__NUXT__?.[appId] : window.__NUXT__),
|
|
}
|
|
|
|
if (payloadCache!.config?.public) {
|
|
payloadCache!.config.public = reactive(payloadCache!.config.public)
|
|
}
|
|
|
|
return payloadCache
|
|
}
|
|
|
|
export async function parsePayload (payload: string) {
|
|
return await parse(payload, useNuxtApp()._payloadRevivers)
|
|
}
|
|
|
|
/**
|
|
* This is an experimental function for configuring passing rich data from server -> client.
|
|
* @since 3.4.0
|
|
*/
|
|
export function definePayloadReducer (
|
|
name: string,
|
|
reduce: (data: any) => any,
|
|
) {
|
|
if (import.meta.server) {
|
|
useNuxtApp().ssrContext!._payloadReducers[name] = reduce
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is an experimental function for configuring passing rich data from server -> client.
|
|
*
|
|
* This function _must_ be called in a Nuxt plugin that is `unshift`ed to the beginning of the Nuxt plugins array.
|
|
* @since 3.4.0
|
|
*/
|
|
export function definePayloadReviver (
|
|
name: string,
|
|
revive: (data: any) => any | undefined,
|
|
) {
|
|
if (import.meta.dev && getCurrentInstance()) {
|
|
console.warn('[nuxt] [definePayloadReviver] This function must be called in a Nuxt plugin that is `unshift`ed to the beginning of the Nuxt plugins array.')
|
|
}
|
|
if (import.meta.client) {
|
|
useNuxtApp()._payloadRevivers[name] = revive
|
|
}
|
|
}
|