@nuxt/scripts
Version:
Load third-party scripts with better performance, privacy and DX in Nuxt Apps.
126 lines (125 loc) • 3.95 kB
JavaScript
import { defu } from "defu";
import { useScript as _useScript } from "@unhead/vue/scripts";
import { reactive } from "vue";
import { onNuxtReady, useNuxtApp, useRuntimeConfig, injectHead } from "nuxt/app";
import { logger } from "../logger.js";
import { resolveTrigger } from "#build/nuxt-scripts-trigger-resolver";
function useNuxtScriptRuntimeConfig() {
return useRuntimeConfig().public["nuxt-scripts"];
}
export function resolveScriptKey(input) {
return input.key || input.src || (typeof input.innerHTML === "string" ? input.innerHTML : "");
}
export function useScript(input, options) {
input = typeof input === "string" ? { src: input } : input;
options = defu(options, useNuxtScriptRuntimeConfig()?.defaultScriptOptions);
if (import.meta.dev && options.bundle === "unsupported") {
console.warn("[Nuxt Scripts] Bundling is not supported for dynamic script sources. Static URLs are required for bundling.");
options.bundle = false;
}
if (options.trigger && typeof options.trigger === "object" && !("then" in options.trigger)) {
const resolved = resolveTrigger(options.trigger);
if (resolved) {
options.trigger = resolved;
}
}
const id = String(resolveScriptKey(input));
const nuxtApp = useNuxtApp();
options.head = options.head || injectHead();
if (!options.head) {
throw new Error("useScript() has been called without Nuxt context.");
}
nuxtApp.$scripts = nuxtApp.$scripts || reactive({});
const exists = !!nuxtApp.$scripts?.[id];
const err = options._validate?.();
if (import.meta.dev && import.meta.client && err) {
options.trigger = new Promise(() => {
});
if (!exists) {
let out = `Skipping script \`${id}\` due to invalid options:
`;
for (const e of err.issues) {
out += ` ${e.message}
`;
}
logger.info(out);
}
} else if (options.trigger === "onNuxtReady" || options.trigger === "client") {
if (!options.warmupStrategy) {
options.warmupStrategy = "preload";
}
if (options.trigger === "onNuxtReady") {
options.trigger = onNuxtReady;
}
}
const instance = _useScript(input, options);
const _remove = instance.remove;
instance.remove = () => {
nuxtApp.$scripts[id] = void 0;
return _remove();
};
const _load = instance.load;
instance.load = async () => {
if (err) {
return Promise.reject(err);
}
return _load();
};
nuxtApp.$scripts[id] = instance;
if (import.meta.dev && import.meta.client) {
let syncScripts = function() {
nuxtApp._scripts[instance.id] = payload;
nuxtApp.hooks.callHook("scripts:updated", { scripts: nuxtApp._scripts });
};
if (exists) {
return instance;
}
const payload = {
...options.devtools,
src: input.src,
$script: null,
events: []
};
nuxtApp._scripts = nuxtApp._scripts || {};
if (!nuxtApp._scripts[instance.id]) {
options.head.hooks.hook("script:updated", (ctx) => {
if (ctx.script.id !== instance.id)
return;
payload.events.push({
type: "status",
status: ctx.script.status,
at: Date.now()
});
payload.$script = instance;
syncScripts();
});
options.head.hooks.hook("script:instance-fn", (ctx) => {
if (ctx.script.id !== instance.id || String(ctx.fn).startsWith("__v_"))
return;
payload.events.push({
type: "fn-call",
fn: ctx.fn,
at: Date.now()
});
syncScripts();
});
payload.$script = instance;
if (err) {
payload.events.push({
type: "status",
status: "validation-failed",
args: err,
at: Date.now()
});
}
payload.events.push({
type: "status",
status: "awaitingLoad",
trigger: options?.trigger,
at: Date.now()
});
syncScripts();
}
}
return instance;
}