@nuxt/scripts
Version:
Load third-party scripts with better performance, privacy and DX in Nuxt Apps.
89 lines (88 loc) • 3.59 kB
JavaScript
import { defu } from "defu";
import { useRuntimeConfig } from "nuxt/app";
import { useScript } from "./composables/useScript.js";
import { parse } from "#nuxt-scripts-validator";
import { parseURL, withQuery, parseQuery } from "ufo";
function validateScriptInputSchema(key, schema, options) {
if (import.meta.dev) {
try {
parse(schema, options);
} catch (_e) {
return _e;
}
}
return null;
}
export function scriptRuntimeConfig(key) {
return (useRuntimeConfig().public.scripts || {})[key];
}
export function useRegistryScript(registryKey, optionsFn, _userOptions) {
const scriptConfig = scriptRuntimeConfig(registryKey);
const userOptions = Object.assign(_userOptions || {}, typeof scriptConfig === "object" ? scriptConfig : {});
const options = optionsFn(userOptions, { scriptInput: userOptions.scriptInput });
let finalScriptInput = options.scriptInput;
const userSrc = userOptions.scriptInput?.src;
const optionsSrc = options.scriptInput?.src;
if (userSrc && optionsSrc && typeof optionsSrc === "string" && typeof userSrc === "string") {
const defaultUrl = parseURL(optionsSrc);
const customUrl = parseURL(userSrc);
const defaultQuery = parseQuery(defaultUrl.search || "");
const customQuery = parseQuery(customUrl.search || "");
const mergedQuery = { ...defaultQuery, ...customQuery };
const baseUrl = customUrl.href?.split("?")[0] || userSrc;
finalScriptInput = {
...options.scriptInput || {},
src: withQuery(baseUrl, mergedQuery)
};
}
const scriptInput = defu(finalScriptInput, userOptions.scriptInput, { key: registryKey });
const scriptOptions = Object.assign(userOptions?.scriptOptions || {}, options.scriptOptions || {});
if (import.meta.dev) {
const error = new Error("Stack trace for component location");
const stack = error.stack?.split("\n");
const callerLine = stack?.find(
(line) => line.includes(".vue") && !line.includes("useRegistryScript") && !line.includes("node_modules")
);
let loadedFrom = "unknown";
if (callerLine) {
const urlMatch = callerLine.match(/https?:\/\/[^/]+\/_nuxt\/(.+\.vue)(?:\?[^)]*)?:(\d+):(\d+)/) || callerLine.match(/\(https?:\/\/[^/]+\/_nuxt\/(.+\.vue)(?:\?[^)]*)?:(\d+):(\d+)\)/);
if (urlMatch) {
const [, filePath, line, column] = urlMatch;
loadedFrom = `./${filePath}:${line}:${column}`;
} else {
const vueMatch = callerLine.match(/([^/\s]+\.vue):(\d+):(\d+)/);
if (vueMatch) {
const [, fileName, line, column] = vueMatch;
loadedFrom = `./${fileName}:${line}:${column}`;
} else {
loadedFrom = callerLine.trim().replace(/^\s*at\s+/, "");
}
}
}
scriptOptions.devtools = defu(scriptOptions.devtools, { registryKey, loadedFrom });
if (options.schema) {
const registryMeta = {};
for (const k in options.schema.entries) {
if (options.schema.entries[k].type !== "optional") {
registryMeta[k] = String(userOptions[k]);
}
}
scriptOptions.devtools.registryMeta = registryMeta;
}
}
const init = scriptOptions.beforeInit;
if (import.meta.dev) {
scriptOptions._validate = () => {
if (!userOptions.scriptInput?.src && !scriptOptions.skipValidation && options.schema) {
return validateScriptInputSchema(registryKey, options.schema, userOptions);
}
};
}
scriptOptions.beforeInit = () => {
init?.();
if (import.meta.client) {
options.clientInit?.();
}
};
return useScript(scriptInput, scriptOptions);
}