@nuxt/scripts 1.0.0-beta.3 → 1.0.0-beta.31

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 (217) hide show
  1. package/README.md +3 -3
  2. package/dist/client/200.html +1 -1
  3. package/dist/client/404.html +1 -1
  4. package/dist/client/_nuxt/{DdVDSbUA.js → 6CwTUC2b.js} +1 -1
  5. package/dist/client/_nuxt/{CD5B-xvT.js → B71AlSZ1.js} +1 -1
  6. package/dist/client/_nuxt/{Ds2G8aQM.js → BYGJV5dd.js} +1 -1
  7. package/dist/client/_nuxt/V4W-T8W6.js +162 -0
  8. package/dist/client/_nuxt/builds/latest.json +1 -1
  9. package/dist/client/_nuxt/builds/meta/70b59a3e-a025-4a77-a25a-dfadf5b1749d.json +1 -0
  10. package/dist/client/_nuxt/entry.C5SUNdim.css +1 -0
  11. package/dist/client/_nuxt/error-404.1K8v8Su2.css +1 -0
  12. package/dist/client/_nuxt/error-500.B9qvKpQm.css +1 -0
  13. package/dist/client/index.html +1 -1
  14. package/dist/module.d.mts +6 -18
  15. package/dist/module.d.ts +164 -0
  16. package/dist/module.json +1 -1
  17. package/dist/module.mjs +908 -645
  18. package/dist/registry.d.ts +6 -0
  19. package/dist/registry.mjs +244 -78
  20. package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.d.vue.ts +16 -9
  21. package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +57 -30
  22. package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue.d.ts +16 -9
  23. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.d.vue.ts +22 -39
  24. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue +69 -72
  25. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue.d.ts +22 -39
  26. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsCircle.d.vue.ts +5 -1
  27. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsCircle.vue +25 -38
  28. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsCircle.vue.d.ts +5 -1
  29. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsGeoJson.d.vue.ts +43 -0
  30. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsGeoJson.vue +61 -0
  31. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsGeoJson.vue.d.ts +43 -0
  32. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.d.vue.ts +4 -0
  33. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.vue +22 -26
  34. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.vue.d.ts +4 -0
  35. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.d.vue.ts +9 -5
  36. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue +62 -53
  37. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue.d.ts +9 -5
  38. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.d.vue.ts +26 -11
  39. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue +48 -45
  40. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue.d.ts +26 -11
  41. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.d.vue.ts +15 -4
  42. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue +47 -37
  43. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue.d.ts +15 -4
  44. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.d.vue.ts +77 -0
  45. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue +209 -0
  46. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue.d.ts +77 -0
  47. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPinElement.d.vue.ts +4 -0
  48. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPinElement.vue +23 -32
  49. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPinElement.vue.d.ts +4 -0
  50. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolygon.d.vue.ts +7 -3
  51. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolygon.vue +24 -38
  52. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolygon.vue.d.ts +7 -3
  53. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolyline.d.vue.ts +7 -3
  54. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolyline.vue +24 -38
  55. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolyline.vue.d.ts +7 -3
  56. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsRectangle.d.vue.ts +7 -3
  57. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsRectangle.vue +25 -38
  58. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsRectangle.vue.d.ts +7 -3
  59. package/dist/runtime/components/GoogleMaps/bindGoogleMapsEvents.d.ts +13 -0
  60. package/dist/runtime/components/GoogleMaps/bindGoogleMapsEvents.js +8 -0
  61. package/dist/runtime/components/GoogleMaps/injectionKeys.d.ts +13 -0
  62. package/dist/runtime/components/GoogleMaps/injectionKeys.js +3 -0
  63. package/dist/runtime/components/GoogleMaps/useGoogleMapsResource.d.ts +26 -0
  64. package/dist/runtime/components/GoogleMaps/useGoogleMapsResource.js +42 -0
  65. package/dist/runtime/components/ScriptBlueskyEmbed.d.vue.ts +87 -0
  66. package/dist/runtime/components/ScriptBlueskyEmbed.vue +85 -0
  67. package/dist/runtime/components/ScriptBlueskyEmbed.vue.d.ts +87 -0
  68. package/dist/runtime/components/ScriptCrisp.vue +1 -1
  69. package/dist/runtime/components/ScriptGoogleAdsense.vue +1 -1
  70. package/dist/runtime/components/ScriptGravatar.d.vue.ts +22 -0
  71. package/dist/runtime/components/ScriptGravatar.vue +46 -0
  72. package/dist/runtime/components/ScriptGravatar.vue.d.ts +22 -0
  73. package/dist/runtime/components/ScriptInstagramEmbed.d.vue.ts +2 -2
  74. package/dist/runtime/components/ScriptInstagramEmbed.vue +5 -2
  75. package/dist/runtime/components/ScriptInstagramEmbed.vue.d.ts +2 -2
  76. package/dist/runtime/components/ScriptIntercom.vue +4 -3
  77. package/dist/runtime/components/ScriptPayPalButtons.d.vue.ts +43 -32
  78. package/dist/runtime/components/ScriptPayPalButtons.vue +48 -79
  79. package/dist/runtime/components/ScriptPayPalButtons.vue.d.ts +43 -32
  80. package/dist/runtime/components/ScriptPayPalMessages.d.vue.ts +37 -23
  81. package/dist/runtime/components/ScriptPayPalMessages.vue +46 -50
  82. package/dist/runtime/components/ScriptPayPalMessages.vue.d.ts +37 -23
  83. package/dist/runtime/components/ScriptStripePricingTable.vue +2 -2
  84. package/dist/runtime/components/ScriptVimeoPlayer.d.vue.ts +9 -0
  85. package/dist/runtime/components/ScriptVimeoPlayer.vue +13 -10
  86. package/dist/runtime/components/ScriptVimeoPlayer.vue.d.ts +9 -0
  87. package/dist/runtime/components/ScriptXEmbed.d.vue.ts +2 -2
  88. package/dist/runtime/components/ScriptXEmbed.vue +6 -3
  89. package/dist/runtime/components/ScriptXEmbed.vue.d.ts +2 -2
  90. package/dist/runtime/components/ScriptYouTubePlayer.d.vue.ts +2 -2
  91. package/dist/runtime/components/ScriptYouTubePlayer.vue +11 -5
  92. package/dist/runtime/components/ScriptYouTubePlayer.vue.d.ts +2 -2
  93. package/dist/runtime/composables/useScript.js +13 -6
  94. package/dist/runtime/composables/useScriptEventPage.js +2 -2
  95. package/dist/runtime/composables/useScriptTriggerConsent.d.ts +10 -0
  96. package/dist/runtime/composables/useScriptTriggerConsent.js +33 -20
  97. package/dist/runtime/composables/useScriptTriggerElement.js +1 -1
  98. package/dist/runtime/composables/useScriptTriggerIdleTimeout.js +1 -1
  99. package/dist/runtime/registry/bing-uet.d.ts +20 -0
  100. package/dist/runtime/registry/bing-uet.js +29 -0
  101. package/dist/runtime/registry/bluesky-embed.d.ts +116 -0
  102. package/dist/runtime/registry/bluesky-embed.js +72 -0
  103. package/dist/runtime/registry/clarity.d.ts +10 -15
  104. package/dist/runtime/registry/clarity.js +22 -31
  105. package/dist/runtime/registry/cloudflare-web-analytics.d.ts +2 -13
  106. package/dist/runtime/registry/cloudflare-web-analytics.js +2 -14
  107. package/dist/runtime/registry/crisp.d.ts +10 -40
  108. package/dist/runtime/registry/crisp.js +2 -33
  109. package/dist/runtime/registry/databuddy-analytics.d.ts +2 -35
  110. package/dist/runtime/registry/databuddy-analytics.js +20 -45
  111. package/dist/runtime/registry/fathom-analytics.d.ts +7 -26
  112. package/dist/runtime/registry/fathom-analytics.js +2 -24
  113. package/dist/runtime/registry/google-adsense.d.ts +3 -11
  114. package/dist/runtime/registry/google-adsense.js +2 -11
  115. package/dist/runtime/registry/google-analytics.d.ts +3 -5
  116. package/dist/runtime/registry/google-analytics.js +3 -8
  117. package/dist/runtime/registry/google-maps.d.ts +3 -9
  118. package/dist/runtime/registry/google-maps.js +2 -8
  119. package/dist/runtime/registry/google-recaptcha.d.ts +2 -6
  120. package/dist/runtime/registry/google-recaptcha.js +4 -12
  121. package/dist/runtime/registry/google-sign-in.d.ts +2 -13
  122. package/dist/runtime/registry/google-sign-in.js +2 -22
  123. package/dist/runtime/registry/google-tag-manager.d.ts +3 -28
  124. package/dist/runtime/registry/google-tag-manager.js +4 -27
  125. package/dist/runtime/registry/gravatar.d.ts +26 -0
  126. package/dist/runtime/registry/gravatar.js +33 -0
  127. package/dist/runtime/registry/hotjar.d.ts +4 -6
  128. package/dist/runtime/registry/hotjar.js +2 -5
  129. package/dist/runtime/registry/instagram-embed.d.ts +3 -18
  130. package/dist/runtime/registry/instagram-embed.js +4 -19
  131. package/dist/runtime/registry/intercom.d.ts +4 -12
  132. package/dist/runtime/registry/intercom.js +2 -12
  133. package/dist/runtime/registry/matomo-analytics.d.ts +3 -12
  134. package/dist/runtime/registry/matomo-analytics.js +3 -12
  135. package/dist/runtime/registry/meta-pixel.d.ts +4 -6
  136. package/dist/runtime/registry/meta-pixel.js +2 -4
  137. package/dist/runtime/registry/mixpanel-analytics.d.ts +22 -0
  138. package/dist/runtime/registry/mixpanel-analytics.js +46 -0
  139. package/dist/runtime/registry/npm.d.ts +3 -7
  140. package/dist/runtime/registry/npm.js +2 -9
  141. package/dist/runtime/registry/paypal.d.ts +4 -25
  142. package/dist/runtime/registry/paypal.js +3 -66
  143. package/dist/runtime/registry/plausible-analytics.js +18 -13
  144. package/dist/runtime/registry/posthog.d.ts +10 -12
  145. package/dist/runtime/registry/posthog.js +7 -14
  146. package/dist/runtime/registry/reddit-pixel.d.ts +5 -6
  147. package/dist/runtime/registry/reddit-pixel.js +2 -4
  148. package/dist/runtime/registry/rybbit-analytics.d.ts +2 -14
  149. package/dist/runtime/registry/rybbit-analytics.js +10 -20
  150. package/dist/runtime/registry/schemas.d.ts +982 -0
  151. package/dist/runtime/registry/schemas.js +937 -0
  152. package/dist/runtime/registry/segment.d.ts +2 -5
  153. package/dist/runtime/registry/segment.js +2 -5
  154. package/dist/runtime/registry/snapchat-pixel.d.ts +4 -33
  155. package/dist/runtime/registry/snapchat-pixel.js +2 -20
  156. package/dist/runtime/registry/stripe.d.ts +3 -4
  157. package/dist/runtime/registry/stripe.js +2 -4
  158. package/dist/runtime/registry/tiktok-pixel.d.ts +4 -7
  159. package/dist/runtime/registry/tiktok-pixel.js +2 -6
  160. package/dist/runtime/registry/umami-analytics.d.ts +2 -31
  161. package/dist/runtime/registry/umami-analytics.js +2 -36
  162. package/dist/runtime/registry/vercel-analytics.d.ts +29 -0
  163. package/dist/runtime/registry/vercel-analytics.js +84 -0
  164. package/dist/runtime/registry/vimeo-player.d.ts +2 -2
  165. package/dist/runtime/registry/vimeo-player.js +1 -1
  166. package/dist/runtime/registry/x-embed.d.ts +3 -17
  167. package/dist/runtime/registry/x-embed.js +3 -18
  168. package/dist/runtime/registry/x-pixel.d.ts +4 -7
  169. package/dist/runtime/registry/x-pixel.js +2 -5
  170. package/dist/runtime/registry/youtube-player.d.ts +7 -7
  171. package/dist/runtime/registry/youtube-player.js +1 -1
  172. package/dist/runtime/server/{sw-handler.d.ts → bluesky-embed-image.d.ts} +1 -1
  173. package/dist/runtime/server/bluesky-embed-image.js +7 -0
  174. package/dist/runtime/server/bluesky-embed.d.ts +16 -0
  175. package/dist/runtime/server/bluesky-embed.js +59 -0
  176. package/dist/runtime/server/google-maps-geocode-proxy.d.ts +2 -0
  177. package/dist/runtime/server/google-maps-geocode-proxy.js +34 -0
  178. package/dist/runtime/server/google-static-maps-proxy.js +2 -13
  179. package/dist/runtime/server/gravatar-proxy.d.ts +2 -0
  180. package/dist/runtime/server/gravatar-proxy.js +46 -0
  181. package/dist/runtime/server/instagram-embed-asset.js +8 -41
  182. package/dist/runtime/server/instagram-embed-image.js +6 -53
  183. package/dist/runtime/server/instagram-embed.d.ts +16 -0
  184. package/dist/runtime/server/instagram-embed.js +173 -35
  185. package/dist/runtime/server/proxy-handler.js +144 -113
  186. package/dist/runtime/server/utils/image-proxy.d.ts +12 -0
  187. package/dist/runtime/server/utils/image-proxy.js +70 -0
  188. package/dist/runtime/server/utils/privacy.d.ts +1 -2
  189. package/dist/runtime/server/utils/privacy.js +54 -34
  190. package/dist/runtime/server/x-embed-image.js +5 -49
  191. package/dist/runtime/server/x-embed.js +3 -2
  192. package/dist/runtime/types.d.ts +74 -40
  193. package/dist/runtime/utils/pure.d.ts +1 -5
  194. package/dist/runtime/utils/pure.js +0 -67
  195. package/dist/runtime/utils.d.ts +4 -3
  196. package/dist/runtime/utils.js +24 -10
  197. package/dist/shared/scripts.D7e2ENu6.mjs +211 -0
  198. package/dist/stats.d.mts +202 -0
  199. package/dist/stats.d.ts +202 -0
  200. package/dist/stats.mjs +3860 -0
  201. package/dist/types-source.d.mts +17 -0
  202. package/dist/types-source.d.ts +17 -0
  203. package/dist/types-source.mjs +3614 -0
  204. package/package.json +52 -38
  205. package/dist/client/_nuxt/D-kOnTuH.js +0 -162
  206. package/dist/client/_nuxt/builds/meta/f1474569-6922-450d-bc3f-4fd5f3e1391a.json +0 -1
  207. package/dist/client/_nuxt/entry.D45OuV0w.css +0 -1
  208. package/dist/client/_nuxt/error-404.B57D-jUQ.css +0 -1
  209. package/dist/client/_nuxt/error-500.DTHUW7BI.css +0 -1
  210. package/dist/runtime/components/ScriptPayPalMarks.d.vue.ts +0 -52
  211. package/dist/runtime/components/ScriptPayPalMarks.vue +0 -69
  212. package/dist/runtime/components/ScriptPayPalMarks.vue.d.ts +0 -52
  213. package/dist/runtime/plugins/sw-register.client.d.ts +0 -2
  214. package/dist/runtime/plugins/sw-register.client.js +0 -12
  215. package/dist/runtime/server/sw-handler.js +0 -25
  216. package/dist/runtime/sw/proxy-sw.template.d.ts +0 -1
  217. package/dist/runtime/sw/proxy-sw.template.js +0 -54
