import { hasProtocol, joinURL } from 'ufo' import { parse } from 'devalue' import { useHead } from '@unhead/vue' import { getCurrentInstance } from 'vue' import { useNuxtApp, useRuntimeConfig } from '../nuxt' // @ts-expect-error virtual import import { renderJsonPayloads } from '#build/nuxt.config.mjs' interface LoadPayloadOptions { fresh?: boolean hash?: string } export function loadPayload (url: string, opts: LoadPayloadOptions = {}): Record | Promise> | null { if (process.server) { return null } const payloadURL = _getPayloadURL(url, opts) const nuxtApp = useNuxtApp() const cache = nuxtApp._payloadCache = nuxtApp._payloadCache || {} if (cache[payloadURL]) { return cache[payloadURL] } cache[payloadURL] = _importPayload(payloadURL).then((payload) => { if (!payload) { delete cache[payloadURL] return null } return payload }) return cache[payloadURL] } export function preloadPayload (url: string, opts: LoadPayloadOptions = {}) { const payloadURL = _getPayloadURL(url, opts) useHead({ link: [ { rel: 'modulepreload', href: payloadURL } ] }) } // --- Internal --- const extension = renderJsonPayloads ? 'json' : 'js' function _getPayloadURL (url: string, opts: LoadPayloadOptions = {}) { const u = new URL(url, 'http://localhost') if (u.search) { throw new Error('Payload URL cannot contain search params: ' + url) } if (u.host !== 'localhost' || hasProtocol(u.pathname, { acceptRelative: true })) { throw new Error('Payload URL must not include hostname: ' + url) } const hash = opts.hash || (opts.fresh ? Date.now() : '') return joinURL(useRuntimeConfig().app.baseURL, u.pathname, hash ? `_payload.${hash}.${extension}` : `_payload.${extension}`) } async function _importPayload (payloadURL: string) { if (process.server) { return null } try { return renderJsonPayloads ? parsePayload(await fetch(payloadURL).then(res => res.text())) : await import(/* webpackIgnore: true */ /* @vite-ignore */ payloadURL).then(r => r.default || r) } catch (err) { console.warn('[nuxt] Cannot load payload ', payloadURL, err) } return null } export function isPrerendered () { // Note: Alternative for server is checking x-nitro-prerender header const nuxtApp = useNuxtApp() return !!nuxtApp.payload.prerenderedAt } let payloadCache: any = null export async function getNuxtClientPayload () { if (process.server) { return } if (payloadCache) { return payloadCache } const el = document.getElementById('__NUXT_DATA__') if (!el) { return {} } const inlineData = parsePayload(el.textContent || '') const externalData = el.dataset.src ? await _importPayload(el.dataset.src) : undefined payloadCache = { ...inlineData, ...externalData, ...window.__NUXT__ } return payloadCache } export function parsePayload (payload: string) { return parse(payload, useNuxtApp()._payloadRevivers) } /** * This is an experimental function for configuring passing rich data from server -> client. */ export function definePayloadReducer ( name: string, reduce: (data: any) => any ) { if (process.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. */ export function definePayloadReviver ( name: string, revive: (data: string) => any | undefined ) { if (process.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 (process.client) { useNuxtApp()._payloadRevivers[name] = revive } }