@nuxt/scripts 1.0.0-beta.19 → 1.0.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/client/200.html +1 -1
  2. package/dist/client/404.html +1 -1
  3. package/dist/client/_nuxt/{BAII0Gd6.js → 9LJPrOyI.js} +1 -1
  4. package/dist/client/_nuxt/DFEfk2pB.js +162 -0
  5. package/dist/client/_nuxt/{BFIM6xmj.js → DMlY-BNa.js} +1 -1
  6. package/dist/client/_nuxt/{XsmoCkZQ.js → __ZZTkMj.js} +1 -1
  7. package/dist/client/_nuxt/builds/latest.json +1 -1
  8. package/dist/client/_nuxt/builds/meta/8212d4fa-7985-421b-815a-03a886e667d4.json +1 -0
  9. package/dist/client/_nuxt/entry.CACgbLJl.css +1 -0
  10. package/dist/client/_nuxt/error-404.CHeaW3dp.css +1 -0
  11. package/dist/client/_nuxt/error-500.DvOvWme_.css +1 -0
  12. package/dist/client/index.html +1 -1
  13. package/dist/module.d.mts +7 -5
  14. package/dist/module.d.ts +7 -5
  15. package/dist/module.json +1 -1
  16. package/dist/module.mjs +389 -197
  17. package/dist/registry.mjs +5 -2
  18. package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +1 -1
  19. package/dist/runtime/components/ScriptYouTubePlayer.vue +2 -1
  20. package/dist/runtime/registry/clarity.d.ts +8 -8
  21. package/dist/runtime/registry/crisp.d.ts +7 -7
  22. package/dist/runtime/registry/databuddy-analytics.js +18 -0
  23. package/dist/runtime/registry/fathom-analytics.d.ts +4 -3
  24. package/dist/runtime/registry/fathom-analytics.js +0 -1
  25. package/dist/runtime/registry/google-analytics.d.ts +1 -1
  26. package/dist/runtime/registry/google-recaptcha.js +1 -1
  27. package/dist/runtime/registry/google-tag-manager.js +1 -1
  28. package/dist/runtime/registry/hotjar.d.ts +1 -1
  29. package/dist/runtime/registry/instagram-embed.js +2 -1
  30. package/dist/runtime/registry/intercom.d.ts +1 -1
  31. package/dist/runtime/registry/meta-pixel.d.ts +1 -1
  32. package/dist/runtime/registry/reddit-pixel.d.ts +2 -1
  33. package/dist/runtime/registry/rybbit-analytics.js +1 -1
  34. package/dist/runtime/registry/schemas.d.ts +6 -0
  35. package/dist/runtime/registry/schemas.js +7 -1
  36. package/dist/runtime/registry/snapchat-pixel.d.ts +1 -1
  37. package/dist/runtime/registry/tiktok-pixel.d.ts +1 -1
  38. package/dist/runtime/registry/x-pixel.d.ts +1 -1
  39. package/dist/runtime/server/gravatar-proxy.js +1 -1
  40. package/dist/runtime/server/instagram-embed-asset.js +2 -1
  41. package/dist/runtime/server/instagram-embed-image.js +2 -1
  42. package/dist/runtime/server/instagram-embed.js +21 -12
  43. package/dist/runtime/server/proxy-handler.js +37 -27
  44. package/dist/runtime/server/utils/privacy.js +14 -7
  45. package/dist/runtime/server/x-embed.js +3 -2
  46. package/dist/runtime/utils.js +7 -3
  47. package/dist/shared/{scripts.Bg4pl9Yo.mjs → scripts.Crpn87WB.mjs} +15 -7
  48. package/dist/stats.mjs +7 -4
  49. package/dist/types-source.mjs +29 -18
  50. package/package.json +14 -14
  51. package/dist/client/_nuxt/8nZpL1GZ.js +0 -162
  52. package/dist/client/_nuxt/builds/meta/4f48c83d-e40d-436a-afd0-3b8e6ac6f303.json +0 -1
  53. package/dist/client/_nuxt/entry.D45OuV0w.css +0 -1
  54. package/dist/client/_nuxt/error-404.Cqp3ffuH.css +0 -1
  55. package/dist/client/_nuxt/error-500.B9hH8BAi.css +0 -1
package/dist/module.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
- import { useNuxt, addDevServerHandler, extendRouteRules, tryUseNuxt, extendViteConfig, logger as logger$1, useLogger, addTypeTemplate, defineNuxtModule, createResolver, hasNuxtModule, addImports, addComponentsDir, addTemplate, addPluginTemplate, addServerHandler, addBuildPlugin } from '@nuxt/kit';
2
+ import { useNuxt, addDevServerHandler, extendRouteRules, tryUseNuxt, extendViteConfig, useLogger, addPluginTemplate, addServerHandler, logger as logger$1, addTypeTemplate, defineNuxtModule, createResolver, hasNuxtModule, addImports, addComponentsDir, addTemplate, addBuildPlugin } from '@nuxt/kit';
3
3
  import { defu } from 'defu';
4
4
  import { join, resolve, relative } from 'pathe';
5
5
  import { resolvePackageJSON, readPackageJSON } from 'pkg-types';
@@ -10,7 +10,7 @@ import { createStorage } from 'unstorage';
10
10
  import fsDriver from 'unstorage/drivers/fs-lite';
11
11
  import { addCustomTab } from '@nuxt/devtools-kit';
12
12
  import { isCI, provider } from 'std-env';
13
- import { parseAndWalk } from 'oxc-walker';
13
+ import { parseAndWalk, ScopeTracker, walk, ScopeTrackerFunctionParam, ScopeTrackerVariable } from 'oxc-walker';
14
14
  import { createUnplugin } from 'unplugin';
15
15
  import { pathToFileURL } from 'node:url';
16
16
  import { createHash } from 'node:crypto';
@@ -18,7 +18,7 @@ import fsp from 'node:fs/promises';
18
18
  import { colors } from 'consola/utils';
19
19
  import MagicString from 'magic-string';
20
20
  import { hash } from 'ohash';