package/dist/module.mjs CHANGED
@@ -1,89 +1,285 @@
1
- import { useNuxt, extendViteConfig, useLogger, addDevServerHandler, extendRouteRules, tryUseNuxt, logger as logger$1, addTypeTemplate, defineNuxtModule, createResolver, hasNuxtModule, addImports, addComponentsDir, addTemplate, addServerHandler, addPluginTemplate, addBuildPlugin } from '@nuxt/kit';
2
- import { existsSync, readFileSync } from 'node:fs';
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { useLogger, addServerHandler, addPluginTemplate, useNuxt, addDevServerHandler, extendRouteRules, tryUseNuxt, extendViteConfig, logger as logger$1, addTypeTemplate, defineNuxtModule, createResolver, hasNuxtModule, addImports, addComponentsDir, addTemplate, addBuildPlugin } from '@nuxt/kit';
3
3
  import { defu } from 'defu';
4
+ import { join, resolve, relative } from 'pathe';
4
5
  import { resolvePackageJSON, readPackageJSON } from 'pkg-types';
6
+ import { lazyEventHandler, eventHandler, createError } from 'h3';
7
+ import { fetch, $fetch } from 'ofetch';
8
+ import { joinURL, parseURL, parseQuery, hasProtocol } from 'ufo';
9
+ import { createStorage } from 'unstorage';
10
+ import fsDriver from 'unstorage/drivers/fs-lite';
5
11
  import { addCustomTab } from '@nuxt/devtools-kit';
12
+ import { isCI, provider } from 'std-env';
13
+ import { parseAndWalk, ScopeTracker, walk, ScopeTrackerFunction, ScopeTrackerIdentifier, ScopeTrackerFunctionParam, ScopeTrackerVariable } from 'oxc-walker';
14
+ import { createUnplugin } from 'unplugin';
15
+ import { pathToFileURL } from 'node:url';
6
16
  import { createHash } from 'node:crypto';
7
17
  import fsp from 'node:fs/promises';
8
- import { createUnplugin } from 'unplugin';
18
+ import { colors } from 'consola/utils';
9
19
  import MagicString from 'magic-string';
10
- import { parseAndWalk } from 'oxc-walker';
11
- import { joinURL, parseURL, parseQuery, hasProtocol } from 'ufo';
12
20
  import { hash } from 'ohash';
13
- import { join, resolve, relative } from 'pathe';
14
- import { colors } from 'consola/utils';
15
- import { fetch, $fetch } from 'ofetch';
16
- import { lazyEventHandler, eventHandler, createError } from 'h3';
17
- import { createStorage } from 'unstorage';
18
- import fsDriver from 'unstorage/drivers/fs-lite';
19
- import { rewriteScriptUrls } from '../dist/runtime/utils/pure.js';
20
- import { pathToFileURL } from 'node:url';
21
- import { isCI, provider } from 'std-env';
22
21
  import { registry } from './registry.mjs';
22
+ import { g as getAllProxyConfigs } from './shared/scripts.D7e2ENu6.mjs';
23
23
 
24
- const DEVTOOLS_UI_ROUTE = "/__nuxt-scripts";
25
- const DEVTOOLS_UI_LOCAL_PORT = 3300;
24
+ function generatePartytownResolveUrl(proxyPrefix) {
25
+ return `function(url, location, type) {
26
+ if (url.origin !== location.origin) {
27
+ return new URL(${JSON.stringify(proxyPrefix)} + '/' + url.host + url.pathname + url.search, location.origin);
28
+ }
29
+ }`;
30
+ }
26
31
 
