UNPKG

@nuxt/scripts

Version:

Load third-party scripts with better performance, privacy and DX in Nuxt Apps.

89 lines (88 loc) 3.59 kB
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); }