21
- import { a as getProxyConfig, g as getAllProxyConfigs, r as routesToInterceptRules } from './shared/scripts.Bg4pl9Yo.mjs';
21
+ import { g as getAllProxyConfigs, r as routesToInterceptRules, a as getProxyConfig } from './shared/scripts.Crpn87WB.mjs';
22
22
  import { registry } from './registry.mjs';
23
23
 
24
24
  const renderedScript = /* @__PURE__ */ new Map();
@@ -121,6 +121,212 @@ async function setupDevToolsUI(options, resolve, nuxt = useNuxt()) {
121
121
  });
122
122
  }
123
123
 
124
+ const logger = useLogger("@nuxt/scripts");
125
+
126
+ const AUTO_INJECT_DEFS = [
127
+ {
128
+ registryKey: "posthog",
129
+ configField: "apiHost",
130
+ computeValue: (collectPrefix, config) => {
131
+ const region = config.region || "us";
132
+ return region === "eu" ? `${collectPrefix}/ph-eu` : `${collectPrefix}/ph`;
133
+ }
134
+ },
135
+ {
136
+ registryKey: "plausibleAnalytics",
137
+ configField: "endpoint",
138
+ computeValue: (collectPrefix) => `${collectPrefix}/plausible/api/event`
139
+ },
140
+ {
141
+ registryKey: "umamiAnalytics",
142
+ configField: "hostUrl",
143
+ computeValue: (collectPrefix) => `${collectPrefix}/umami`
144
+ },
145
+ {
146
+ registryKey: "rybbitAnalytics",
147
+ configField: "analyticsHost",
148
+ computeValue: (collectPrefix) => `${collectPrefix}/rybbit/api`
149
+ },
150
+ {
151
+ registryKey: "databuddyAnalytics",
152
+ configField: "apiUrl",
153
+ computeValue: (collectPrefix) => `${collectPrefix}/databuddy-api`
154
+ }
155
+ ];
156
+ function autoInjectProxyEndpoints(registry, runtimeConfig, collectPrefix) {
157
+ for (const def of AUTO_INJECT_DEFS) {
158
+ const entry = registry[def.registryKey];
159
+ if (!entry || typeof entry !== "object")
160
+ continue;
161
+ const config = Array.isArray(entry) ? entry[0] : entry;
162
+ if (!config || config[def.configField])
163
+ continue;
164
+ config[def.configField] = def.computeValue(collectPrefix, config);
165
+ const rtScripts = runtimeConfig.public?.scripts;
166
+ const rtEntry = rtScripts?.[def.registryKey];
167
+ if (rtEntry && typeof rtEntry === "object") {
168
+ const rtConfig = Array.isArray(rtEntry) ? rtEntry[0] : rtEntry;
169
+ if (rtConfig)
170
+ rtConfig[def.configField] = config[def.configField];
171
+ }
172
+ }
173
+ }
174
+
175
+ function generateInterceptPluginContents(interceptRules) {
176
+ const rulesJson = JSON.stringify(interceptRules);
177
+ return `export default defineNuxtPlugin({
178
+ name: 'nuxt-scripts:intercept',
179
+ enforce: 'pre',
180
+ setup() {
181
+ const rules = ${rulesJson};
182
+ const origBeacon = typeof navigator !== 'undefined' && navigator.sendBeacon
183
+ ? navigator.sendBeacon.bind(navigator)
184
+ : () => false;
185
+ const origFetch = globalThis.fetch.bind(globalThis);
186
+
187
+ function rewriteUrl(url) {
188
+ try {
189
+ const parsed = new URL(url, location.origin);
190
+ for (const rule of rules) {
191
+ if (parsed.hostname === rule.pattern || parsed.hostname.endsWith('.' + rule.pattern)) {
192
+ if (rule.pathPrefix && !parsed.pathname.startsWith(rule.pathPrefix)) continue;
193
+ const path = rule.pathPrefix ? parsed.pathname.slice(rule.pathPrefix.length) : parsed.pathname;
194
+ return location.origin + rule.target + (path.startsWith('/') ? '' : '/') + path + parsed.search;
195
+ }
196
+ }
197
+ } catch {}
198
+ return url;
199
+ }
200
+
201
+ // XMLHttpRequest wrapper \u2014 intercepts .open() to rewrite URL
202
+ const OrigXHR = XMLHttpRequest;
203
+ class ProxiedXHR extends OrigXHR {
204
+ open() {
205
+ const args = Array.from(arguments);
206
+ if (typeof args[1] === 'string') args[1] = rewriteUrl(args[1]);
207
+ return super.open.apply(this, args);
208
+ }
209
+ }
210
+ // Image wrapper \u2014 intercepts .src setter to rewrite URL
211
+ const OrigImage = Image;
212
+ const origSrcDesc = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'src');
213
+ function ProxiedImage(w, h) {
214
+ const img = arguments.length === 2 ? new OrigImage(w, h)
215
+ : arguments.length === 1 ? new OrigImage(w) : new OrigImage();
216
+ if (origSrcDesc && origSrcDesc.set) {
217
+ Object.defineProperty(img, 'src', {
218
+ get() { return origSrcDesc.get.call(this); },
219
+ set(v) { origSrcDesc.set.call(this, typeof v === 'string' ? rewriteUrl(v) : v); },
220
+ configurable: true,
221
+ });
222
+ }
223
+ return img;
224
+ }
225
+
226
+ globalThis.__nuxtScripts = {
227
+ sendBeacon: (url, data) => origBeacon(rewriteUrl(url), data),
228
+ fetch: (url, opts) => origFetch(typeof url === 'string' ? rewriteUrl(url) : url, opts),
229
+ XMLHttpRequest: ProxiedXHR,
230
+ Image: ProxiedImage,
231
+ };
232
+ },
233
+ })
234
+ `;
235
+ }
236
+
237
+ function resolveFirstPartyConfig(config) {
238
+ const enabled = !!config.firstParty;
239
+ const prefix = typeof config.firstParty === "object" ? config.firstParty.prefix : void 0;
240
+ const collectPrefix = typeof config.firstParty === "object" ? config.firstParty.collectPrefix || "/_proxy" : "/_proxy";
241
+ const privacy = typeof config.firstParty === "object" ? config.firstParty.privacy : void 0;
242
+ const assetsPrefix = prefix || config.assets?.prefix || "/_scripts";
243
+ return { enabled, prefix, collectPrefix, privacy, assetsPrefix };
244
+ }
245
+ async function setupFirstPartyHandlers(firstParty, resolvePath) {
246
+ const interceptRules = [];
247
+ addPluginTemplate({
248
+ filename: "nuxt-scripts-intercept.client.mjs",
249
+ getContents() {
250
+ return generateInterceptPluginContents(interceptRules);
251
+ }
252
+ });
253
+ const proxyHandlerPath = await resolvePath("./runtime/server/proxy-handler");
254
+ logger.debug("[nuxt-scripts] Registering proxy handler:", `${firstParty.collectPrefix}/**`, "->", proxyHandlerPath);
255
+ addServerHandler({
256
+ route: `${firstParty.collectPrefix}/**`,
257
+ handler: proxyHandlerPath
258
+ });
259
+ return interceptRules;
260
+ }
261
+ function finalizeFirstParty(opts) {
262
+ const { firstParty, interceptRules, registryScriptsWithImport, nuxtOptions } = opts;
263
+ const proxyConfigs = getAllProxyConfigs(firstParty.collectPrefix);
264
+ const registryKeys = Object.keys(opts.registry || {});
265
+ const neededRoutes = {};
266
+ const routePrivacyOverrides = {};
267
+ const unsupportedScripts = [];
268
+ for (const key of registryKeys) {
269
+ const script = registryScriptsWithImport.find((s) => s.import.name.toLowerCase() === `usescript${key.toLowerCase()}`);
270
+ const proxyKey = script?.proxy || void 0;
271
+ if (proxyKey) {
272
+ const proxyConfig = proxyConfigs[proxyKey];
273
+ if (proxyConfig?.routes) {
274
+ Object.assign(neededRoutes, proxyConfig.routes);
275
+ for (const routePath of Object.keys(proxyConfig.routes)) {
276
+ routePrivacyOverrides[routePath] = proxyConfig.privacy;
277
+ }
278
+ } else {
279
+ unsupportedScripts.push(key);
280
+ }
281
+ }
282
+ }
283
+ if (opts.registry) {
284
+ autoInjectProxyEndpoints(opts.registry, nuxtOptions.runtimeConfig, firstParty.collectPrefix);
285
+ }
286
+ if (unsupportedScripts.length && nuxtOptions.dev) {
287
+ logger.warn(
288
+ `First-party mode is enabled but these scripts don't support it yet: ${unsupportedScripts.join(", ")}.
289
+ They will load directly from third-party servers. Request support at https://github.com/nuxt/scripts/issues`
290
+ );
291
+ }
292
+ interceptRules.push(...routesToInterceptRules(neededRoutes));
293
+ const flatRoutes = {};
294
+ for (const [path, config] of Object.entries(neededRoutes)) {
295
+ flatRoutes[path] = config.proxy;
296
+ }
297
+ nuxtOptions.runtimeConfig["nuxt-scripts-proxy"] = {
298
+ routes: flatRoutes,
299
+ privacy: firstParty.privacy,
300
+ routePrivacy: routePrivacyOverrides
301
+ };
302
+ if (Object.keys(neededRoutes).length && nuxtOptions.dev) {
303
+ const routeCount = Object.keys(neededRoutes).length;
304
+ const scriptsCount = registryKeys.length;
305
+ const privacyLabel = firstParty.privacy === void 0 ? "per-script" : typeof firstParty.privacy === "boolean" ? firstParty.privacy ? "anonymize" : "passthrough" : "custom";
306
+ logger.success(`First-party mode enabled for ${scriptsCount} script(s), ${routeCount} proxy route(s) configured (privacy: ${privacyLabel})`);
307
+ if (logger.level >= 4) {
308
+ for (const [path, config] of Object.entries(neededRoutes)) {
309
+ logger.debug(` ${path} \u2192 ${config.proxy}`);
310
+ }
311
+ }
312
+ }
313
+ const staticPresets = ["static", "github-pages", "cloudflare-pages-static"];
314
+ const preset = process.env.NITRO_PRESET || "";
315
+ if (staticPresets.includes(preset)) {
316
+ logger.warn(
317
+ `First-party collection endpoints require a server runtime (detected: ${preset || "static"}).
318
+ Scripts will be bundled, but collection requests will not be proxied.
319
+
320
+ Options:
321
+ 1. Configure platform rewrites (Vercel, Netlify, Cloudflare)
322
+ 2. Switch to server-rendered mode (ssr: true)
323
+ 3. Disable with firstParty: false
324
+
325
+ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
326
+ );
327
+ }
328
+ }
329
+
124
330
  const isStackblitz = provider === "stackblitz";
125
331
  async function promptToInstall(name, installCommand, options) {
126
332
  if (await resolvePackageJSON(name).catch(() => null))
@@ -161,8 +367,6 @@ function installNuxtModule(name, options) {
161
367
  }, { rootDir: nuxt.options.rootDir, searchPaths: nuxt.options.modulesDir, ...options });
162
368
  }
163
369
 
164
- const logger = useLogger("@nuxt/scripts");
165
-
166
370
  function isVue(id, opts = {}) {
167
371
  const { search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
168
372
  if (id.endsWith(".vue") && !search) {
@@ -184,19 +388,20 @@ function isVue(id, opts = {}) {
184
388
  }
185
389
  return true;
186
390
  }
187
- const JS_RE = /\.(?:[cm]?j|t)sx?$/;
391
+ const JS_RE$1 = /\.(?:[cm]?j|t)sx?$/;
188
392
  function isJS(id) {
189
393
  const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href));
190
- return JS_RE.test(pathname);
394
+ return JS_RE$1.test(pathname);
191
395
  }
192
396
 
397
+ const VUE_RE$1 = /\.vue/;
193
398
  function NuxtScriptsCheckScripts() {
194
399
  return createUnplugin(() => {
195
400
  return {
196
401
  name: "nuxt-scripts:check-scripts",
197
402
  transform: {
198
403
  filter: {
199
- id: /\.vue/
404
+ id: VUE_RE$1
200
405
  },
201
406
  handler(code, id) {
202
407
  if (!isVue(id, { type: ["script"] }))
@@ -245,6 +450,10 @@ function NuxtScriptsCheckScripts() {
245
450
  });
246
451
  }
247
452
 
453
+ const WORD_OR_DOLLAR_RE = /[\w$]/;
454
+ const GA_COLLECT_RE = /([\w$])?"https:\/\/"\+\(.*?\)\+"\.google-analytics\.com\/g\/collect"/g;
455
+ const GA_ANALYTICS_COLLECT_RE = /([\w$])?"https:\/\/"\+\(.*?\)\+"\.analytics\.google\.com\/g\/collect"/g;
456
+ const FATHOM_SELF_HOSTED_RE = /\.src\.indexOf\("cdn\.usefathom\.com"\)\s*<\s*0/;
248
457
  function isPropertyKeyAST(parent, ctx) {
249
458
  return parent?.type === "Property" && ctx.key === "key" || parent?.type === "SwitchCase" && ctx.key === "test";
250
459
  }
@@ -298,51 +507,151 @@ function matchAndRewrite(value, rewrites) {
298
507
  }
299
508
  return null;
300
509
  }
510
+ const WINDOW_GLOBALS = /* @__PURE__ */ new Set(["window", "self", "globalThis"]);
511
+ const NAVIGATOR_GLOBALS = /* @__PURE__ */ new Set(["navigator"]);
512
+ function resolveToGlobal(name, scopeTracker, depth = 0) {
513
+ if (depth > 10)
514
+ return null;
515
+ const decl = scopeTracker.getDeclaration(name);
516
+ if (!decl)
517
+ return name;
518
+ if (decl instanceof ScopeTrackerFunctionParam)
519
+ return null;
520
+ if (decl instanceof ScopeTrackerVariable) {
521
+ const declarators = decl.variableNode.declarations;
522
+ if (!declarators)
523
+ return null;
524
+ for (const declarator of declarators) {
525
+ const id = declarator.id;
526
+ if (!id || id.name !== name)
527
+ continue;
528
+ const init = declarator.init;
529
+ if (!init)
530
+ return null;
531
+ if (init.type === "Identifier")
532
+ return resolveToGlobal(init.name, scopeTracker, depth + 1);
533
+ if (init.type === "MemberExpression" && !init.computed && init.object?.type === "Identifier" && init.property?.type === "Identifier") {
534
+ const objGlobal = resolveToGlobal(init.object.name, scopeTracker, depth + 1);
535
+ if (!objGlobal)
536
+ return null;
537
+ if (WINDOW_GLOBALS.has(objGlobal) || objGlobal === "document")
538
+ return init.property.name;
539
+ return null;
540
+ }
541
+ return null;
542
+ }
543
+ }
544
+ return null;
545
+ }
546
+ function resolveCalleeTarget(callee, scopeTracker) {
547
+ if (callee?.type !== "MemberExpression")
548
+ return null;
549
+ const propName = callee.computed ? callee.property?.type === "Literal" && typeof callee.property.value === "string" ? callee.property.value : null : callee.property?.name;
550
+ if (!propName)
551
+ return null;
552
+ const obj = callee.object;
553
+ if (!obj || obj.type !== "Identifier")
554
+ return null;
555
+ const resolved = resolveToGlobal(obj.name, scopeTracker);
556
+ if (!resolved)
557
+ return null;
558
+ if (propName === "fetch" && WINDOW_GLOBALS.has(resolved))
559
+ return "fetch";
560
+ if (propName === "sendBeacon" && (NAVIGATOR_GLOBALS.has(resolved) || WINDOW_GLOBALS.has(resolved)))
561
+ return "sendBeacon";
562
+ if (propName === "sendBeacon" && resolved === "navigator")
563
+ return "sendBeacon";
564
+ if (propName === "XMLHttpRequest" && WINDOW_GLOBALS.has(resolved))
565
+ return "XMLHttpRequest";
566
+ if (propName === "Image" && WINDOW_GLOBALS.has(resolved))
567
+ return "Image";
568
+ return null;
569
+ }
301
570
  function rewriteScriptUrlsAST(content, filename, rewrites) {
302
571
  const s = new MagicString(content);
303
572
  function needsLeadingSpace(start) {
304
573
  const prev = content[start - 1];
305
- return prev && /[\w$]/.test(prev) ? " " : "";
574
+ return prev && WORD_OR_DOLLAR_RE.test(prev) ? " " : "";
306
575
  }
307
- parseAndWalk(content, filename, (node, parent, ctx) => {
308
- if (node.type === "Literal" && typeof node.value === "string") {
309
- const value = node.value;
310
- const rewritten = matchAndRewrite(value, rewrites);
311
- if (rewritten === null)
312
- return;
313
- const quote = content[node.start];
314
- if (isPropertyKeyAST(parent, ctx)) {
315
- s.overwrite(node.start, node.end, quote + rewritten + quote);
316
- } else {
317
- s.overwrite(node.start, node.end, `${needsLeadingSpace(node.start)}self.location.origin+${quote}${rewritten}${quote}`);
318
- }
319
- }
320
- if (node.type === "TemplateLiteral" && node.expressions?.length === 0) {
321
- const quasis = node.quasis;
322
- if (quasis?.length === 1) {
323
- const value = quasis[0].value?.cooked ?? quasis[0].value?.raw;
324
- if (typeof value !== "string")
325
- return;
576
+ const scopeTracker = new ScopeTracker({ preserveExitedScopes: true });
577
+ const { program } = parseAndWalk(content, filename, { scopeTracker });
578
+ scopeTracker.freeze();
579
+ walk(program, {
580
+ scopeTracker,
581
+ enter(node, parent, ctx) {
582
+ if (node.type === "Literal" && typeof node.value === "string") {
583
+ const value = node.value;
326
584
  const rewritten = matchAndRewrite(value, rewrites);
327
585
  if (rewritten === null)
328
586
  return;
587
+ const quote = content[node.start];
329
588
  if (isPropertyKeyAST(parent, ctx)) {
330
- s.overwrite(node.start, node.end, `\`${rewritten}\``);
589
+ s.overwrite(node.start, node.end, quote + rewritten + quote);
331
590
  } else {
332
- s.overwrite(node.start, node.end, `${needsLeadingSpace(node.start)}self.location.origin+\`${rewritten}\``);
591
+ s.overwrite(node.start, node.end, `${needsLeadingSpace(node.start)}self.location.origin+${quote}${rewritten}${quote}`);
333
592
  }
334
593
  }