27
- async function setupDevToolsUI(options, resolve, nuxt = useNuxt()) {
28
- const clientPath = await resolve("./client");
29
- const isProductionBuild = existsSync(clientPath);
30
- if (isProductionBuild) {
31
- nuxt.hook("vite:serverCreated", async (server) => {
32
- const sirv = await import('sirv').then((r) => r.default || r);
33
- server.middlewares.use(
34
- DEVTOOLS_UI_ROUTE,
35
- sirv(clientPath, { dev: true, single: true })
36
- );
32
+ const logger = useLogger("@nuxt/scripts");
33
+
34
+ function generateInterceptPluginContents(proxyPrefix) {
35
+ return `export default defineNuxtPlugin({
36
+ name: 'nuxt-scripts:intercept',
37
+ enforce: 'pre',
38
+ setup() {
39
+ const proxyPrefix = ${JSON.stringify(proxyPrefix)};
40
+ const origBeacon = typeof navigator !== 'undefined' && navigator.sendBeacon
41
+ ? navigator.sendBeacon.bind(navigator)
42
+ : () => false;
43
+ const origFetch = globalThis.fetch.bind(globalThis);
44
+
45
+ function proxyUrl(url) {
46
+ try {
47
+ const parsed = new URL(url, location.origin);
48
+ if (parsed.origin !== location.origin)
49
+ return location.origin + proxyPrefix + '/' + parsed.host + parsed.pathname + parsed.search;
50
+ } catch {}
51
+ return url;
52
+ }
53
+
54
+ // XMLHttpRequest wrapper \u2014 intercepts .open() to rewrite URL
55
+ const OrigXHR = XMLHttpRequest;
56
+ class ProxiedXHR extends OrigXHR {
57
+ open() {
58
+ const args = Array.from(arguments);
59
+ if (typeof args[1] === 'string') args[1] = proxyUrl(args[1]);
60
+ return super.open.apply(this, args);
61
+ }
62
+ }
63
+ // Image wrapper \u2014 intercepts .src setter to rewrite URL
64
+ const OrigImage = Image;
65
+ const origSrcDesc = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'src');
66
+ function ProxiedImage(w, h) {
67
+ const img = arguments.length === 2 ? new OrigImage(w, h)
68
+ : arguments.length === 1 ? new OrigImage(w) : new OrigImage();
69
+ if (origSrcDesc && origSrcDesc.set) {
70
+ Object.defineProperty(img, 'src', {
71
+ get() { return origSrcDesc.get.call(this); },
72
+ set(v) { origSrcDesc.set.call(this, typeof v === 'string' ? proxyUrl(v) : v); },
73
+ configurable: true,
74
+ });
75
+ }
76
+ return img;
77
+ }
78
+
79
+ globalThis.__nuxtScripts = {
80
+ sendBeacon: (url, data) => origBeacon(proxyUrl(url), data),
81
+ fetch: (url, opts) => origFetch(typeof url === 'string' ? proxyUrl(url) : url, opts),
82
+ XMLHttpRequest: ProxiedXHR,
83
+ Image: ProxiedImage,
84
+ };
85
+ },
86
+ })
87
+ `;
88
+ }
89
+
90
+ async function setupFirstParty(config, resolvePath) {
91
+ const enabled = !!config.firstParty;
92
+ const proxyPrefix = typeof config.firstParty === "object" ? config.firstParty.proxyPrefix || "/_scripts/p" : "/_scripts/p";
93
+ const privacy = typeof config.firstParty === "object" ? config.firstParty.privacy : void 0;
94
+ const assetsPrefix = config.assets?.prefix || "/_scripts/assets";
95
+ const proxyConfigs = enabled ? getAllProxyConfigs() : {};
96
+ const firstParty = { enabled, proxyPrefix, privacy, assetsPrefix, proxyConfigs };
97
+ if (enabled) {
98
+ const proxyHandlerPath = await resolvePath("./runtime/server/proxy-handler");
99
+ logger.debug("[nuxt-scripts] Registering proxy handler:", `${proxyPrefix}/**`, "->", proxyHandlerPath);
100
+ addServerHandler({
101
+ route: `${proxyPrefix}/**`,
102
+ handler: proxyHandlerPath
37
103
  });
38
- } else {
39
- extendViteConfig((config) => {
40
- config.server = config.server || {};
41
- config.server.proxy = config.server.proxy || {};
42
- config.server.proxy[DEVTOOLS_UI_ROUTE] = {
43
- target: `http://localhost:${DEVTOOLS_UI_LOCAL_PORT}${DEVTOOLS_UI_ROUTE}`,
44
- changeOrigin: true,
45
- followRedirects: true,
46
- rewrite: (path) => path.replace(DEVTOOLS_UI_ROUTE, "")
104
+ }
105
+ return firstParty;
106
+ }
107
+ function applyAutoInject(registry, runtimeConfig, proxyPrefix, registryKey, autoInject) {
108
+ const entry = registry[registryKey];
109
+ if (!entry)
110
+ return;
111
+ const input = entry[0];
112
+ const scriptOptions = entry[1];
113
+ if (input?.firstParty === false || scriptOptions?.firstParty === false)
114
+ return;
115
+ const rtScripts = runtimeConfig.public?.scripts;
116
+ const rtEntry = rtScripts?.[registryKey];
117
+ const config = rtEntry && typeof rtEntry === "object" ? rtEntry : input;
118
+ if (!config || config[autoInject.configField])
119
+ return;
120
+ const value = autoInject.computeValue(proxyPrefix, config);
121
+ input[autoInject.configField] = value;
122
+ if (rtEntry && typeof rtEntry === "object" && rtEntry !== input)
123
+ rtEntry[autoInject.configField] = value;
124
+ }
125
+ function computePrivacyLevel(privacy) {
126
+ const flags = Object.values(privacy);
127
+ if (flags.every(Boolean))
128
+ return "full";
129
+ if (flags.some(Boolean))
130
+ return "partial";
131
+ return "none";
132
+ }
133
+ function finalizeFirstParty(opts) {
134
+ const { firstParty, registryScripts, nuxtOptions } = opts;
135
+ const { proxyConfigs, proxyPrefix } = firstParty;
136
+ const registryKeys = Object.keys(opts.registry || {});
137
+ const scriptByKey = /* @__PURE__ */ new Map();
138
+ for (const script of registryScripts) {
139
+ if (script.registryKey)
140
+ scriptByKey.set(script.registryKey, script);
141
+ }
142
+ const domainPrivacy = {};
143
+ const unsupportedScripts = [];
144
+ const unmatchedScripts = [];
145
+ let totalDomains = 0;
146
+ const devtoolsScripts = [];
147
+ for (const key of registryKeys) {
148
+ const script = scriptByKey.get(key);
149
+ if (!script) {
150
+ unmatchedScripts.push(key);
151
+ continue;
152
+ }
153
+ if (script.proxy === false)
154
+ continue;
155
+ const registryEntry = opts.registry?.[key];
156
+ const entryScriptOptions = registryEntry?.[1];
157
+ const entryInput = registryEntry?.[0];
158
+ if (entryScriptOptions?.firstParty === false || entryInput?.firstParty === false)
159
+ continue;
160
+ const configKey = script.proxy || key;
161
+ const proxyConfig = proxyConfigs[configKey];
162
+ if (!proxyConfig) {
163
+ if (script.scriptBundling !== false)
164
+ unsupportedScripts.push(key);
165
+ continue;
166
+ }
167
+ for (const domain of proxyConfig.domains) {
168
+ domainPrivacy[domain] = proxyConfig.privacy;
169
+ totalDomains++;
170
+ }
171
+ if (proxyConfig.autoInject && opts.registry)
172
+ applyAutoInject(opts.registry, nuxtOptions.runtimeConfig, proxyPrefix, key, proxyConfig.autoInject);
173
+ if (nuxtOptions.dev) {
174
+ const privacy = proxyConfig.privacy;
175
+ const normalizedPrivacy = {
176
+ ip: !!privacy.ip,
177
+ userAgent: !!privacy.userAgent,
178
+ language: !!privacy.language,
179
+ screen: !!privacy.screen,
180
+ timezone: !!privacy.timezone,
181
+ hardware: !!privacy.hardware
47
182
  };
48
- });
183
+ const logo = script.logo;
184
+ const logoStr = typeof logo === "object" ? logo.dark || logo.light : logo || "";
185
+ devtoolsScripts.push({
186
+ registryKey: key,
187
+ label: script.label || key,
188
+ logo: logoStr,
189
+ category: script.category || "unknown",
190
+ configKey,
191
+ mechanism: script.src === false ? "config-injection-proxy" : "bundle-rewrite-intercept",
192
+ hasAutoInject: !!proxyConfig.autoInject,
193
+ autoInjectField: proxyConfig.autoInject?.configField,
194
+ hasPostProcess: !!proxyConfig.postProcess,
195
+ privacy: normalizedPrivacy,
196
+ privacyLevel: computePrivacyLevel(normalizedPrivacy),
197
+ domains: [...proxyConfig.domains]
198
+ });
199
+ }
49
200
  }
50
- addCustomTab({
51
- // unique identifier
52
- name: "nuxt-scripts",
53
- // title to display in the tab
54
- title: "Scripts",
55
- // any icon from Iconify, or a URL to an image
56
- icon: "carbon:script",
57
- // iframe view
58
- view: {
59
- type: "iframe",
60
- src: DEVTOOLS_UI_ROUTE
201
+ if (unmatchedScripts.length) {
202
+ logger.warn(
203
+ `First-party mode: could not find registry scripts for: ${unmatchedScripts.join(", ")}.
204
+ These scripts will not have proxy routes registered. Check that the registry key matches a known script.`
205
+ );
206
+ }
207
+ if (unsupportedScripts.length && nuxtOptions.dev) {
208
+ logger.warn(
209
+ `First-party mode is enabled but these scripts don't support it yet: ${unsupportedScripts.join(", ")}.
210
+ They will load directly from third-party servers. Request support at https://github.com/nuxt/scripts/issues`
211
+ );
212
+ }
213
+ addPluginTemplate({
214
+ filename: "nuxt-scripts-intercept.client.mjs",
215
+ getContents() {
216
+ return generateInterceptPluginContents(proxyPrefix);
61
217
  }
62
218
  });
63
- }
219
+ nuxtOptions.runtimeConfig["nuxt-scripts-proxy"] = {
220
+ proxyPrefix,
221
+ domainPrivacy,
222
+ privacy: firstParty.privacy
223
+ };
224
+ const privacyLabel = firstParty.privacy === void 0 ? "per-script" : typeof firstParty.privacy === "boolean" ? firstParty.privacy ? "anonymize" : "passthrough" : "custom";
225
+ if (totalDomains > 0 && nuxtOptions.dev) {
226
+ const scriptsCount = registryKeys.length;
227
+ logger.success(`First-party mode enabled for ${scriptsCount} script(s), ${totalDomains} domain(s) proxied (privacy: ${privacyLabel})`);
228
+ }
229
+ const staticPresets = ["static", "github-pages", "cloudflare-pages-static", "netlify-static", "azure-static", "firebase-static"];
230
+ const preset = process.env.NITRO_PRESET || "";
231
+ if (staticPresets.includes(preset)) {
232
+ logger.warn(
233
+ `First-party collection endpoints require a server runtime (detected: ${preset || "static"}).
234
+ Scripts will be bundled, but collection requests will not be proxied.
64
235
 
65
- const logger = useLogger("@nuxt/scripts");
236
+ Options:
237
+ 1. Configure platform rewrites (Vercel, Netlify, Cloudflare)
238
+ 2. Switch to server-rendered mode (ssr: true)
239
+ 3. Disable with firstParty: false
240
+
241
+ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
242
+ );
243
+ }
244
+ let devtools;
245
+ if (nuxtOptions.dev) {
246
+ const allDomains = /* @__PURE__ */ new Set();
247
+ for (const s of devtoolsScripts) {
248
+ for (const d of s.domains)
249
+ allDomains.add(d);
250
+ }
251
+ devtools = {
252
+ enabled: true,
253
+ proxyPrefix,
254
+ privacyMode: privacyLabel,
255
+ scripts: devtoolsScripts,
256
+ totalDomains: allDomains.size
257
+ };
258
+ }
259
+ return { proxyPrefix, devtools };
260
+ }
66
261
 
67
262
  const renderedScript = /* @__PURE__ */ new Map();
68
263
  const ONE_YEAR_IN_SECONDS = 60 * 60 * 24 * 365;
69
- const bundleStorage = () => {
264
+ function bundleStorage() {
70
265
  const nuxt = tryUseNuxt();
71
266
  return createStorage({
72
267
  driver: fsDriver({
73
268
  base: resolve(nuxt?.options.rootDir || "", "node_modules/.cache/nuxt/scripts")
74
269
  })
75
270
  });
76
- };
271
+ }
77
272
  function setupPublicAssetStrategy(options = {}) {
78
- const assetsBaseURL = options.prefix || "/_scripts";
273
+ const assetsBaseURL = options.prefix || "/_scripts/assets";
79
274
  const nuxt = useNuxt();
80
275
  const storage = bundleStorage();
81
276
  addDevServerHandler({
82
277
  route: assetsBaseURL,
83
278
  handler: lazyEventHandler(async () => {
84
279
  return eventHandler(async (event) => {
85
- const filename = event.path.slice(1);
86
- const scriptDescriptor = renderedScript.get(join(assetsBaseURL, event.path.slice(1)));
280
+ const cleanPath = (event.path || "").split("?")[0]?.slice(1) || "";
281
+ const filename = cleanPath;
282
+ const scriptDescriptor = renderedScript.get(join(assetsBaseURL, cleanPath));
87
283
  if (!scriptDescriptor || scriptDescriptor instanceof Error)
88
284
  throw createError({ statusCode: 404 });
89
285
  if (scriptDescriptor.content) {
@@ -123,242 +319,447 @@ function setupPublicAssetStrategy(options = {}) {
123
319
  };
124
320
  }
125
321
 
126
- function buildProxyConfig(collectPrefix) {
127
- return {
128
- googleAnalytics: {
129
- // GA4: screen/timezone/UA needed for device, time, and OS reports; rest anonymized safely
130
- privacy: { ip: true, userAgent: false, language: true, screen: false, timezone: false, hardware: true },
131
- rewrite: [
132
- // Modern gtag.js uses www.google.com/g/collect
133
- { from: "www.google.com/g/collect", to: `${collectPrefix}/ga/g/collect` },
134
- // Older gtag.js constructs URLs dynamically: "https://"+(subdomain)+".google-analytics.com/g/collect"
135
- // We need to catch the suffix pattern with leading dot
136
- { from: ".google-analytics.com/g/collect", to: `${collectPrefix}/ga/g/collect` },
137
- { from: ".analytics.google.com/g/collect", to: `${collectPrefix}/ga/g/collect` },
138
- // Full domain patterns for static URLs
139
- { from: "www.google-analytics.com/g/collect", to: `${collectPrefix}/ga/g/collect` },
140
- { from: "analytics.google.com/g/collect", to: `${collectPrefix}/ga/g/collect` },
141
- // Legacy endpoints still used by some scripts
142
- { from: "www.google-analytics.com", to: `${collectPrefix}/ga` },
143
- { from: "analytics.google.com", to: `${collectPrefix}/ga` },
144
- // DoubleClick tracking (used by GA4 for ads/conversions)
145
- { from: "stats.g.doubleclick.net/g/collect", to: `${collectPrefix}/ga/g/collect` },
146
- { from: "stats.g.doubleclick.net", to: `${collectPrefix}/ga-dc` },
147
- // Google Ads/Syndication tracking
148
- { from: "pagead2.googlesyndication.com", to: `${collectPrefix}/ga-syn` },
149
- { from: "www.googleadservices.com", to: `${collectPrefix}/ga-ads` },
150
- { from: "googleads.g.doubleclick.net", to: `${collectPrefix}/ga-gads` }
151
- ],
152
- routes: {
153
- [`${collectPrefix}/ga/**`]: { proxy: "https://www.google-analytics.com/**" },
154
- [`${collectPrefix}/ga-dc/**`]: { proxy: "https://stats.g.doubleclick.net/**" },
155
- [`${collectPrefix}/ga-syn/**`]: { proxy: "https://pagead2.googlesyndication.com/**" },
156
- [`${collectPrefix}/ga-ads/**`]: { proxy: "https://www.googleadservices.com/**" },
157
- [`${collectPrefix}/ga-gads/**`]: { proxy: "https://googleads.g.doubleclick.net/**" }
158
- }
159
- },
160
- googleTagManager: {
161
- // GTM: container only, passes data through — downstream tags have their own privacy
162
- privacy: { ip: false, userAgent: false, language: false, screen: false, timezone: false, hardware: false },
163
- rewrite: [
164
- { from: "www.googletagmanager.com", to: `${collectPrefix}/gtm` }
165
- ],
166
- routes: {
167
- [`${collectPrefix}/gtm/**`]: { proxy: "https://www.googletagmanager.com/**" }
168
- }
169
- },
170
- metaPixel: {
171
- // Meta: untrusted ad network — full anonymization
172
- privacy: { ip: true, userAgent: true, language: true, screen: true, timezone: true, hardware: true },
173
- rewrite: [
174
- // SDK script loading
175
- { from: "connect.facebook.net", to: `${collectPrefix}/meta` },
176
- // Tracking pixel endpoint (www and non-www)
177
- { from: "www.facebook.com/tr", to: `${collectPrefix}/meta-tr` },
178
- { from: "facebook.com/tr", to: `${collectPrefix}/meta-tr` },
179
- // Additional Meta tracking domains
180
- { from: "pixel.facebook.com", to: `${collectPrefix}/meta-px` },
181
- { from: "www.facebook.com/plugins", to: `${collectPrefix}/meta-plugins` }
182
- ],
183
- routes: {
184
- [`${collectPrefix}/meta/**`]: { proxy: "https://connect.facebook.net/**" },
185
- [`${collectPrefix}/meta-tr/**`]: { proxy: "https://www.facebook.com/tr/**" },
186
- [`${collectPrefix}/meta-px/**`]: { proxy: "https://pixel.facebook.com/**" },
187
- [`${collectPrefix}/meta-plugins/**`]: { proxy: "https://www.facebook.com/plugins/**" }
322
+ const DEVTOOLS_UI_ROUTE = "/__nuxt-scripts";
323
+ const DEVTOOLS_UI_LOCAL_PORT = 3300;
324
+
325
+ async function setupDevToolsUI(options, resolve, nuxt = useNuxt()) {
326
+ const clientPath = await resolve("./client");
327
+ const isProductionBuild = existsSync(clientPath);
328
+ if (isProductionBuild) {
329
+ nuxt.hook("vite:serverCreated", async (server) => {
330
+ const sirv = await import('sirv').then((r) => r.default || r);
331
+ server.middlewares.use(
332
+ DEVTOOLS_UI_ROUTE,
333
+ sirv(clientPath, { dev: true, single: true })
334
+ );
335
+ });
336
+ } else {
337
+ extendViteConfig((config) => {
338
+ config.server = config.server || {};
339
+ config.server.proxy = config.server.proxy || {};
340
+ config.server.proxy[DEVTOOLS_UI_ROUTE] = {
341
+ target: `http://localhost:${DEVTOOLS_UI_LOCAL_PORT}${DEVTOOLS_UI_ROUTE}`,
342
+ changeOrigin: true,
343
+ followRedirects: true,
344
+ rewrite: (path) => path.replace(DEVTOOLS_UI_ROUTE, "")
345
+ };
346
+ });
347
+ }
348
+ addCustomTab({
349
+ // unique identifier
350
+ name: "nuxt-scripts",
351
+ // title to display in the tab
352
+ title: "Scripts",
353
+ // any icon from Iconify, or a URL to an image
354
+ icon: "carbon:script",
355
+ // iframe view
356
+ view: {
357
+ type: "iframe",
358
+ src: DEVTOOLS_UI_ROUTE
359
+ }
360
+ });
361
+ }
362
+
363
+ const isStackblitz = provider === "stackblitz";
364
+ async function promptToInstall(name, installCommand, options) {
365
+ if (await resolvePackageJSON(name).catch(() => null))
366
+ return true;
367
+ logger$1.info(`Package ${name} is missing`);
368
+ if (isCI)
369
+ return false;
370
+ if (options.prompt === true || options.prompt !== false && !isStackblitz) {
371
+ const confirm = await logger$1.prompt(`Do you want to install ${name} package?`, {
372
+ type: "confirm",
373
+ name: "confirm",
374
+ initial: true
375
+ });
376
+ if (!confirm)
377
+ return false;
378
+ }
379
+ logger$1.info(`Installing ${name}...`);
380
+ try {
381
+ await installCommand();
382
+ logger$1.success(`Installed ${name}`);
383
+ return true;
384
+ } catch (err) {
385
+ logger$1.error(err);
386
+ return false;
387
+ }
388
+ }
389
+ const installPrompts = /* @__PURE__ */ new Set();
390
+ function installNuxtModule(name, options) {
391
+ if (installPrompts.has(name))
392
+ return;
393
+ installPrompts.add(name);
394
+ const nuxt = tryUseNuxt();
395
+ if (!nuxt)
396
+ return;
397
+ return promptToInstall(name, async () => {
398
+ const { runCommand } = await import(String("nuxi"));
399
+ await runCommand("module", ["add", name, "--cwd", nuxt.options.rootDir]);
400
+ }, { rootDir: nuxt.options.rootDir, searchPaths: nuxt.options.modulesDir, ...options });
401
+ }
402
+
403
+ function normalizeRegistryConfig(registry) {
404
+ for (const key of Object.keys(registry)) {
405
+ const entry = registry[key];
406
+ if (!entry) {
407
+ delete registry[key];
408
+ continue;
409
+ }
410
+ if (entry === true) {
411
+ registry[key] = [{}];
412
+ } else if (entry === "mock") {
413
+ registry[key] = [{}, { trigger: "manual", skipValidation: true }];
414
+ } else if (Array.isArray(entry)) {
415
+ if (!entry[0] && !entry[1]) {
416
+ delete registry[key];
417
+ continue;
188
418
  }
189
- },
190
- tiktokPixel: {
191
- // TikTok: untrusted ad network full anonymization
192
- privacy: { ip: true, userAgent: true, language: true, screen: true, timezone: true, hardware: true },
193
- rewrite: [
194
- { from: "analytics.tiktok.com", to: `${collectPrefix}/tiktok` }
195
- ],
196
- routes: {
197
- [`${collectPrefix}/tiktok/**`]: { proxy: "https://analytics.tiktok.com/**" }
419
+ if (!entry[0])
420
+ entry[0] = {};
421
+ } else if (typeof entry === "object") {
422
+ registry[key] = [entry];
423
+ } else {
424
+ delete registry[key];
425
+ }
426
+ }
427
+ }
428
+
429
+ function isVue(id, opts = {}) {
430
+ const { search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
431
+ if (id.endsWith(".vue") && !search) {
432
+ return true;
433
+ }
434
+ if (!search) {
435
+ return false;
436
+ }
437
+ const query = parseQuery(search);
438
+ if (query.nuxt_component) {
439
+ return false;
440
+ }
441
+ if (query.macro && (search === "?macro=true" || !opts.type || opts.type.includes("script"))) {
442
+ return true;
443
+ }
444
+ const type = "setup" in query ? "script" : query.type;
445
+ if (!("vue" in query) || opts.type && !opts.type.includes(type)) {
446
+ return false;
447
+ }
448
+ return true;
449
+ }
450
+ const JS_RE$1 = /\.(?:[cm]?j|t)sx?$/;
451
+ function isJS(id) {
452
+ const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href));
453
+ return JS_RE$1.test(pathname);
454
+ }
455
+
456
+ const VUE_RE$1 = /\.vue/;
457
+ function NuxtScriptsCheckScripts() {
458
+ return createUnplugin(() => {
459
+ return {
460
+ name: "nuxt-scripts:check-scripts",
461
+ transform: {
462
+ filter: {
463
+ id: VUE_RE$1
464
+ },
465
+ handler(code, id) {
466
+ if (!isVue(id, { type: ["script"] }))
467
+ return;
468
+ if (!code.includes("useScript"))
469
+ return;
470
+ let nameNode;
471
+ let errorNode;
472
+ parseAndWalk(code, id, (_node) => {
473
+ if (_node.type === "VariableDeclaration" && _node.declarations?.[0]?.id?.type === "ObjectPattern") {
474
+ const objPattern = _node.declarations[0]?.id;
475
+ for (const property of objPattern.properties) {
476
+ if (property.type === "Property" && property.key.type === "Identifier" && property.key.name === "$script" && property.value.type === "Identifier") {
477
+ nameNode = _node;
478
+ }
479
+ }
480
+ }
481
+ if (nameNode) {
482
+ let sequence = _node.type === "SequenceExpression" ? _node : null;
483
+ let assignmentExpression;
484
+ if (_node.type === "VariableDeclaration") {
485
+ if (_node.declarations[0]?.init?.type === "SequenceExpression") {
486
+ sequence = _node.declarations[0]?.init;
487
+ assignmentExpression = _node.declarations[0]?.init?.expressions?.[0];
488
+ }
489
+ }
490
+ if (sequence && !assignmentExpression) {
491
+ assignmentExpression = sequence.expressions[0]?.type === "AssignmentExpression" ? sequence.expressions[0] : null;
492
+ }
493
+ if (assignmentExpression) {
494
+ const right = assignmentExpression?.right;
495
+ if (right.callee?.name === "_withAsyncContext") {
496
+ if (right.arguments[0]?.body?.name === "$script" || right.arguments[0]?.body?.callee?.object?.name === "$script") {
497
+ errorNode = nameNode;
498
+ }
499
+ }
500
+ }
501
+ }
502
+ });
503
+ if (errorNode) {
504
+ return this.error(new Error("You can't use a top-level await on $script as it will never resolve."));
505
+ }
506
+ }
198
507
  }
199
- },
200
- segment: {
201
- // Segment: trusted data pipeline — needs maximum fidelity for downstream destinations
202
- privacy: { ip: false, userAgent: false, language: false, screen: false, timezone: false, hardware: false },
203
- rewrite: [
204
- { from: "api.segment.io", to: `${collectPrefix}/segment` },
205
- { from: "cdn.segment.com", to: `${collectPrefix}/segment-cdn` }
206
- ],
207
- routes: {
208
- [`${collectPrefix}/segment/**`]: { proxy: "https://api.segment.io/**" },
209
- [`${collectPrefix}/segment-cdn/**`]: { proxy: "https://cdn.segment.com/**" }
508
+ };
509
+ });
510
+ }
511
+
512
+ const WORD_OR_DOLLAR_RE = /[\w$]/;
513
+ const BLANK_CANVAS_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIHWNgAAIABQABNjN9GQAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAA0lEQVQI12NgYGBgAAAABQABXvMqOgAAAABJRU5ErkJggg==";
514
+ function isPropertyKeyAST(parent, ctx) {
515
+ return parent?.type === "Property" && ctx.key === "key" || parent?.type === "SwitchCase" && ctx.key === "test";
516
+ }
517
+ function matchAndRewrite(value, rewrites) {
518
+ for (const { from, to } of rewrites) {
519
+ const isSuffixMatch = from.startsWith(".");
520
+ const fromSlashIdx = from.indexOf("/");
521
+ const fromHost = fromSlashIdx > 0 ? from.slice(0, fromSlashIdx) : from;
522
+ const fromPath = fromSlashIdx > 0 ? from.slice(fromSlashIdx) : "";
523
+ if (!value.includes(fromHost))
524
+ continue;
525
+ const url = parseURL(value);
526
+ let shouldRewrite = false;
527
+ let rewriteSuffix = "";
528
+ if (url.host) {
529
+ const hostMatches = isSuffixMatch ? url.host.endsWith(fromHost) : url.host === fromHost;
530
+ if (hostMatches) {
531
+ const fullPath = url.pathname + (url.search || "") + (url.hash || "");
532
+ if (fromPath && fullPath.startsWith(fromPath)) {
533
+ shouldRewrite = true;
534
+ rewriteSuffix = fullPath.slice(fromPath.length);
535
+ } else if (!fromPath) {
536
+ shouldRewrite = true;
537
+ rewriteSuffix = fullPath;
538
+ }
210
539
  }
211
- },
212
- xPixel: {
213
- // X/Twitter: untrusted ad network full anonymization
214
- privacy: { ip: true, userAgent: true, language: true, screen: true, timezone: true, hardware: true },
215
- rewrite: [
216
- { from: "analytics.twitter.com", to: `${collectPrefix}/x` },
217
- { from: "t.co", to: `${collectPrefix}/x-t` }
218
- ],
219
- routes: {
220
- [`${collectPrefix}/x/**`]: { proxy: "https://analytics.twitter.com/**" },
221
- [`${collectPrefix}/x-t/**`]: { proxy: "https://t.co/**" }
540
+ } else if (value.startsWith("//")) {
541
+ const hostPart = value.slice(2).split("/")[0];
542
+ const hostMatches = isSuffixMatch ? hostPart?.endsWith(fromHost) ?? false : hostPart === fromHost;
543
+ if (hostMatches) {
544
+ const remainder = value.slice(2 + (hostPart?.length ?? 0));
545
+ if (fromPath && remainder.startsWith(fromPath)) {
546
+ shouldRewrite = true;
547
+ rewriteSuffix = remainder.slice(fromPath.length);
548
+ } else if (!fromPath) {
549
+ shouldRewrite = true;
550
+ rewriteSuffix = remainder;
551
+ }
222
552
  }
223
- },
224
- snapchatPixel: {
225
- // Snapchat: untrusted ad network — full anonymization
226
- privacy: { ip: true, userAgent: true, language: true, screen: true, timezone: true, hardware: true },
227
- rewrite: [
228
- { from: "tr.snapchat.com", to: `${collectPrefix}/snap` }
229
- ],
230
- routes: {
231
- [`${collectPrefix}/snap/**`]: { proxy: "https://tr.snapchat.com/**" }
553
+ } else if (fromPath && (value.startsWith(from) || isSuffixMatch && value.includes(from))) {
554
+ const domainEnd = value.indexOf(from) + from.length;
555
+ const nextChar = value[domainEnd];
556
+ if (!nextChar || nextChar === "/" || nextChar === "?" || nextChar === "#") {
557
+ shouldRewrite = true;
558
+ rewriteSuffix = value.slice(domainEnd);
232
559
  }
233
- },
234
- redditPixel: {
235
- // Reddit: untrusted ad network full anonymization
236
- privacy: { ip: true, userAgent: true, language: true, screen: true, timezone: true, hardware: true },
237
- rewrite: [
238
- { from: "alb.reddit.com", to: `${collectPrefix}/reddit` }
239
- ],
240
- routes: {
241
- [`${collectPrefix}/reddit/**`]: { proxy: "https://alb.reddit.com/**" }
560
+ }
561
+ if (shouldRewrite) {
562
+ return rewriteSuffix === "/" || rewriteSuffix.startsWith("?") || rewriteSuffix.startsWith("#") ? to + rewriteSuffix : joinURL(to, rewriteSuffix);
563
+ }
564
+ }
565
+ return null;
566
+ }
567
+ const WINDOW_GLOBALS = /* @__PURE__ */ new Set(["window", "self", "globalThis"]);
568
+ const NAVIGATOR_GLOBALS = /* @__PURE__ */ new Set(["navigator"]);
569
+ function resolveToGlobal(name, scopeTracker, depth = 0) {
570
+ if (depth > 10)
571
+ return null;
572
+ const decl = scopeTracker.getDeclaration(name);
573
+ if (!decl)
574
+ return name;
575
+ if (decl instanceof ScopeTrackerFunctionParam)
576
+ return null;
577
+ if (decl instanceof ScopeTrackerVariable) {
578
+ const declarators = decl.variableNode.declarations;
579
+ if (!declarators)
580
+ return null;
581
+ for (const declarator of declarators) {
582
+ const id = declarator.id;
583
+ if (!id || id.name !== name)
584
+ continue;
585
+ const init = declarator.init;
586
+ if (!init)
587
+ return null;
588
+ if (init.type === "Identifier")
589
+ return resolveToGlobal(init.name, scopeTracker, depth + 1);
590
+ if (init.type === "MemberExpression" && init.object?.type === "Identifier") {
591
+ const memberProp = init.computed ? init.property?.type === "Literal" && typeof init.property.value === "string" ? init.property.value : null : init.property?.type === "Identifier" ? init.property.name : null;
592
+ if (!memberProp)
593
+ return null;
594
+ const objGlobal = resolveToGlobal(init.object.name, scopeTracker, depth + 1);
595
+ if (!objGlobal)
596
+ return null;
597
+ if (WINDOW_GLOBALS.has(objGlobal) || objGlobal === "document")
598
+ return memberProp;
599
+ return null;
242
600
  }
243
- },
244
- clarity: {
245
- // Clarity: screen/UA/timezone needed for heatmaps and device filtering; rest anonymized
246
- privacy: { ip: true, userAgent: false, language: true, screen: false, timezone: false, hardware: true },
247
- rewrite: [
248
- // Main clarity domain
249
- { from: "www.clarity.ms", to: `${collectPrefix}/clarity` },
250
- // Script loader (the actual SDK is loaded from here)
251
- { from: "scripts.clarity.ms", to: `${collectPrefix}/clarity-scripts` },
252
- // Data collection endpoint
253
- { from: "d.clarity.ms", to: `${collectPrefix}/clarity-data` },
254
- // Event collection endpoint
255
- { from: "e.clarity.ms", to: `${collectPrefix}/clarity-events` }
256
- ],
257
- routes: {
258
- [`${collectPrefix}/clarity/**`]: { proxy: "https://www.clarity.ms/**" },
259
- [`${collectPrefix}/clarity-scripts/**`]: { proxy: "https://scripts.clarity.ms/**" },
260
- [`${collectPrefix}/clarity-data/**`]: { proxy: "https://d.clarity.ms/**" },
261
- [`${collectPrefix}/clarity-events/**`]: { proxy: "https://e.clarity.ms/**" }
601
+ return null;
602
+ }
603
+ }
604
+ return null;
605
+ }
606
+ function resolveCalleeTarget(callee, scopeTracker) {
607
+ if (callee?.type !== "MemberExpression")
608
+ return null;
609
+ const propName = callee.computed ? callee.property?.type === "Literal" && typeof callee.property.value === "string" ? callee.property.value : null : callee.property?.name;
610
+ if (!propName)
611
+ return null;
612
+ const obj = callee.object;
613
+ if (!obj || obj.type !== "Identifier")
614
+ return null;
615
+ const resolved = resolveToGlobal(obj.name, scopeTracker);
616
+ if (!resolved)
617
+ return null;
618
+ if (propName === "fetch" && WINDOW_GLOBALS.has(resolved))
619
+ return "fetch";
620
+ if (propName === "sendBeacon" && (NAVIGATOR_GLOBALS.has(resolved) || WINDOW_GLOBALS.has(resolved)))
621
+ return "sendBeacon";
622
+ if (propName === "sendBeacon" && resolved === "navigator")
623
+ return "sendBeacon";
624
+ if (propName === "XMLHttpRequest" && WINDOW_GLOBALS.has(resolved))
625
+ return "XMLHttpRequest";
626
+ if (propName === "Image" && WINDOW_GLOBALS.has(resolved))
627
+ return "Image";
628
+ return null;
629
+ }
630
+ function rewriteScriptUrlsAST(content, filename, rewrites, postProcess, options) {
631
+ const s = new MagicString(content);
632
+ function needsLeadingSpace(start) {
633
+ const prev = content[start - 1];
634
+ return prev && WORD_OR_DOLLAR_RE.test(prev) ? " " : "";
635
+ }
636
+ const scopeTracker = new ScopeTracker({ preserveExitedScopes: true });
637
+ const { program } = parseAndWalk(content, filename, { scopeTracker });
638
+ scopeTracker.freeze();
639
+ walk(program, {
640
+ scopeTracker,
641
+ enter(node, parent, ctx) {
642
+ if (node.type === "Literal" && typeof node.value === "string") {
643
+ const value = node.value;
644
+ const rewritten = matchAndRewrite(value, rewrites);
645
+ if (rewritten === null)
646
+ return;
647
+ const quote = content[node.start];
648
+ if (isPropertyKeyAST(parent, ctx)) {
649
+ s.overwrite(node.start, node.end, quote + rewritten + quote);
650
+ } else {
651
+ s.overwrite(node.start, node.end, `${needsLeadingSpace(node.start)}self.location.origin+${quote}${rewritten}${quote}`);
652
+ }
262
653
  }
263
- },
264
- posthog: {
265
- // No rewrites needed - PostHog uses NPM mode, SDK URLs are set via api_host config
266
- // PostHog: needs real IP for GeoIP enrichment + feature flag targeting
267
- privacy: { ip: false, userAgent: false, language: false, screen: false, timezone: false, hardware: false },
268
- routes: {
269
- // US region
270
- [`${collectPrefix}/ph/static/**`]: { proxy: "https://us-assets.i.posthog.com/static/**" },
271
- [`${collectPrefix}/ph/**`]: { proxy: "https://us.i.posthog.com/**" },
272
- // EU region
273
- [`${collectPrefix}/ph-eu/static/**`]: { proxy: "https://eu-assets.i.posthog.com/static/**" },
274
- [`${collectPrefix}/ph-eu/**`]: { proxy: "https://eu.i.posthog.com/**" }
654
+ if (node.type === "TemplateLiteral") {
655
+ const quasis = node.quasis;
656
+ const expressions = node.expressions;
657
+ if (expressions?.length === 0 && quasis?.length === 1) {
658
+ const value = quasis[0].value?.cooked ?? quasis[0].value?.raw;
659
+ if (typeof value !== "string")
660
+ return;
661
+ const rewritten = matchAndRewrite(value, rewrites);
662
+ if (rewritten === null)
663
+ return;
664
+ if (isPropertyKeyAST(parent, ctx)) {
665
+ s.overwrite(node.start, node.end, `\`${rewritten}\``);
666
+ } else {
667
+ s.overwrite(node.start, node.end, `${needsLeadingSpace(node.start)}self.location.origin+\`${rewritten}\``);
668
+ }
669
+ } else if (expressions?.length > 0 && quasis?.length > 0) {
670
+ const firstQuasi = quasis[0];
671
+ const value = firstQuasi.value?.cooked ?? firstQuasi.value?.raw;
672
+ if (typeof value !== "string" || isPropertyKeyAST(parent, ctx))
673
+ return;
674
+ const rewritten = matchAndRewrite(value, rewrites);
675
+ if (rewritten === null)
676
+ return;
677
+ s.overwrite(node.start, firstQuasi.end, `${needsLeadingSpace(node.start)}self.location.origin+\`${rewritten}`);
678
+ }
275
679
  }
276
- },
277
- hotjar: {
278
- // Hotjar: screen/UA/timezone needed for heatmaps and device segmentation; rest anonymized
279
- privacy: { ip: true, userAgent: false, language: true, screen: false, timezone: false, hardware: true },
280
- rewrite: [
281
- // Static assets
282
- { from: "static.hotjar.com", to: `${collectPrefix}/hotjar` },
283
- // Script loader (bootstrap script loads the main SDK from here)
284
- { from: "script.hotjar.com", to: `${collectPrefix}/hotjar-script` },
285
- // Configuration/variables
286
- { from: "vars.hotjar.com", to: `${collectPrefix}/hotjar-vars` },
287
- // Data ingestion endpoint
288
- { from: "in.hotjar.com", to: `${collectPrefix}/hotjar-in` },
289
- // Video capture
290
- { from: "vc.hotjar.com", to: `${collectPrefix}/hotjar-vc` },
291
- // Metrics/telemetry
292
- { from: "metrics.hotjar.io", to: `${collectPrefix}/hotjar-metrics` },
293
- // Insights (ContentSquare integration)
294
- { from: "insights.hotjar.com", to: `${collectPrefix}/hotjar-insights` }
295
- ],
296
- routes: {
297
- [`${collectPrefix}/hotjar/**`]: { proxy: "https://static.hotjar.com/**" },
298
- [`${collectPrefix}/hotjar-script/**`]: { proxy: "https://script.hotjar.com/**" },
299
- [`${collectPrefix}/hotjar-vars/**`]: { proxy: "https://vars.hotjar.com/**" },
300
- [`${collectPrefix}/hotjar-in/**`]: { proxy: "https://in.hotjar.com/**" },
301
- [`${collectPrefix}/hotjar-vc/**`]: { proxy: "https://vc.hotjar.com/**" },
302
- [`${collectPrefix}/hotjar-metrics/**`]: { proxy: "https://metrics.hotjar.io/**" },
303
- [`${collectPrefix}/hotjar-insights/**`]: { proxy: "https://insights.hotjar.com/**" }
680
+ if (node.type === "CallExpression" && !options?.skipApiRewrites) {
681
+ const callee = node.callee;
682
+ const canvasPropName = callee?.type === "MemberExpression" ? callee.computed ? callee.property?.type === "Literal" && typeof callee.property.value === "string" ? callee.property.value : null : callee.property?.name : null;
683
+ if (canvasPropName === "toDataURL" && callee.object) {
684
+ const blankCanvas = `"${BLANK_CANVAS_DATA_URL}"`;
685
+ if (callee.object.type === "Identifier") {
686
+ const decl = scopeTracker.getDeclaration(callee.object.name);
687
+ if (decl instanceof ScopeTrackerFunction || decl instanceof ScopeTrackerIdentifier) ; else {
688
+ s.overwrite(node.start, node.end, blankCanvas);
689
+ return;
690
+ }
691
+ } else {
692
+ s.overwrite(node.start, node.end, blankCanvas);
693
+ return;
694
+ }
695
+ }
696
+ if (canvasPropName === "getExtension") {
697
+ const args = node.arguments;
698
+ if (args?.length === 1 && args[0]?.type === "Literal" && args[0].value === "WEBGL_debug_renderer_info") {
699
+ s.overwrite(node.start, node.end, "null");
700
+ return;
701
+ }
702
+ }
703
+ if (callee?.type === "Identifier" && callee.name === "fetch") {
704
+ if (!scopeTracker.getDeclaration("fetch"))
705
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.fetch");
706
+ return;
707
+ }
708
+ const target = resolveCalleeTarget(callee, scopeTracker);
709
+ if (target === "fetch" || target === "sendBeacon") {
710
+ s.overwrite(callee.start, callee.end, `__nuxtScripts.${target}`);
711
+ return;
712
+ }
713
+ if (callee?.type === "MemberExpression" && !callee.computed && callee.property?.name === "sendBeacon" && callee.object?.type === "Identifier") {
714
+ const resolved = resolveToGlobal(callee.object.name, scopeTracker);
715
+ if (resolved === null) {
716
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.sendBeacon");
717
+ }
718
+ }
304
719
  }
305
- }
306
- };
307
- }
308
- function getProxyConfig(key, collectPrefix) {
309
- const configs = buildProxyConfig(collectPrefix);
310
- return configs[key];
311
- }
312
- function getAllProxyConfigs(collectPrefix) {
313
- return buildProxyConfig(collectPrefix);
314
- }
315
- function getSWInterceptRules(collectPrefix) {
316
- const configs = buildProxyConfig(collectPrefix);
317
- const rules = [];
318
- for (const config of Object.values(configs)) {
319
- if (!config.routes)
320
- continue;
321
- for (const [localPath, { proxy }] of Object.entries(config.routes)) {
322
- const match = proxy.match(/^https?:\/\/([^/]+)(\/.*)?\/\*\*$/);
323
- if (match?.[1]) {
324
- const domain = match[1];
325
- const pathPrefix = match[2] || "";
326
- const target = localPath.replace(/\/\*\*$/, "");
327
- rules.push({ pattern: domain, pathPrefix, target });
720
+ if (node.type === "NewExpression" && !options?.skipApiRewrites) {
721
+ const callee = node.callee;
722
+ if (callee?.type === "Identifier" && callee.name === "XMLHttpRequest") {
723
+ if (!scopeTracker.getDeclaration("XMLHttpRequest"))
724
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.XMLHttpRequest");
725
+ return;
726
+ }
727
+ if (callee?.type === "Identifier" && callee.name === "Image") {
728
+ if (!scopeTracker.getDeclaration("Image"))
729
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.Image");
730
+ return;
731
+ }
732
+ const target = resolveCalleeTarget(callee, scopeTracker);
733
+ if (target === "XMLHttpRequest" || target === "Image") {
734
+ s.overwrite(callee.start, callee.end, `__nuxtScripts.${target}`);
735
+ return;
736
+ }
737
+ if (callee?.type === "MemberExpression" && callee.object?.type === "Identifier") {
738
+ const propName = callee.computed ? callee.property?.type === "Literal" && typeof callee.property.value === "string" ? callee.property.value : null : callee.property?.name;
739
+ if (propName === "XMLHttpRequest" || propName === "Image") {
740
+ const resolved = resolveToGlobal(callee.object.name, scopeTracker);
741
+ if (resolved === null) {
742
+ s.overwrite(callee.start, callee.end, `__nuxtScripts.${propName}`);
743
+ }
744
+ }
745
+ }
328
746
  }
329
747
  }
748
+ });
749
+ let output = s.toString();
750
+ if (postProcess) {
751
+ output = postProcess(output, rewrites);
330
752
  }
331
- return rules;
332
- }
333
-
334
- function isVue(id, opts = {}) {
335
- const { search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
336
- if (id.endsWith(".vue") && !search) {
337
- return true;
338
- }
339
- if (!search) {
340
- return false;
341
- }
342
- const query = parseQuery(search);
343
- if (query.nuxt_component) {
344
- return false;
345
- }
346
- if (query.macro && (search === "?macro=true" || !opts.type || opts.type.includes("script"))) {
347
- return true;
348
- }
349
- const type = "setup" in query ? "script" : query.type;
350
- if (!("vue" in query) || opts.type && !opts.type.includes(type)) {
351
- return false;
352
- }
353
- return true;
354
- }
355
- const JS_RE = /\.(?:[cm]?j|t)sx?$/;
356
- function isJS(id) {
357
- const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href));
358
- return JS_RE.test(pathname);
753
+ return output;
359
754
  }
360
755
 
361
756
  const SEVEN_DAYS_IN_MS = 7 * 24 * 60 * 60 * 1e3;
757
+ const PROTOCOL_RELATIVE_RE = /^\/\//;
758
+ const VUE_RE = /\.vue/;
759
+ const JS_RE = /\.[cm]?[jt]sx?$/;
760
+ const TEST_RE = /\.(?:test|spec)\./;
761
+ const UPPERCASE_RE = /^[A-Z]$/;
762
+ const USE_SCRIPT_RE = /^useScript/;
362
763
  function calculateIntegrity(content, algorithm = "sha384") {
363
764
  const hash = createHash(algorithm).update(content).digest("base64");
364
765
  return `${algorithm}-${hash}`;
@@ -371,14 +772,12 @@ async function isCacheExpired(storage, filename, cacheMaxAge = SEVEN_DAYS_IN_MS)
371
772
  }
372
773
  return Date.now() - meta.timestamp > cacheMaxAge;
373
774
  }
374
- function normalizeScriptData(src, assetsBaseURL = "/_scripts") {
775
+ function normalizeScriptData(src, assetsBaseURL = "/_scripts/assets") {
375
776
  if (hasProtocol(src, { acceptRelative: true })) {
376
- src = src.replace(/^\/\//, "https://");
777
+ src = src.replace(PROTOCOL_RELATIVE_RE, "https://");
377
778
  const url = parseURL(src);
378
- const file = [
379
- `${hash(url)}.js`
380
- // force an extension
381
- ].filter(Boolean).join("-");
779
+ const h = hash(url);
780
+ const file = `${h.startsWith("-") ? `_${h.slice(1)}` : h}.js`;
382
781
  const nuxt = tryUseNuxt();
383
782
  const cdnURL = nuxt?.options.runtimeConfig?.app?.cdnURL || nuxt?.options.app?.cdnURL || "";
384
783
  const baseURL = cdnURL || nuxt?.options.app.baseURL || "";
@@ -387,7 +786,7 @@ function normalizeScriptData(src, assetsBaseURL = "/_scripts") {
387
786
  return { url: src };
388
787
  }
389
788
  async function downloadScript(opts, renderedScript, fetchOptions, cacheMaxAge) {
390
- const { src, url, filename, forceDownload, integrity, proxyRewrites } = opts;
789
+ const { src, url, filename, forceDownload, integrity, proxyRewrites, postProcess, skipApiRewrites } = opts;
391
790
  if (src === url || !filename) {
392
791
  return;
393
792
  }
@@ -425,7 +824,7 @@ async function downloadScript(opts, renderedScript, fetchOptions, cacheMaxAge) {
425
824
  await storage.setItemRaw(`bundle:${filename}`, res);
426
825
  if (proxyRewrites?.length && res) {
427
826
  const content = res.toString("utf-8");
428
- const rewritten = rewriteScriptUrls(content, proxyRewrites);
827
+ const rewritten = rewriteScriptUrlsAST(content, filename || "script.js", proxyRewrites, postProcess, { skipApiRewrites });
429
828
  res = Buffer.from(rewritten, "utf-8");
430
829
  logger.debug(`Rewrote ${proxyRewrites.length} URL patterns in ${filename}`);
431
830
  }
@@ -482,8 +881,8 @@ function NuxtScriptBundleTransformer(options = {
482
881
  transform: {
483
882
  filter: {
484
883
  id: {
485
- include: [/\.vue/, /\.[cm]?[jt]sx?$/],
486
- exclude: [/\.(?:test|spec)\./]
884
+ include: [VUE_RE, JS_RE],
885
+ exclude: [TEST_RE]
487
886
  }
488
887
  },
489
888
  async handler(code, id) {
@@ -493,11 +892,11 @@ function NuxtScriptBundleTransformer(options = {
493
892
  return;
494
893
  const s = new MagicString(code);
495
894
  const deferredOps = [];
496
- parseAndWalk(code, id, function(_node) {
895
+ parseAndWalk(code, id, (_node) => {
497
896
  const calleeName = _node.callee?.name;
498
897
  if (!calleeName)
499
898
  return;
500
- const isValidCallee = calleeName === "useScript" || calleeName?.startsWith("useScript") && /^[A-Z]$/.test(calleeName?.charAt(9)) && !calleeName.startsWith("useScriptTrigger") && !calleeName.startsWith("useScriptEvent");
899
+ const isValidCallee = calleeName === "useScript" || calleeName?.startsWith("useScript") && UPPERCASE_RE.test(calleeName?.charAt(9)) && !calleeName.startsWith("useScriptTrigger") && !calleeName.startsWith("useScriptEvent");
501
900
  if (_node.type === "CallExpression" && _node.callee.type === "Identifier" && isValidCallee) {
502
901
  const fnName = _node.callee?.name;
503
902
  const node = _node;
@@ -505,7 +904,7 @@ function NuxtScriptBundleTransformer(options = {
505
904
  let src;
506
905
  let registryKey;
507
906
  if (fnName !== "useScript") {
508
- const baseName = fnName.replace(/^useScript/, "");
907
+ const baseName = fnName.replace(USE_SCRIPT_RE, "");
509
908
  registryKey = baseName.length > 0 ? baseName.charAt(0).toLowerCase() + baseName.slice(1) : void 0;
510
909
  }
511
910
  if (fnName === "useScript") {
@@ -625,11 +1024,17 @@ function NuxtScriptBundleTransformer(options = {
625
1024
  const { url: _url, filename } = normalizeScriptData(src, options.assetsBaseURL);
626
1025
  const script = options.scripts?.find((s2) => s2.import.name === fnName);
627
1026
  const proxyConfigKey = script?.proxy !== false ? script?.proxy || registryKey : void 0;
628
- const proxyRewrites = options.firstPartyEnabled && !firstPartyOptOut && proxyConfigKey && options.firstPartyCollectPrefix ? getProxyConfig(proxyConfigKey, options.firstPartyCollectPrefix)?.rewrite : void 0;
1027
+ const proxyConfig = !firstPartyOptOut && proxyConfigKey ? options.proxyConfigs?.[proxyConfigKey] : void 0;
1028
+ const proxyRewrites = proxyConfig?.domains?.map((domain) => ({
1029
+ from: domain,
1030
+ to: `${options.proxyPrefix}/${domain}`
1031
+ }));
1032
+ const postProcess = proxyConfig?.postProcess;
1033
+ const skipApiRewrites = !!(registryKey && options.partytownScripts?.has(registryKey));
629
1034
  deferredOps.push(async () => {
630
1035
  let url = _url;
631
1036
  try {
632
- await downloadScript({ src, url, filename, forceDownload, proxyRewrites, integrity: options.integrity }, renderedScript, options.fetchOptions, options.cacheMaxAge);
1037
+ await downloadScript({ src, url, filename, forceDownload, proxyRewrites, postProcess, integrity: options.integrity, skipApiRewrites }, renderedScript, options.fetchOptions, options.cacheMaxAge);
633
1038
  } catch (e) {
634
1039
  if (options.fallbackOnSrcOnBundleFail) {
635
1040
  logger.warn(`[Nuxt Scripts: Bundle Transformer] Failed to bundle ${src}. Fallback to remote loading.`);
@@ -685,7 +1090,7 @@ function NuxtScriptBundleTransformer(options = {
685
1090
  s.appendRight(node.arguments[0].start + 1, ` scriptInput: { src: '${url}'${integrityProps} }, `);
686
1091
  }
687
1092
  } else {
688
- s.appendRight(node.callee.end, `({ scriptInput: { src: '${url}'${integrityProps} } })`);
1093
+ s.overwrite(node.callee.end, node.end, `({ scriptInput: { src: '${url}'${integrityProps} } })`);
689
1094
  }
690
1095
  }
691
1096
  });
@@ -709,101 +1114,7 @@ function NuxtScriptBundleTransformer(options = {
709
1114
  });
710
1115
  }
711
1116
 
712
- const isStackblitz = provider === "stackblitz";
713
- async function promptToInstall(name, installCommand, options) {
714
- if (await resolvePackageJSON(name).catch(() => null))
715
- return true;
716
- logger$1.info(`Package ${name} is missing`);
717
- if (isCI)
718
- return false;
719
- if (options.prompt === true || options.prompt !== false && !isStackblitz) {
720
- const confirm = await logger$1.prompt(`Do you want to install ${name} package?`, {
721
- type: "confirm",
722
- name: "confirm",
723
- initial: true
724
- });
725
- if (!confirm)
726
- return false;
727
- }
728
- logger$1.info(`Installing ${name}...`);
729
- try {
730
- await installCommand();
731
- logger$1.success(`Installed ${name}`);
732
- return true;
733
- } catch (err) {
734
- logger$1.error(err);
735
- return false;
736
- }
737
- }
738
- const installPrompts = /* @__PURE__ */ new Set();
739
- function installNuxtModule(name, options) {
740
- if (installPrompts.has(name))
741
- return;
742
- installPrompts.add(name);
743
- const nuxt = tryUseNuxt();
744
- if (!nuxt)
745
- return;
746
- return promptToInstall(name, async () => {
747
- const { runCommand } = await import(String("nuxi"));
748
- await runCommand("module", ["add", name, "--cwd", nuxt.options.rootDir]);
749
- }, { rootDir: nuxt.options.rootDir, searchPaths: nuxt.options.modulesDir, ...options });
750
- }
751
-
752
- function NuxtScriptsCheckScripts() {
753
- return createUnplugin(() => {
754
- return {
755
- name: "nuxt-scripts:check-scripts",
756
- transform: {
757
- filter: {
758
- id: /\.vue/
759
- },
760
- handler(code, id) {
761
- if (!isVue(id, { type: ["script"] }))
762
- return;
763
- if (!code.includes("useScript"))
764
- return;
765
- let nameNode;
766
- let errorNode;
767
- parseAndWalk(code, id, function(_node) {
768
- if (_node.type === "VariableDeclaration" && _node.declarations?.[0]?.id?.type === "ObjectPattern") {
769
- const objPattern = _node.declarations[0]?.id;
770
- for (const property of objPattern.properties) {
771
- if (property.type === "Property" && property.key.type === "Identifier" && property.key.name === "$script" && property.value.type === "Identifier") {
772
- nameNode = _node;
773
- }
774
- }
775
- }
776
- if (nameNode) {
777
- let sequence = _node.type === "SequenceExpression" ? _node : null;
778
- let assignmentExpression;
779
- if (_node.type === "VariableDeclaration") {
780
- if (_node.declarations[0]?.init?.type === "SequenceExpression") {
781
- sequence = _node.declarations[0]?.init;
782
- assignmentExpression = _node.declarations[0]?.init?.expressions?.[0];
783
- }
784
- }
785
- if (sequence && !assignmentExpression) {
786
- assignmentExpression = sequence.expressions[0]?.type === "AssignmentExpression" ? sequence.expressions[0] : null;
787
- }
788
- if (assignmentExpression) {
789
- const right = assignmentExpression?.right;
790
- if (right.callee?.name === "_withAsyncContext") {
791
- if (right.arguments[0]?.body?.name === "$script" || right.arguments[0]?.body?.callee?.object?.name === "$script") {
792
- errorNode = nameNode;
793
- }
794
- }
795
- }
796
- }
797
- });
798
- if (errorNode) {
799
- return this.error(new Error("You can't use a top-level await on $script as it will never resolve."));
800
- }
801
- }
802
- }
803
- };
804
- });
805
- }
806
-
1117
+ const TRIGGER_PLACEHOLDER_RE = /"__TRIGGER_PLACEHOLDER__"/g;
807
1118
  function registerTypeTemplates({ nuxt, config, newScripts }) {
808
1119
  addTypeTemplate({
809
1120
  filename: "types/nuxt-scripts-augments.d.ts",
@@ -815,7 +1126,7 @@ function registerTypeTemplates({ nuxt, config, newScripts }) {
815
1126
  let augments = `// Generated by @nuxt/scripts
816
1127
  declare module '#app' {
817
1128
  interface NuxtApp {
818
- $scripts: Record<${[...Object.keys(config.globals || {}), ...Object.keys(config.registry || {})].map((k) => `'${k}'`).concat(["string"]).join(" | ")}, import('#nuxt-scripts/types').UseScriptContext<any> | undefined>
1129
+ $scripts: Record<${[...[...Object.keys(config.globals || {}), ...Object.keys(config.registry || {})].map((k) => `'${k}'`), ...["string"]].join(" | ")}, import('#nuxt-scripts/types').UseScriptContext<any> | undefined>
819
1130
  _scripts: Record<string, import('#nuxt-scripts/types').NuxtDevToolsScriptInstance>
820
1131
  }
821
1132
  interface RuntimeNuxtHooks {
@@ -896,28 +1207,30 @@ function templatePlugin(config, registry) {
896
1207
  let needsInteractionImport = false;
897
1208
  let needsServiceWorkerImport = false;
898
1209
  for (const [k, c] of Object.entries(config.registry || {})) {
899
- const importDefinition = registry.find((i) => i.proxy === k || i.import.name === `useScript${k.substring(0, 1).toUpperCase() + k.substring(1)}`);
1210
+ if (c === false)
1211
+ continue;
1212
+ const importDefinition = registry.find((i) => i.import.name.toLowerCase() === `usescript${k.toLowerCase()}`);
900
1213
  if (importDefinition) {
901
1214
  resolvedRegistryKeys.push(k);
902
1215
  imports.unshift(`import { ${importDefinition.import.name} } from '${importDefinition.import.from}'`);
903
- if (c === "mock") {
904
- inits.push(`const ${k} = ${importDefinition.import.name}({ scriptOptions: { trigger: 'manual', skipValidation: true } })`);
905
- } else if (Array.isArray(c) && c.length === 2) {
906
- const input = c[0] || {};
907
- const scriptOptions = { ...c[1] };
908
- const triggerResolved = resolveTriggerForTemplate(scriptOptions?.trigger);
1216
+ const [input, scriptOptions] = c;
1217
+ if (scriptOptions) {
1218
+ const opts = { ...scriptOptions };
1219
+ const triggerResolved = resolveTriggerForTemplate(opts.trigger);
909
1220
  if (triggerResolved) {
910
- scriptOptions.trigger = "__TRIGGER_PLACEHOLDER__";
911
- if (triggerResolved.includes("useScriptTriggerIdleTimeout")) needsIdleTimeoutImport = true;
912
- if (triggerResolved.includes("useScriptTriggerInteraction")) needsInteractionImport = true;
913
- if (triggerResolved.includes("useScriptTriggerServiceWorker")) needsServiceWorkerImport = true;
1221
+ opts.trigger = "__TRIGGER_PLACEHOLDER__";
1222
+ if (triggerResolved.includes("useScriptTriggerIdleTimeout"))
1223
+ needsIdleTimeoutImport = true;
1224
+ if (triggerResolved.includes("useScriptTriggerInteraction"))
1225
+ needsInteractionImport = true;
1226
+ if (triggerResolved.includes("useScriptTriggerServiceWorker"))
1227
+ needsServiceWorkerImport = true;
914
1228
  }
915
- const args = { ...input, scriptOptions };
916
- const argsJson = triggerResolved ? JSON.stringify(args).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved) : JSON.stringify(args);
1229
+ const args = { ...input, scriptOptions: opts };
1230
+ const argsJson = triggerResolved ? JSON.stringify(args).replace(TRIGGER_PLACEHOLDER_RE, triggerResolved) : JSON.stringify(args);
917
1231
  inits.push(`const ${k} = ${importDefinition.import.name}(${argsJson})`);
918
1232
  } else {
919
- const args = (typeof c !== "object" ? {} : c) || {};
920
- inits.push(`const ${k} = ${importDefinition.import.name}(${JSON.stringify(args)})`);
1233
+ inits.push(`const ${k} = ${importDefinition.import.name}(${JSON.stringify(input)})`);
921
1234
  }
922
1235
  }
923
1236
  }
@@ -928,11 +1241,14 @@ function templatePlugin(config, registry) {
928
1241
  const options = c[1];
929
1242
  const triggerResolved = resolveTriggerForTemplate(options?.trigger);
930
1243
  if (triggerResolved) {
931
- if (triggerResolved.includes("useScriptTriggerIdleTimeout")) needsIdleTimeoutImport = true;
932
- if (triggerResolved.includes("useScriptTriggerInteraction")) needsInteractionImport = true;
933
- if (triggerResolved.includes("useScriptTriggerServiceWorker")) needsServiceWorkerImport = true;
1244
+ if (triggerResolved.includes("useScriptTriggerIdleTimeout"))
1245
+ needsIdleTimeoutImport = true;
1246
+ if (triggerResolved.includes("useScriptTriggerInteraction"))
1247
+ needsInteractionImport = true;
1248
+ if (triggerResolved.includes("useScriptTriggerServiceWorker"))
1249
+ needsServiceWorkerImport = true;
934
1250
  const resolvedOptions = { ...options, trigger: "__TRIGGER_PLACEHOLDER__" };
935
- const optionsJson = JSON.stringify(resolvedOptions).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved);
1251
+ const optionsJson = JSON.stringify(resolvedOptions).replace(TRIGGER_PLACEHOLDER_RE, triggerResolved);
936
1252
  inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...typeof c[0] === "string" ? { src: c[0] } : c[0] })}, { ...${optionsJson}, use: () => ({ ${k}: window.${k} }) })`);
937
1253
  } else {
938
1254
  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} }) })`);
@@ -940,11 +1256,14 @@ function templatePlugin(config, registry) {
940
1256
  } else if (typeof c === "object" && c !== null) {
941
1257
  const triggerResolved = resolveTriggerForTemplate(c.trigger);
942
1258
  if (triggerResolved) {
943
- if (triggerResolved.includes("useScriptTriggerIdleTimeout")) needsIdleTimeoutImport = true;
944
- if (triggerResolved.includes("useScriptTriggerInteraction")) needsInteractionImport = true;
945
- if (triggerResolved.includes("useScriptTriggerServiceWorker")) needsServiceWorkerImport = true;
1259
+ if (triggerResolved.includes("useScriptTriggerIdleTimeout"))
1260
+ needsIdleTimeoutImport = true;
1261
+ if (triggerResolved.includes("useScriptTriggerInteraction"))
1262
+ needsInteractionImport = true;
1263
+ if (triggerResolved.includes("useScriptTriggerServiceWorker"))
1264
+ needsServiceWorkerImport = true;
946
1265
  const resolvedOptions = { ...c, trigger: "__TRIGGER_PLACEHOLDER__" };
947
- const argsJson = JSON.stringify({ key: k, ...resolvedOptions }).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved);
1266
+ const argsJson = JSON.stringify({ key: k, ...resolvedOptions }).replace(TRIGGER_PLACEHOLDER_RE, triggerResolved);
948
1267
  inits.push(`const ${k} = useScript(${argsJson}, { use: () => ({ ${k}: window.${k} }) })`);
949
1268
  } else {
950
1269
  inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...c })}, { use: () => ({ ${k}: window.${k} }) })`);
@@ -973,19 +1292,106 @@ function templatePlugin(config, registry) {
973
1292
  ` parallel: true,`,
974
1293
  ` setup() {`,
975
1294
  ...inits.map((i) => ` ${i}`),
976
- ` return { provide: { $scripts: { ${[...Object.keys(config.globals || {}), ...resolvedRegistryKeys].join(", ")} } } }`,
1295
+ ` return { provide: { scripts: { ${[...Object.keys(config.globals || {}), ...resolvedRegistryKeys].join(", ")} } } }`,
977
1296
  ` }`,
978
1297
  `})`
979
1298
  ].join("\n");
980
1299
  }
981
1300
 
1301
+ const SELF_CLOSING_SCRIPT_RE = /<((?:Script[A-Z]|script-)\w[\w-]*)\b([^>]*?)\/\s*>/g;
1302
+ function fixSelfClosingScriptComponents(nuxt) {
1303
+ function expandTags(content) {
1304
+ SELF_CLOSING_SCRIPT_RE.lastIndex = 0;
1305
+ if (!SELF_CLOSING_SCRIPT_RE.test(content))
1306
+ return null;
1307
+ SELF_CLOSING_SCRIPT_RE.lastIndex = 0;
1308
+ return content.replace(SELF_CLOSING_SCRIPT_RE, (_, tag, attrs) => `<${tag}${attrs.trimEnd()}></${tag}>`);
1309
+ }
1310
+ function fixFile(filePath) {
1311
+ if (!existsSync(filePath))
1312
+ return;
1313
+ const content = readFileSync(filePath, "utf-8");
1314
+ const fixed = expandTags(content);
1315
+ if (fixed)
1316
+ nuxt.vfs[filePath] = fixed;
1317
+ }
1318
+ function scanDir(dir) {
1319
+ if (!existsSync(dir))
1320
+ return;
1321
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1322
+ const fullPath = resolve(dir, entry.name);
1323
+ if (entry.isDirectory())
1324
+ scanDir(fullPath);
1325
+ else if (entry.name.endsWith(".vue"))
1326
+ fixFile(fullPath);
1327
+ }
1328
+ }
1329
+ const pagesDirs = /* @__PURE__ */ new Set();
1330
+ for (const layer of nuxt.options._layers) {
1331
+ pagesDirs.add(resolve(
1332
+ layer.config.srcDir,
1333
+ layer.config.dir?.pages || "pages"
1334
+ ));
1335
+ }
1336
+ for (const dir of pagesDirs) scanDir(dir);
1337
+ if (nuxt.options.dev) {
1338
+ nuxt.hook("builder:watch", (_event, relativePath) => {
1339
+ if (!relativePath.endsWith(".vue"))
1340
+ return;
1341
+ for (const layer of nuxt.options._layers) {
1342
+ const fullPath = resolve(layer.config.srcDir, relativePath);
1343
+ for (const dir of pagesDirs) {
1344
+ if (fullPath.startsWith(`${dir}/`)) {
1345
+ fixFile(fullPath);
1346
+ return;
1347
+ }
1348
+ }
1349
+ }
1350
+ });
1351
+ }
1352
+ }
1353
+ const REGISTRY_ENV_DEFAULTS = {
1354
+ bingUet: { id: "" },
1355
+ clarity: { id: "" },
1356
+ cloudflareWebAnalytics: { token: "" },
1357
+ crisp: { id: "" },
1358
+ databuddyAnalytics: { clientId: "" },
1359
+ fathomAnalytics: { site: "" },
1360
+ googleAdsense: { client: "" },
1361
+ googleAnalytics: { id: "" },
1362
+ googleMaps: { apiKey: "" },
1363
+ googleRecaptcha: { siteKey: "" },
1364
+ googleSignIn: { clientId: "" },
1365
+ googleTagManager: { id: "" },
1366
+ hotjar: { id: "" },
1367
+ intercom: { app_id: "" },
1368
+ matomoAnalytics: { matomoUrl: "" },
1369
+ mixpanelAnalytics: { token: "" },
1370
+ metaPixel: { id: "" },
1371
+ paypal: { clientId: "" },
1372
+ plausibleAnalytics: { domain: "" },
1373
+ posthog: { apiKey: "" },
1374
+ redditPixel: { id: "" },
1375
+ rybbitAnalytics: { siteId: "" },
1376
+ segment: { writeKey: "" },
1377
+ snapchatPixel: { id: "" },
1378
+ stripe: {},
1379
+ tiktokPixel: { id: "" },
1380
+ umamiAnalytics: { websiteId: "" },
1381
+ vercelAnalytics: {},
1382
+ xPixel: { id: "" }
1383
+ };
1384
+ const UPPER_RE = /([A-Z])/g;
1385
+ const toScreamingSnake = (s) => s.replace(UPPER_RE, "_$1").toUpperCase();
982
1386
  const PARTYTOWN_FORWARDS = {
1387
+ bingUet: ["uetq.push"],
983
1388
  googleAnalytics: ["dataLayer.push", "gtag"],
984
- plausible: ["plausible"],
985
- fathom: ["fathom", "fathom.trackEvent", "fathom.trackPageview"],
986
- umami: ["umami", "umami.track"],
987
- matomo: ["_paq.push"],
1389
+ plausibleAnalytics: ["plausible"],
1390
+ fathomAnalytics: ["fathom", "fathom.trackEvent", "fathom.trackPageview"],
1391
+ umamiAnalytics: ["umami", "umami.track"],
1392
+ matomoAnalytics: ["_paq.push"],
988
1393
  segment: ["analytics", "analytics.track", "analytics.page", "analytics.identify"],
1394
+ mixpanelAnalytics: ["mixpanel", "mixpanel.init", "mixpanel.track", "mixpanel.identify", "mixpanel.people.set", "mixpanel.reset", "mixpanel.register"],
989
1395
  metaPixel: ["fbq"],
990
1396
  xPixel: ["twq"],
991
1397
  tiktokPixel: ["ttq.track", "ttq.page", "ttq.identify"],
@@ -1039,39 +1445,51 @@ const module$1 = defineNuxtModule({
1039
1445
  if (unheadVersion?.startsWith("1")) {
1040
1446
  logger.error(`Nuxt Scripts requires Unhead >= 2, you are using v${unheadVersion}. Please run \`nuxi upgrade --clean\` to upgrade...`);
1041
1447
  }
1448
+ if (config.registry) {
1449
+ normalizeRegistryConfig(config.registry);
1450
+ nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
1451
+ const registryWithDefaults = {};
1452
+ for (const [key, entry] of Object.entries(config.registry)) {
1453
+ if (entry === false)
1454
+ continue;
1455
+ const input = entry[0];
1456
+ const envDefaults = REGISTRY_ENV_DEFAULTS[key];
1457
+ if (!envDefaults) {
1458
+ registryWithDefaults[key] = input;
1459
+ continue;
1460
+ }
1461
+ const envResolved = {};
1462
+ for (const [field, defaultValue] of Object.entries(envDefaults)) {
1463
+ const envKey = `NUXT_PUBLIC_SCRIPTS_${toScreamingSnake(key)}_${toScreamingSnake(field)}`;
1464
+ envResolved[field] = process.env[envKey] || defaultValue;
1465
+ }
1466
+ registryWithDefaults[key] = defu(input, envResolved);
1467
+ }
1468
+ nuxt.options.runtimeConfig.public.scripts = defu(
1469
+ nuxt.options.runtimeConfig.public.scripts || {},
1470
+ registryWithDefaults
1471
+ );
1472
+ }
1473
+ const googleMapsEnabled = config.googleStaticMapsProxy?.enabled || !!config.registry?.googleMaps;
1042
1474
  nuxt.options.runtimeConfig["nuxt-scripts"] = {
1043
1475
  version,
1044
1476
  // Private proxy config with API key (server-side only)
1045
- googleStaticMapsProxy: config.googleStaticMapsProxy?.enabled ? { apiKey: nuxt.options.runtimeConfig.public.scripts?.googleMaps?.apiKey } : void 0,
1046
- swTemplate: readFileSync(await resolvePath("./runtime/sw/proxy-sw.template.js"), "utf-8")
1477
+ googleStaticMapsProxy: googleMapsEnabled ? { apiKey: nuxt.options.runtimeConfig.public.scripts?.googleMaps?.apiKey } : void 0
1047
1478
  };
1048
1479
  nuxt.options.runtimeConfig.public["nuxt-scripts"] = {
1049
1480
  // expose for devtools
1050
1481
  version: nuxt.options.dev ? version : void 0,
1051
1482
  defaultScriptOptions: config.defaultScriptOptions,
1052
1483
  // Only expose enabled and cacheMaxAge to client, not apiKey
1053
- googleStaticMapsProxy: config.googleStaticMapsProxy?.enabled ? { enabled: true, cacheMaxAge: config.googleStaticMapsProxy.cacheMaxAge } : void 0
1484
+ googleStaticMapsProxy: googleMapsEnabled ? { enabled: true, cacheMaxAge: config.googleStaticMapsProxy?.cacheMaxAge ?? 3600 } : void 0
1054
1485
  };
1055
- if (config.registry) {
1056
- nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
1057
- nuxt.options.runtimeConfig.public.scripts = defu(
1058
- nuxt.options.runtimeConfig.public.scripts || {},
1059
- config.registry
1060
- );
1061
- }
1062
1486
  if (config.defaultScriptOptions?.bundle !== void 0) {
1063
1487
  logger.warn(
1064
1488
  "`scripts.defaultScriptOptions.bundle` is deprecated. Use `scripts.firstParty: true` instead. First-party mode is now enabled by default."
1065
1489
  );
1066
1490
  }
1067
- const staticPresets = ["static", "github-pages", "cloudflare-pages-static"];
1068
- const preset = process.env.NITRO_PRESET || "";
1069
- const isStaticPreset = staticPresets.includes(preset);
1070
- const firstPartyEnabled = !!config.firstParty;
1071
- const firstPartyPrefix = typeof config.firstParty === "object" ? config.firstParty.prefix : void 0;
1072
- const firstPartyCollectPrefix = typeof config.firstParty === "object" ? config.firstParty.collectPrefix || "/_proxy" : "/_proxy";
1073
- const firstPartyPrivacy = typeof config.firstParty === "object" ? config.firstParty.privacy : void 0;
1074
- const assetsPrefix = firstPartyPrefix || config.assets?.prefix || "/_scripts";
1491
+ const firstParty = await setupFirstParty(config, resolvePath);
1492
+ const assetsPrefix = firstParty.assetsPrefix;
1075
1493
  if (config.partytown?.length) {
1076
1494
  config.registry = config.registry || {};
1077
1495
  const requiredForwards = [];
@@ -1084,12 +1502,8 @@ const module$1 = defineNuxtModule({
1084
1502
  }
1085
1503
  const reg = config.registry;
1086
1504
  const existing = reg[scriptKey];
1087
- if (Array.isArray(existing)) {
1505
+ if (existing) {
1088
1506
  existing[1] = { ...existing[1], partytown: true };
1089
- } else if (existing && typeof existing === "object" && existing !== true && existing !== "mock") {
1090
- reg[scriptKey] = [existing, { partytown: true }];
1091
- } else if (existing === true || existing === "mock") {
1092
- reg[scriptKey] = [{}, { partytown: true }];
1093
1507
  } else {
1094
1508
  reg[scriptKey] = [{}, { partytown: true }];
1095
1509
  }
@@ -1123,110 +1537,14 @@ const module$1 = defineNuxtModule({
1123
1537
  path: await resolvePath("./runtime/components"),
1124
1538
  pathPrefix: false
1125
1539
  });
1540
+ fixSelfClosingScriptComponents(nuxt);
1126
1541
  addTemplate({
1127
1542
  filename: "nuxt-scripts-trigger-resolver.mjs",
1128
1543
  getContents() {
1129
1544
  return templateTriggerResolver(config.defaultScriptOptions);
1130
1545
  }
1131
1546
  });
1132
- const swHandlerPath = await resolvePath("./runtime/server/sw-handler");
1133
- logger.debug("[nuxt-scripts] First-party config:", { firstPartyEnabled, firstPartyPrivacy, firstPartyCollectPrefix });
1134
- if (firstPartyEnabled && !nuxt.options.dev) {
1135
- const swPath = "/_nuxt-scripts-sw.js";
1136
- const swRules = getSWInterceptRules(firstPartyCollectPrefix);
1137
- addServerHandler({
1138
- route: swPath,
1139
- handler: swHandlerPath
1140
- });
1141
- addPluginTemplate({
1142
- filename: "nuxt-scripts-sw-register.client.mjs",
1143
- getContents() {
1144
- return `import { defineNuxtPlugin } from 'nuxt/app'
1145
-
1146
- export default defineNuxtPlugin({
1147
- name: 'nuxt-scripts:sw-register',
1148
- enforce: 'pre',
1149
- async setup() {
1150
- if (!('serviceWorker' in navigator)) return;
1151
-
1152
- try {
1153
- const reg = await navigator.serviceWorker.register('${swPath}', { scope: '/' });
1154
-
1155
- // Wait for SW to be active and controlling this page
1156
- if (!navigator.serviceWorker.controller) {
1157
- await new Promise((resolve) => {
1158
- const onControllerChange = () => {
1159
- navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange);
1160
- resolve();
1161
- };
1162
- navigator.serviceWorker.addEventListener('controllerchange', onControllerChange);
1163
-
1164
- // Fallback timeout
1165
- setTimeout(resolve, 2000);
1166
- });
1167
- }
1168
- } catch (err) {
1169
- console.warn('[nuxt-scripts] SW registration failed:', err);
1170
- }
1171
- },
1172
- })
1173
- `;
1174
- }
1175
- });
1176
- addPluginTemplate({
1177
- filename: "nuxt-scripts-beacon-intercept.client.mjs",
1178
- getContents() {
1179
- const rulesJson = JSON.stringify(swRules);
1180
- return `export default defineNuxtPlugin({
1181
- name: 'nuxt-scripts:beacon-intercept',
1182
- enforce: 'pre',
1183
- setup() {
1184
- if (typeof navigator === 'undefined' || !navigator.sendBeacon) return;
1185
-
1186
- const rules = ${rulesJson};
1187
- const originalBeacon = navigator.sendBeacon.bind(navigator);
1188
-
1189
- navigator.sendBeacon = (url, data) => {
1190
- try {
1191
- const parsed = new URL(url, window.location.origin);
1192
-
1193
- // Check if this URL matches any of our proxy rules
1194
- for (const rule of rules) {
1195
- if (parsed.hostname === rule.pattern || parsed.hostname.endsWith('.' + rule.pattern)) {
1196
- // Check path prefix if specified
1197
- if (rule.pathPrefix && !parsed.pathname.startsWith(rule.pathPrefix)) {
1198
- continue;
1199
- }
1200
-
1201
- // Rewrite to proxy: strip pathPrefix from original, prepend target
1202
- const pathWithoutPrefix = rule.pathPrefix
1203
- ? parsed.pathname.slice(rule.pathPrefix.length)
1204
- : parsed.pathname;
1205
- const separator = pathWithoutPrefix.startsWith('/') ? '' : '/';
1206
- const proxyUrl = rule.target + separator + pathWithoutPrefix + parsed.search;
1207
-
1208
- return originalBeacon(proxyUrl, data);
1209
- }
1210
- }
1211
- } catch (e) {
1212
- // URL parsing failed, pass through
1213
- }
1214
-
1215
- return originalBeacon(url, data);
1216
- };
1217
- },
1218
- })
1219
- `;
1220
- }
1221
- });
1222
- nuxt.options.runtimeConfig.public["nuxt-scripts-sw"] = { path: swPath };
1223
- const proxyHandlerPath = await resolvePath("./runtime/server/proxy-handler");
1224
- logger.debug("[nuxt-scripts] Registering proxy handler:", `${firstPartyCollectPrefix}/**`, "->", proxyHandlerPath);
1225
- addServerHandler({
1226
- route: `${firstPartyCollectPrefix}/**`,
1227
- handler: proxyHandlerPath
1228
- });
1229
- }
1547
+ logger.debug("[nuxt-scripts] First-party config:", firstParty);
1230
1548
  const scripts = await registry(resolvePath);
1231
1549
  for (const script of scripts) {
1232
1550
  if (script.import?.name) {
@@ -1254,89 +1572,26 @@ export default defineNuxtPlugin({
1254
1572
  });
1255
1573
  }
1256
1574
  const { renderedScript } = setupPublicAssetStrategy(config.assets);
1257
- if (firstPartyEnabled) {
1258
- const proxyConfigs = getAllProxyConfigs(firstPartyCollectPrefix);
1259
- const registryKeys = Object.keys(config.registry || {});
1260
- const neededRoutes = {};
1261
- const routePrivacyOverrides = {};
1262
- const unsupportedScripts = [];
1263
- for (const key of registryKeys) {
1264
- const script = registryScriptsWithImport.find((s) => s.import.name.toLowerCase() === `usescript${key.toLowerCase()}`);
1265
- const proxyKey = script?.proxy || void 0;
1266
- if (proxyKey) {
1267
- const proxyConfig = proxyConfigs[proxyKey];
1268
- if (proxyConfig?.routes) {
1269
- Object.assign(neededRoutes, proxyConfig.routes);
1270
- for (const routePath of Object.keys(proxyConfig.routes)) {
1271
- routePrivacyOverrides[routePath] = proxyConfig.privacy;
1272
- }
1273
- } else {
1274
- unsupportedScripts.push(key);
1275
- }
1276
- }
1277
- }
1278
- if (config.registry?.posthog && typeof config.registry.posthog === "object") {
1279
- const phConfig = config.registry.posthog;
1280
- if (!phConfig.apiHost) {
1281
- const region = phConfig.region || "us";
1282
- phConfig.apiHost = region === "eu" ? `${firstPartyCollectPrefix}/ph-eu` : `${firstPartyCollectPrefix}/ph`;
1283
- }
1284
- }
1285
- if (unsupportedScripts.length && nuxt.options.dev) {
1286
- logger.warn(
1287
- `First-party mode is enabled but these scripts don't support it yet: ${unsupportedScripts.join(", ")}.
1288
- They will load directly from third-party servers. Request support at https://github.com/nuxt/scripts/issues`
1289
- );
1290
- }
1291
- const flatRoutes = {};
1292
- for (const [path, config2] of Object.entries(neededRoutes)) {
1293
- flatRoutes[path] = config2.proxy;
1294
- }
1295
- const allRewrites = [];
1296
- for (const key of registryKeys) {
1297
- const script = registryScriptsWithImport.find((s) => s.import.name.toLowerCase() === `usescript${key.toLowerCase()}`);
1298
- const proxyKey = script?.proxy !== false ? script?.proxy || key : void 0;
1299
- if (proxyKey) {
1300
- const proxyConfig = proxyConfigs[proxyKey];
1301
- if (proxyConfig?.rewrite) {
1302
- allRewrites.push(...proxyConfig.rewrite);
1303
- }
1304
- }
1575
+ if (firstParty.enabled) {
1576
+ const { proxyPrefix, devtools: devtoolsData } = finalizeFirstParty({
1577
+ firstParty,
1578
+ registry: config.registry,
1579
+ registryScripts,
1580
+ nuxtOptions: nuxt.options
1581
+ });
1582
+ if (devtoolsData) {
1583
+ nuxt.options.runtimeConfig.public["nuxt-scripts-devtools"] = devtoolsData;
1305
1584
  }
1306
- nuxt.options.runtimeConfig["nuxt-scripts-proxy"] = {
1307
- routes: flatRoutes,
1308
- privacy: firstPartyPrivacy,
1309
- // undefined = use per-script defaults, set = global override
1310
- routePrivacy: routePrivacyOverrides,
1311
- // per-script privacy from registry
1312
- rewrites: allRewrites
1313
- };
1314
- if (Object.keys(neededRoutes).length) {
1315
- if (nuxt.options.dev) {
1316
- const routeCount = Object.keys(neededRoutes).length;
1317
- const scriptsCount = registryKeys.length;
1318
- const privacyLabel = firstPartyPrivacy === void 0 ? "per-script" : typeof firstPartyPrivacy === "boolean" ? firstPartyPrivacy ? "anonymize" : "passthrough" : "custom";
1319
- logger.success(`First-party mode enabled for ${scriptsCount} script(s), ${routeCount} proxy route(s) configured (privacy: ${privacyLabel})`);
1320
- if (logger.level >= 4) {
1321
- for (const [path, config2] of Object.entries(neededRoutes)) {
1322
- logger.debug(` ${path} \u2192 ${config2.proxy}`);
1323
- }
1324
- }
1585
+ if (config.partytown?.length && hasNuxtModule("@nuxtjs/partytown")) {
1586
+ const partytownConfig = nuxt.options.partytown || {};
1587
+ if (!partytownConfig.resolveUrl) {
1588
+ partytownConfig.resolveUrl = generatePartytownResolveUrl(proxyPrefix);
1589
+ nuxt.options.partytown = partytownConfig;
1590
+ logger.info("[partytown] Auto-configured resolveUrl for first-party proxy");
1591
+ } else {
1592
+ logger.warn("[partytown] Custom resolveUrl already set \u2014 first-party proxy URLs will not be auto-rewritten in Partytown worker. Add first-party proxy rules to your resolveUrl manually.");
1325
1593
  }
1326
1594
  }
1327
- if (isStaticPreset) {
1328
- logger.warn(
1329
- `First-party collection endpoints require a server runtime (detected: ${preset || "static"}).
1330
- Scripts will be bundled, but collection requests will not be proxied.
1331
-
1332
- Options:
1333
- 1. Configure platform rewrites (Vercel, Netlify, Cloudflare)
1334
- 2. Switch to server-rendered mode (ssr: true)
1335
- 3. Disable with firstParty: false
1336
-
1337
- See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
1338
- );
1339
- }
1340
1595
  }
1341
1596
  const moduleInstallPromises = /* @__PURE__ */ new Map();
1342
1597
  addBuildPlugin(NuxtScriptsCheckScripts(), {
@@ -1345,9 +1600,10 @@ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
1345
1600
  addBuildPlugin(NuxtScriptBundleTransformer({
1346
1601
  scripts: registryScriptsWithImport,
1347
1602
  registryConfig: nuxt.options.runtimeConfig.public.scripts,
1348
- defaultBundle: firstPartyEnabled || config.defaultScriptOptions?.bundle,
1349
- firstPartyEnabled,
1350
- firstPartyCollectPrefix,
1603
+ defaultBundle: firstParty.enabled || config.defaultScriptOptions?.bundle,
1604
+ proxyConfigs: firstParty.proxyConfigs,
1605
+ proxyPrefix: firstParty.proxyPrefix,
1606
+ partytownScripts: new Set(config.partytown || []),
1351
1607
  moduleDetected(module) {
1352
1608
  if (nuxt.options.dev && module !== "@nuxt/scripts" && !moduleInstallPromises.has(module) && !hasNuxtModule(module))
1353
1609
  moduleInstallPromises.set(module, () => installNuxtModule(module));
@@ -1360,37 +1616,44 @@ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
1360
1616
  renderedScript
1361
1617
  }));
1362
1618
  nuxt.hooks.hook("build:done", async () => {
1363
- const initPromise = Array.from(moduleInstallPromises.values());
1619
+ const initPromise = [...moduleInstallPromises.values()];
1364
1620
  for (const p of initPromise)
1365
1621
  await p?.();
1366
1622
  });
1367
1623
  });
1368
- if (config.googleStaticMapsProxy?.enabled) {
1369
- addServerHandler({
1370
- route: "/_scripts/google-static-maps-proxy",
1371
- handler: await resolvePath("./runtime/server/google-static-maps-proxy")
1372
- });
1624
+ const enabledEndpoints = {};
1625
+ for (const script of scripts) {
1626
+ if (!script.serverHandlers?.length || !script.registryKey)
1627
+ continue;
1628
+ const isEnabled = script.registryKey === "googleMaps" ? config.googleStaticMapsProxy?.enabled || config.registry?.googleMaps : config.registry?.[script.registryKey];
1629
+ if (!isEnabled)
1630
+ continue;
1631
+ enabledEndpoints[script.registryKey] = true;
1632
+ for (const handler of script.serverHandlers) {
1633
+ addServerHandler({
1634
+ route: handler.route,
1635
+ handler: handler.handler,
1636
+ middleware: handler.middleware
1637
+ });
1638
+ }
1639
+ if (script.registryKey === "gravatar") {
1640
+ const gravatarConfig = config.registry?.gravatar?.[0] || {};
1641
+ nuxt.options.runtimeConfig.public["nuxt-scripts"] = defu(
1642
+ { gravatarProxy: { cacheMaxAge: gravatarConfig.cacheMaxAge ?? 3600 } },
1643
+ nuxt.options.runtimeConfig.public["nuxt-scripts"]
1644
+ );
1645
+ }
1646
+ if (script.registryKey === "googleMaps") {
1647
+ nuxt.options.runtimeConfig["nuxt-scripts"] = defu(
1648
+ { googleMapsGeocodeProxy: { apiKey: nuxt.options.runtimeConfig.public.scripts?.googleMaps?.apiKey } },
1649
+ nuxt.options.runtimeConfig["nuxt-scripts"]
1650
+ );
1651
+ }
1373
1652
  }
1374
- addServerHandler({
1375
- route: "/api/_scripts/x-embed",
1376
- handler: await resolvePath("./runtime/server/x-embed")
1377
- });
1378
- addServerHandler({
1379
- route: "/api/_scripts/x-embed-image",
1380
- handler: await resolvePath("./runtime/server/x-embed-image")
1381
- });
1382
- addServerHandler({
1383
- route: "/api/_scripts/instagram-embed",
1384
- handler: await resolvePath("./runtime/server/instagram-embed")
1385
- });
1386
- addServerHandler({
1387
- route: "/api/_scripts/instagram-embed-image",
1388
- handler: await resolvePath("./runtime/server/instagram-embed-image")
1389
- });
1390
- addServerHandler({
1391
- route: "/api/_scripts/instagram-embed-asset",
1392
- handler: await resolvePath("./runtime/server/instagram-embed-asset")
1393
- });
1653
+ nuxt.options.runtimeConfig.public["nuxt-scripts"] = defu(
1654
+ { endpoints: enabledEndpoints },
1655
+ nuxt.options.runtimeConfig.public["nuxt-scripts"]
1656
+ );
1394
1657
  if (nuxt.options.dev) {
1395
1658
  setupDevToolsUI(config, resolvePath);
1396
1659
  }