diff --git a/packages/script/src/module.ts b/packages/script/src/module.ts index 1d565209..e32da9c9 100644 --- a/packages/script/src/module.ts +++ b/packages/script/src/module.ts @@ -563,6 +563,14 @@ export default defineNuxtModule({ : undefined, } as any + // Build-time constant: `__NUXT_SCRIPTS_DEBUG__` is replaced inline by the + // bundler, so debug branches DCE away in production when `debug: false`. + const debugConst = JSON.stringify(!!config.debug) + nuxt.options.vite ||= {} + nuxt.options.vite.define = { ...nuxt.options.vite.define, __NUXT_SCRIPTS_DEBUG__: debugConst } + nuxt.options.nitro ||= {} + nuxt.options.nitro.replace = { ...nuxt.options.nitro.replace, __NUXT_SCRIPTS_DEBUG__: debugConst } + // Register proxy handler unconditionally. The handler rejects unknown domains // at runtime, so it's safe to register even when no scripts use proxy. const scriptsBase = config.prefix || '/_scripts' diff --git a/packages/script/src/runtime/composables/useScript.ts b/packages/script/src/runtime/composables/useScript.ts index d6751138..2f5e5cb2 100644 --- a/packages/script/src/runtime/composables/useScript.ts +++ b/packages/script/src/runtime/composables/useScript.ts @@ -6,6 +6,7 @@ import { injectHead, onNuxtReady, useHead, useNuxtApp, useRuntimeConfig } from ' import { markRaw, ref } from 'vue' // @ts-expect-error virtual template import { resolveTrigger } from '#build/nuxt-scripts-trigger-resolver' +import { debugEnabled } from '../debug' import { logger } from '../logger' type NuxtScriptsApp = ReturnType & { @@ -293,6 +294,58 @@ export function useScript = Record { + if (entry.script.id !== instance.id) + return + const status = entry.script.status + const elapsed = Math.round(performance.now() - t0) + if (status === 'loading') + tLoadStart = performance.now() + const payload: Record = { ...ctx, status, elapsedMs: elapsed } + if (status === 'loaded' && tLoadStart) + payload.loadMs = Math.round(performance.now() - tLoadStart) + const fn = status === 'error' ? log.warn : log.debug + fn(`status: ${status}`, payload) + }) + const _origLoad = instance.load + instance.load = () => { + log.debug('load() called', ctx) + return _origLoad() + } + const _origRemove = instance.remove + instance.remove = () => { + log.debug('remove() called', ctx) + return _origRemove() + } + const _origReload = instance.reload + instance.reload = async () => { + log.debug('reload() called', ctx) + return _origReload() + } + } + // used for devtools integration if (import.meta.dev && import.meta.client) { if (exists) { diff --git a/packages/script/src/runtime/debug.ts b/packages/script/src/runtime/debug.ts new file mode 100644 index 00000000..6c74abf8 --- /dev/null +++ b/packages/script/src/runtime/debug.ts @@ -0,0 +1,4 @@ +declare const __NUXT_SCRIPTS_DEBUG__: boolean + +export const debugEnabled: boolean + = typeof __NUXT_SCRIPTS_DEBUG__ !== 'undefined' && __NUXT_SCRIPTS_DEBUG__ diff --git a/packages/script/src/runtime/logger.ts b/packages/script/src/runtime/logger.ts index e7e56c5a..413357ed 100644 --- a/packages/script/src/runtime/logger.ts +++ b/packages/script/src/runtime/logger.ts @@ -1,6 +1,10 @@ import { createConsola } from 'consola' +import { debugEnabled } from './debug' export const logger = createConsola({ + // 4 = debug, 3 = info (consola defaults). Lift the threshold so `logger.debug` + // fires when debug is opted-in at build time or in dev. + level: debugEnabled ? 4 : 3, defaults: { tag: 'nuxt-scripts', }, diff --git a/packages/script/src/runtime/types.ts b/packages/script/src/runtime/types.ts index bbbaf1af..8419fc27 100644 --- a/packages/script/src/runtime/types.ts +++ b/packages/script/src/runtime/types.ts @@ -168,6 +168,13 @@ export type NuxtUseScriptOptions = {}> = * @internal */ registryMeta?: Record + /** + * Source location (file:line:col) the script was registered from, captured + * via dev-only stack-trace parsing in `useRegistryScript`. Surfaced in + * debug logs and Nuxt DevTools. + * @internal + */ + loadedFrom?: string /** * Known third-party domains this script communicates with. * @internal