206 lines
6.7 KiB
JavaScript
206 lines
6.7 KiB
JavaScript
import { hasProtocol, joinURL } from "ufo";
|
|
import { parse } from "devalue";
|
|
import { getCurrentInstance, onServerPrefetch, reactive } from "vue";
|
|
import { useNuxtApp, useRuntimeConfig } from "../nuxt.js";
|
|
import { useHead } from "./head.js";
|
|
import { useRoute } from "./router.js";
|
|
import { getAppManifest, getRouteRules } from "./manifest.js";
|
|
import { appId, appManifest, multiApp, payloadExtraction, renderJsonPayloads } from "#build/nuxt.config.mjs";
|
|
export async function loadPayload(url, opts = {}) {
|
|
if (import.meta.server || !payloadExtraction) {
|
|
return null;
|
|
}
|
|
if (await shouldLoadPayload(url)) {
|
|
const payloadURL = await _getPayloadURL(url, opts);
|
|
const cache = isCachedPayloadRoute(url) ? "default" : "force-cache";
|
|
return await _importPayload(payloadURL, cache) || null;
|
|
}
|
|
return null;
|
|
}
|
|
let linkRelType;
|
|
function detectLinkRelType() {
|
|
if (import.meta.server) {
|
|
return "preload";
|
|
}
|
|
if (linkRelType) {
|
|
return linkRelType;
|
|
}
|
|
const relList = document.createElement("link").relList;
|
|
linkRelType = relList && relList.supports && relList.supports("prefetch") ? "prefetch" : "preload";
|
|
return linkRelType;
|
|
}
|
|
export function preloadPayload(url, opts = {}) {
|
|
const nuxtApp = useNuxtApp();
|
|
const promise = shouldLoadPayload(url).then(async (shouldPreload) => {
|
|
if (!shouldPreload) {
|
|
return;
|
|
}
|
|
const payloadURL = await _getPayloadURL(url, opts);
|
|
const link = renderJsonPayloads ? { rel: detectLinkRelType(), as: "fetch", crossorigin: "anonymous", href: payloadURL } : { rel: "modulepreload", crossorigin: "", href: payloadURL };
|
|
if (import.meta.server) {
|
|
nuxtApp.runWithContext(() => useHead({ link: [link] }));
|
|
} else {
|
|
const linkEl = document.createElement("link");
|
|
for (const key of Object.keys(link)) {
|
|
linkEl[key === "crossorigin" ? "crossOrigin" : key] = link[key];
|
|
}
|
|
document.head.appendChild(linkEl);
|
|
return new Promise((resolve, reject) => {
|
|
const cleanup = () => {
|
|
linkEl.removeEventListener("load", onLoad);
|
|
linkEl.removeEventListener("error", onError);
|
|
};
|
|
const onLoad = () => {
|
|
cleanup();
|
|
resolve();
|
|
};
|
|
const onError = () => {
|
|
cleanup();
|
|
reject();
|
|
};
|
|
linkEl.addEventListener("load", onLoad);
|
|
linkEl.addEventListener("error", onError);
|
|
});
|
|
}
|
|
});
|
|
if (import.meta.server) {
|
|
onServerPrefetch(() => promise);
|
|
}
|
|
return promise;
|
|
}
|
|
const filename = renderJsonPayloads ? "_payload.json" : "_payload.js";
|
|
const payloadBuildIdParam = "_b";
|
|
async function _getPayloadURL(url, opts = {}) {
|
|
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 || import.meta.dev ? Date.now() : config.app.buildId);
|
|
const cdnURL = config.app.cdnURL;
|
|
const baseOrCdnURL = cdnURL && await isPrerendered(url) ? cdnURL : config.app.baseURL;
|
|
const payloadURL = joinURL(baseOrCdnURL, u.pathname, filename);
|
|
if (!isCachedPayloadRoute(url)) {
|
|
u.search = "";
|
|
}
|
|
if (hash) {
|
|
u.searchParams.set(payloadBuildIdParam, String(hash));
|
|
}
|
|
return payloadURL + u.search;
|
|
}
|
|
async function _importPayload(payloadURL, cache) {
|
|
if (import.meta.server || !payloadExtraction) {
|
|
return null;
|
|
}
|
|
try {
|
|
if (renderJsonPayloads) {
|
|
const res = await fetch(payloadURL, import.meta.dev ? {} : { cache });
|
|
if (!res.ok) {
|
|
if (import.meta.dev) {
|
|
console.warn(`[nuxt] Cannot load payload ${payloadURL}: ${res.status} ${res.statusText}`);
|
|
}
|
|
return null;
|
|
}
|
|
return await parsePayload(await res.text());
|
|
} else {
|
|
return await import(
|
|
/* webpackIgnore: true */
|
|
/* @vite-ignore */
|
|
payloadURL
|
|
).then((r) => r.default || r);
|
|
}
|
|
} catch (err) {
|
|
console.warn("[nuxt] Cannot load payload ", payloadURL, err);
|
|
}
|
|
return null;
|
|
}
|
|
function _shouldLoadPrerenderedPayload(rules) {
|
|
if (rules.redirect) {
|
|
return false;
|
|
}
|
|
if (rules.prerender) {
|
|
return true;
|
|
}
|
|
}
|
|
function _getRoutePath(url) {
|
|
return new URL(url, "http://localhost").pathname;
|
|
}
|
|
export function isCachedPayloadRoute(url) {
|
|
return !!getRouteRules({ path: _getRoutePath(url) }).payload;
|
|
}
|
|
async function _isPrerenderedInManifest(url) {
|
|
if (!appManifest) {
|
|
return false;
|
|
}
|
|
url = _getRoutePath(url);
|
|
url = url === "/" ? url : url.replace(/\/$/, "");
|
|
try {
|
|
const manifest = await getAppManifest();
|
|
return manifest.prerendered.includes(url);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
export async function shouldLoadPayload(url = useRoute().path) {
|
|
const rules = getRouteRules({ path: _getRoutePath(url) });
|
|
if (rules.ssr === false) {
|
|
return false;
|
|
}
|
|
const res = _shouldLoadPrerenderedPayload(rules);
|
|
if (res !== void 0) {
|
|
return res;
|
|
}
|
|
if (rules.payload) {
|
|
return true;
|
|
}
|
|
const prerendered = await _isPrerenderedInManifest(url);
|
|
return prerendered;
|
|
}
|
|
export async function isPrerendered(url = useRoute().path) {
|
|
const res = _shouldLoadPrerenderedPayload(getRouteRules({ path: _getRoutePath(url) }));
|
|
if (res !== void 0) {
|
|
return res;
|
|
}
|
|
const prerendered = await _isPrerenderedInManifest(url);
|
|
return prerendered;
|
|
}
|
|
let payloadCache = null;
|
|
export async function getNuxtClientPayload() {
|
|
if (import.meta.server) {
|
|
return null;
|
|
}
|
|
if (payloadCache) {
|
|
return payloadCache;
|
|
}
|
|
const el = multiApp ? document.querySelector(`[data-nuxt-data="${appId}"]`) : document.getElementById("__NUXT_DATA__");
|
|
if (!el) {
|
|
return {};
|
|
}
|
|
const inlineData = await parsePayload(el.textContent || "");
|
|
const externalData = el.dataset.src ? await _importPayload(el.dataset.src, inlineData.prerenderedAt ? "force-cache" : "no-cache") : void 0;
|
|
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) {
|
|
return await parse(payload, useNuxtApp()._payloadRevivers);
|
|
}
|
|
export function definePayloadReducer(name, reduce) {
|
|
if (import.meta.server) {
|
|
useNuxtApp().ssrContext["~payloadReducers"][name] = reduce;
|
|
}
|
|
}
|
|
export function definePayloadReviver(name, revive) {
|
|
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;
|
|
}
|
|
}
|