335
- }
336
- if (node.type === "CallExpression") {
337
- const callee = node.callee;
338
- if (callee?.type === "MemberExpression" && !callee.computed && callee.object?.type === "Identifier" && callee.object.name === "navigator" && callee.property?.name === "sendBeacon") {
339
- s.overwrite(callee.start, callee.end, "__nuxtScripts.sendBeacon");
594
+ if (node.type === "TemplateLiteral" && node.expressions?.length === 0) {
595
+ const quasis = node.quasis;
596
+ if (quasis?.length === 1) {
597
+ const value = quasis[0].value?.cooked ?? quasis[0].value?.raw;
598
+ if (typeof value !== "string")
599
+ return;
600
+ const rewritten = matchAndRewrite(value, rewrites);
601
+ if (rewritten === null)
602
+ return;
603
+ if (isPropertyKeyAST(parent, ctx)) {
604
+ s.overwrite(node.start, node.end, `\`${rewritten}\``);
605
+ } else {
606
+ s.overwrite(node.start, node.end, `${needsLeadingSpace(node.start)}self.location.origin+\`${rewritten}\``);
607
+ }
608
+ }
340
609
  }
341
- if (callee?.type === "Identifier" && callee.name === "fetch") {
342
- s.overwrite(callee.start, callee.end, "__nuxtScripts.fetch");
610
+ if (node.type === "CallExpression") {
611
+ const callee = node.callee;
612
+ if (callee?.type === "Identifier" && callee.name === "fetch") {
613
+ if (!scopeTracker.getDeclaration("fetch"))
614
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.fetch");
615
+ return;
616
+ }
617
+ const target = resolveCalleeTarget(callee, scopeTracker);
618
+ if (target === "fetch" || target === "sendBeacon") {
619
+ s.overwrite(callee.start, callee.end, `__nuxtScripts.${target}`);
620
+ return;
621
+ }
622
+ if (callee?.type === "MemberExpression" && !callee.computed && callee.property?.name === "sendBeacon" && callee.object?.type === "Identifier") {
623
+ const resolved = resolveToGlobal(callee.object.name, scopeTracker);
624
+ if (resolved === null) {
625
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.sendBeacon");
626
+ }
627
+ }
343
628
  }
344
- if (callee?.type === "MemberExpression" && !callee.computed && callee.object?.type === "Identifier" && (callee.object.name === "window" || callee.object.name === "self" || callee.object.name === "globalThis") && callee.property?.name === "fetch") {
345
- s.overwrite(callee.start, callee.end, "__nuxtScripts.fetch");
629
+ if (node.type === "NewExpression") {
630
+ const callee = node.callee;
631
+ if (callee?.type === "Identifier" && callee.name === "XMLHttpRequest") {
632
+ if (!scopeTracker.getDeclaration("XMLHttpRequest"))
633
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.XMLHttpRequest");
634
+ return;
635
+ }
636
+ if (callee?.type === "Identifier" && callee.name === "Image") {
637
+ if (!scopeTracker.getDeclaration("Image"))
638
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.Image");
639
+ return;
640
+ }
641
+ const target = resolveCalleeTarget(callee, scopeTracker);
642
+ if (target === "XMLHttpRequest" || target === "Image") {
643
+ s.overwrite(callee.start, callee.end, `__nuxtScripts.${target}`);
644
+ return;
645
+ }
646
+ if (callee?.type === "MemberExpression" && callee.object?.type === "Identifier") {
647
+ const propName = callee.computed ? callee.property?.type === "Literal" && typeof callee.property.value === "string" ? callee.property.value : null : callee.property?.name;
648
+ if (propName === "XMLHttpRequest" || propName === "Image") {
649
+ const resolved = resolveToGlobal(callee.object.name, scopeTracker);
650
+ if (resolved === null) {
651
+ s.overwrite(callee.start, callee.end, `__nuxtScripts.${propName}`);
652
+ }
653
+ }
654
+ }
346
655
  }
347
656
  }
348
657
  });
@@ -350,18 +659,30 @@ function rewriteScriptUrlsAST(content, filename, rewrites) {
350
659
  const gaRewrite = rewrites.find((r) => r.from.includes("google-analytics.com/g/collect"));
351
660
  if (gaRewrite) {
352
661
  output = output.replace(
353
- /([\w$])?"https:\/\/"\+\(.*?\)\+"\.google-analytics\.com\/g\/collect"/g,
662
+ GA_COLLECT_RE,
354
663
  (_, prevChar) => `${prevChar ? `${prevChar} ` : ""}self.location.origin+"${gaRewrite.to}"`
355
664
  );
356
665
  output = output.replace(
357
- /([\w$])?"https:\/\/"\+\(.*?\)\+"\.analytics\.google\.com\/g\/collect"/g,
666
+ GA_ANALYTICS_COLLECT_RE,
358
667
  (_, prevChar) => `${prevChar ? `${prevChar} ` : ""}self.location.origin+"${gaRewrite.to}"`
359
668
  );
360
669
  }
670
+ if (rewrites.some((r) => r.from === "cdn.usefathom.com")) {
671
+ output = output.replace(
672
+ FATHOM_SELF_HOSTED_RE,
673
+ '.src.indexOf("cdn.usefathom.com")<-1'
674
+ );
675
+ }
361
676
  return output;
362
677
  }
363
678
 
364
679
  const SEVEN_DAYS_IN_MS = 7 * 24 * 60 * 60 * 1e3;
680
+ const PROTOCOL_RELATIVE_RE = /^\/\//;
681
+ const VUE_RE = /\.vue/;
682
+ const JS_RE = /\.[cm]?[jt]sx?$/;
683
+ const TEST_RE = /\.(?:test|spec)\./;
684
+ const UPPERCASE_RE = /^[A-Z]$/;
685
+ const USE_SCRIPT_RE = /^useScript/;
365
686
  function calculateIntegrity(content, algorithm = "sha384") {
366
687
  const hash = createHash(algorithm).update(content).digest("base64");
367
688
  return `${algorithm}-${hash}`;
@@ -376,7 +697,7 @@ async function isCacheExpired(storage, filename, cacheMaxAge = SEVEN_DAYS_IN_MS)
376
697
  }
377
698
  function normalizeScriptData(src, assetsBaseURL = "/_scripts") {
378
699
  if (hasProtocol(src, { acceptRelative: true })) {
379
- src = src.replace(/^\/\//, "https://");
700
+ src = src.replace(PROTOCOL_RELATIVE_RE, "https://");
380
701
  const url = parseURL(src);
381
702
  const h = hash(url);
382
703
  const file = `${h.startsWith("-") ? `_${h.slice(1)}` : h}.js`;
@@ -483,8 +804,8 @@ function NuxtScriptBundleTransformer(options = {
483
804
  transform: {
484
805
  filter: {
485
806
  id: {
486
- include: [/\.vue/, /\.[cm]?[jt]sx?$/],
487
- exclude: [/\.(?:test|spec)\./]
807
+ include: [VUE_RE, JS_RE],
808
+ exclude: [TEST_RE]
488
809
  }
489
810
  },
490
811
  async handler(code, id) {
@@ -498,7 +819,7 @@ function NuxtScriptBundleTransformer(options = {
498
819
  const calleeName = _node.callee?.name;
499
820
  if (!calleeName)
500
821
  return;
501
- const isValidCallee = calleeName === "useScript" || calleeName?.startsWith("useScript") && /^[A-Z]$/.test(calleeName?.charAt(9)) && !calleeName.startsWith("useScriptTrigger") && !calleeName.startsWith("useScriptEvent");
822
+ const isValidCallee = calleeName === "useScript" || calleeName?.startsWith("useScript") && UPPERCASE_RE.test(calleeName?.charAt(9)) && !calleeName.startsWith("useScriptTrigger") && !calleeName.startsWith("useScriptEvent");
502
823
  if (_node.type === "CallExpression" && _node.callee.type === "Identifier" && isValidCallee) {
503
824
  const fnName = _node.callee?.name;
504
825
  const node = _node;
@@ -506,7 +827,7 @@ function NuxtScriptBundleTransformer(options = {
506
827
  let src;
507
828
  let registryKey;
508
829
  if (fnName !== "useScript") {
509
- const baseName = fnName.replace(/^useScript/, "");
830
+ const baseName = fnName.replace(USE_SCRIPT_RE, "");
510
831
  registryKey = baseName.length > 0 ? baseName.charAt(0).toLowerCase() + baseName.slice(1) : void 0;
511
832
  }
512
833
  if (fnName === "useScript") {
@@ -710,6 +1031,7 @@ function NuxtScriptBundleTransformer(options = {
710
1031
  });
711
1032
  }
712
1033
 
1034
+ const TRIGGER_PLACEHOLDER_RE = /"__TRIGGER_PLACEHOLDER__"/g;
713
1035
  function registerTypeTemplates({ nuxt, config, newScripts }) {
714
1036
  addTypeTemplate({
715
1037
  filename: "types/nuxt-scripts-augments.d.ts",
@@ -721,7 +1043,7 @@ function registerTypeTemplates({ nuxt, config, newScripts }) {
721
1043
  let augments = `// Generated by @nuxt/scripts
722
1044
  declare module '#app' {
723
1045
  interface NuxtApp {
724
- $scripts: Record<${[...Object.keys(config.globals || {}), ...Object.keys(config.registry || {})].map((k) => `'${k}'`).concat(["string"]).join(" | ")}, import('#nuxt-scripts/types').UseScriptContext<any> | undefined>
1046
+ $scripts: Record<${[...[...Object.keys(config.globals || {}), ...Object.keys(config.registry || {})].map((k) => `'${k}'`), ...["string"]].join(" | ")}, import('#nuxt-scripts/types').UseScriptContext<any> | undefined>
725
1047
  _scripts: Record<string, import('#nuxt-scripts/types').NuxtDevToolsScriptInstance>
726
1048
  }
727
1049
  interface RuntimeNuxtHooks {
@@ -822,7 +1144,7 @@ function templatePlugin(config, registry) {
822
1144
  needsServiceWorkerImport = true;
823
1145
  }
824
1146
  const args = { ...input, scriptOptions };
825
- const argsJson = triggerResolved ? JSON.stringify(args).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved) : JSON.stringify(args);
1147
+ const argsJson = triggerResolved ? JSON.stringify(args).replace(TRIGGER_PLACEHOLDER_RE, triggerResolved) : JSON.stringify(args);
826
1148
  inits.push(`const ${k} = ${importDefinition.import.name}(${argsJson})`);
827
1149
  } else {
828
1150
  const args = (typeof c !== "object" ? {} : c) || {};
@@ -844,7 +1166,7 @@ function templatePlugin(config, registry) {
844
1166
  if (triggerResolved.includes("useScriptTriggerServiceWorker"))
845
1167
  needsServiceWorkerImport = true;
846
1168
  const resolvedOptions = { ...options, trigger: "__TRIGGER_PLACEHOLDER__" };
847
- const optionsJson = JSON.stringify(resolvedOptions).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved);
1169
+ const optionsJson = JSON.stringify(resolvedOptions).replace(TRIGGER_PLACEHOLDER_RE, triggerResolved);
848
1170
  inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...typeof c[0] === "string" ? { src: c[0] } : c[0] })}, { ...${optionsJson}, use: () => ({ ${k}: window.${k} }) })`);
849
1171
  } else {
850
1172
  inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...typeof c[0] === "string" ? { src: c[0] } : c[0] })}, { ...${JSON.stringify(c[1])}, use: () => ({ ${k}: window.${k} }) })`);
@@ -859,7 +1181,7 @@ function templatePlugin(config, registry) {
859
1181
  if (triggerResolved.includes("useScriptTriggerServiceWorker"))
860
1182
  needsServiceWorkerImport = true;
861
1183
  const resolvedOptions = { ...c, trigger: "__TRIGGER_PLACEHOLDER__" };
862
- const argsJson = JSON.stringify({ key: k, ...resolvedOptions }).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved);
1184
+ const argsJson = JSON.stringify({ key: k, ...resolvedOptions }).replace(TRIGGER_PLACEHOLDER_RE, triggerResolved);
863
1185
  inits.push(`const ${k} = useScript(${argsJson}, { use: () => ({ ${k}: window.${k} }) })`);
864
1186
  } else {
865
1187
  inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...c })}, { use: () => ({ ${k}: window.${k} }) })`);
@@ -1076,14 +1398,8 @@ const module$1 = defineNuxtModule({
1076
1398
  "`scripts.defaultScriptOptions.bundle` is deprecated. Use `scripts.firstParty: true` instead. First-party mode is now enabled by default."
1077
1399
  );
1078
1400
  }
1079
- const staticPresets = ["static", "github-pages", "cloudflare-pages-static"];
1080
- const preset = process.env.NITRO_PRESET || "";
1081
- const isStaticPreset = staticPresets.includes(preset);
1082
- const firstPartyEnabled = !!config.firstParty;
1083
- const firstPartyPrefix = typeof config.firstParty === "object" ? config.firstParty.prefix : void 0;
1084
- const firstPartyCollectPrefix = typeof config.firstParty === "object" ? config.firstParty.collectPrefix || "/_proxy" : "/_proxy";
1085
- const firstPartyPrivacy = typeof config.firstParty === "object" ? config.firstParty.privacy : void 0;
1086
- const assetsPrefix = firstPartyPrefix || config.assets?.prefix || "/_scripts";
1401
+ const firstParty = resolveFirstPartyConfig(config);
1402
+ const assetsPrefix = firstParty.assetsPrefix;
1087
1403
  if (config.partytown?.length) {
1088
1404
  config.registry = config.registry || {};
1089
1405
  const requiredForwards = [];
@@ -1142,52 +1458,10 @@ const module$1 = defineNuxtModule({
1142
1458
  return templateTriggerResolver(config.defaultScriptOptions);
1143
1459
  }
1144
1460
  });
1145
- logger.debug("[nuxt-scripts] First-party config:", { firstPartyEnabled, firstPartyPrivacy, firstPartyCollectPrefix });
1461
+ logger.debug("[nuxt-scripts] First-party config:", firstParty);
1146
1462
  let interceptRules = [];
1147
- if (firstPartyEnabled) {
1148
- addPluginTemplate({
1149
- filename: "nuxt-scripts-intercept.client.mjs",
1150
- getContents() {
1151
- const rulesJson = JSON.stringify(interceptRules);
1152
- return `export default defineNuxtPlugin({
1153
- name: 'nuxt-scripts:intercept',
1154
- enforce: 'pre',
1155
- setup() {
1156
- const rules = ${rulesJson};
1157
- const origBeacon = typeof navigator !== 'undefined' && navigator.sendBeacon
1158
- ? navigator.sendBeacon.bind(navigator)
1159
- : () => false;
1160
- const origFetch = globalThis.fetch.bind(globalThis);
1161
-
1162
- function rewriteUrl(url) {
1163
- try {
1164
- const parsed = new URL(url, location.origin);
1165
- for (const rule of rules) {
1166
- if (parsed.hostname === rule.pattern || parsed.hostname.endsWith('.' + rule.pattern)) {
1167
- if (rule.pathPrefix && !parsed.pathname.startsWith(rule.pathPrefix)) continue;
1168
- const path = rule.pathPrefix ? parsed.pathname.slice(rule.pathPrefix.length) : parsed.pathname;
1169
- return location.origin + rule.target + (path.startsWith('/') ? '' : '/') + path + parsed.search;
1170
- }
1171
- }
1172
- } catch {}
1173
- return url;
1174
- }
1175
-
1176
- globalThis.__nuxtScripts = {
1177
- sendBeacon: (url, data) => origBeacon(rewriteUrl(url), data),
1178
- fetch: (url, opts) => origFetch(typeof url === 'string' ? rewriteUrl(url) : url, opts),
1179
- };
1180
- },
1181
- })
1182
- `;
1183
- }
1184
- });
1185
- const proxyHandlerPath = await resolvePath("./runtime/server/proxy-handler");
1186
- logger.debug("[nuxt-scripts] Registering proxy handler:", `${firstPartyCollectPrefix}/**`, "->", proxyHandlerPath);
1187
- addServerHandler({
1188
- route: `${firstPartyCollectPrefix}/**`,
1189
- handler: proxyHandlerPath
1190
- });
1463
+ if (firstParty.enabled) {
1464
+ interceptRules = await setupFirstPartyHandlers(firstParty, resolvePath);
1191
1465
  }
1192
1466
  const scripts = await registry(resolvePath);
1193
1467
  for (const script of scripts) {
@@ -1216,96 +1490,14 @@ const module$1 = defineNuxtModule({
1216
1490
  });
1217
1491
  }
1218
1492
  const { renderedScript } = setupPublicAssetStrategy(config.assets);
1219
- if (firstPartyEnabled) {
1220
- const proxyConfigs = getAllProxyConfigs(firstPartyCollectPrefix);
1221
- const registryKeys = Object.keys(config.registry || {});
1222
- const neededRoutes = {};
1223
- const routePrivacyOverrides = {};
1224
- const unsupportedScripts = [];
1225
- for (const key of registryKeys) {
1226
- const script = registryScriptsWithImport.find((s) => s.import.name.toLowerCase() === `usescript${key.toLowerCase()}`);
1227
- const proxyKey = script?.proxy || void 0;
1228
- if (proxyKey) {
1229
- const proxyConfig = proxyConfigs[proxyKey];
1230
- if (proxyConfig?.routes) {
1231
- Object.assign(neededRoutes, proxyConfig.routes);
1232
- for (const routePath of Object.keys(proxyConfig.routes)) {
1233
- routePrivacyOverrides[routePath] = proxyConfig.privacy;
1234
- }
1235
- } else {
1236
- unsupportedScripts.push(key);
1237
- }
1238
- }
1239
- }
1240
- if (config.registry?.posthog && typeof config.registry.posthog === "object") {
1241
- const phConfig = Array.isArray(config.registry.posthog) ? config.registry.posthog[0] : config.registry.posthog;
1242
- if (phConfig && !phConfig.apiHost) {
1243
- const region = phConfig.region || "us";
1244
- phConfig.apiHost = region === "eu" ? `${firstPartyCollectPrefix}/ph-eu` : `${firstPartyCollectPrefix}/ph`;
1245
- const rtScripts = nuxt.options.runtimeConfig.public.scripts;
1246
- if (rtScripts?.posthog && typeof rtScripts.posthog === "object") {
1247
- const rtPhConfig = Array.isArray(rtScripts.posthog) ? rtScripts.posthog[0] : rtScripts.posthog;
1248
- if (rtPhConfig)
1249
- rtPhConfig.apiHost = phConfig.apiHost;
1250
- }
1251
- }
1252
- }
1253
- if (config.registry?.plausibleAnalytics && typeof config.registry.plausibleAnalytics === "object") {
1254
- const paConfig = Array.isArray(config.registry.plausibleAnalytics) ? config.registry.plausibleAnalytics[0] : config.registry.plausibleAnalytics;
1255
- if (paConfig && !paConfig.endpoint) {
1256
- paConfig.endpoint = `${firstPartyCollectPrefix}/plausible/api/event`;
1257
- const rtScripts = nuxt.options.runtimeConfig.public.scripts;
1258
- if (rtScripts?.plausibleAnalytics && typeof rtScripts.plausibleAnalytics === "object") {
1259
- const rtPaConfig = Array.isArray(rtScripts.plausibleAnalytics) ? rtScripts.plausibleAnalytics[0] : rtScripts.plausibleAnalytics;
1260
- if (rtPaConfig)
1261
- rtPaConfig.endpoint = paConfig.endpoint;
1262
- }
1263
- }
1264
- }
1265
- if (unsupportedScripts.length && nuxt.options.dev) {
1266
- logger.warn(
1267
- `First-party mode is enabled but these scripts don't support it yet: ${unsupportedScripts.join(", ")}.
1268
- They will load directly from third-party servers. Request support at https://github.com/nuxt/scripts/issues`
1269
- );
1270
- }
1271
- interceptRules = routesToInterceptRules(neededRoutes);
1272
- const flatRoutes = {};
1273
- for (const [path, config2] of Object.entries(neededRoutes)) {
1274
- flatRoutes[path] = config2.proxy;
1275
- }
1276
- nuxt.options.runtimeConfig["nuxt-scripts-proxy"] = {
1277
- routes: flatRoutes,
1278
- privacy: firstPartyPrivacy,
1279
- // undefined = use per-script defaults, set = global override
1280
- routePrivacy: routePrivacyOverrides
1281
- // per-script privacy from registry
1282
- };
1283
- if (Object.keys(neededRoutes).length) {
1284
- if (nuxt.options.dev) {
1285
- const routeCount = Object.keys(neededRoutes).length;
1286
- const scriptsCount = registryKeys.length;
1287
- const privacyLabel = firstPartyPrivacy === void 0 ? "per-script" : typeof firstPartyPrivacy === "boolean" ? firstPartyPrivacy ? "anonymize" : "passthrough" : "custom";
1288
- logger.success(`First-party mode enabled for ${scriptsCount} script(s), ${routeCount} proxy route(s) configured (privacy: ${privacyLabel})`);
1289
- if (logger.level >= 4) {
1290
- for (const [path, config2] of Object.entries(neededRoutes)) {
1291
- logger.debug(` ${path} \u2192 ${config2.proxy}`);
1292
- }
1293
- }
1294
- }
1295
- }
1296
- if (isStaticPreset) {
1297
- logger.warn(
1298
- `First-party collection endpoints require a server runtime (detected: ${preset || "static"}).
1299
- Scripts will be bundled, but collection requests will not be proxied.
1300
-
1301
- Options:
1302
- 1. Configure platform rewrites (Vercel, Netlify, Cloudflare)
1303
- 2. Switch to server-rendered mode (ssr: true)
1304
- 3. Disable with firstParty: false
1305
-
1306
- See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
1307
- );
1308
- }
1493
+ if (firstParty.enabled) {
1494
+ finalizeFirstParty({
1495
+ firstParty,
1496
+ interceptRules,
1497
+ registry: config.registry,
1498
+ registryScriptsWithImport,
1499
+ nuxtOptions: nuxt.options
1500
+ });
1309
1501
  }
1310
1502
  const moduleInstallPromises = /* @__PURE__ */ new Map();
1311
1503
  addBuildPlugin(NuxtScriptsCheckScripts(), {
@@ -1314,9 +1506,9 @@ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
1314
1506
  addBuildPlugin(NuxtScriptBundleTransformer({
1315
1507
  scripts: registryScriptsWithImport,
1316
1508
  registryConfig: nuxt.options.runtimeConfig.public.scripts,
1317
- defaultBundle: firstPartyEnabled || config.defaultScriptOptions?.bundle,
1318
- firstPartyEnabled,
1319
- firstPartyCollectPrefix,
1509
+ defaultBundle: firstParty.enabled || config.defaultScriptOptions?.bundle,
1510
+ firstPartyEnabled: firstParty.enabled,
1511
+ firstPartyCollectPrefix: firstParty.collectPrefix,
1320
1512
  moduleDetected(module) {
1321
1513
  if (nuxt.options.dev && module !== "@nuxt/scripts" && !moduleInstallPromises.has(module) && !hasNuxtModule(module))
1322
1514
  moduleInstallPromises.set(module, () => installNuxtModule(module));
@@ -1329,7 +1521,7 @@ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
1329
1521
  renderedScript
1330
1522
  }));
1331
1523
  nuxt.hooks.hook("build:done", async () => {
1332
- const initPromise = Array.from(moduleInstallPromises.values());
1524
+ const initPromise = [...moduleInstallPromises.values()];
1333
1525
  for (const p of initPromise)
1334
1526
  await p?.();
1335
1527
  });