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

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/{Ds2G8aQM.js → AwAKM0sG.js} +1 -1
  5. package/dist/client/_nuxt/Bl23o3st.js +162 -0
  6. package/dist/client/_nuxt/{DdVDSbUA.js → CYlYSSNW.js} +1 -1
  7. package/dist/client/_nuxt/{CD5B-xvT.js → D5FIkDae.js} +1 -1
  8. package/dist/client/_nuxt/builds/latest.json +1 -1
  9. package/dist/client/_nuxt/builds/meta/f0b4dd20-8496-4003-b7a3-05cbae515923.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 +7 -19
  15. package/dist/module.d.ts +164 -0
  16. package/dist/module.json +1 -1
  17. package/dist/module.mjs +935 -645
  18. package/dist/registry.d.ts +6 -0
  19. package/dist/registry.mjs +235 -69
  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 +10 -2
  42. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue +38 -37
  43. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue.d.ts +10 -2
  44. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.d.vue.ts +63 -0
  45. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue +160 -0
  46. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue.d.ts +63 -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 +134 -93
  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.ViOoYQXH.mjs +381 -0
  198. package/dist/stats.d.mts +202 -0
  199. package/dist/stats.d.ts +202 -0
  200. package/dist/stats.mjs +3868 -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 +3600 -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,316 @@
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, r as routesToInterceptRules } from './shared/scripts.ViOoYQXH.mjs';
23
23
 
24
- const DEVTOOLS_UI_ROUTE = "/__nuxt-scripts";
25
- const DEVTOOLS_UI_LOCAL_PORT = 3300;
24
+ function generatePartytownResolveUrl(interceptRules) {
25
+ const rulesJson = JSON.stringify(interceptRules);
26
+ return `function(url, location, type) {
27
+ var rules = ${rulesJson};
28
+ for (var i = 0; i < rules.length; i++) {
29
+ var rule = rules[i];
30
+ if (url.hostname === rule.pattern || url.hostname.endsWith('.' + rule.pattern)) {
31
+ if (rule.pathPrefix && !url.pathname.startsWith(rule.pathPrefix)) continue;
32
+ var path = rule.pathPrefix ? url.pathname.slice(rule.pathPrefix.length) : url.pathname;
33
+ var newPath = rule.target + (path.startsWith('/') ? '' : '/') + path + url.search;
34
+ return new URL(newPath, location.origin);
35
+ }
36
+ }
37
+ }`;
38
+ }
26
39
 
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
- );
40
+ const logger = useLogger("@nuxt/scripts");
41
+
42
+ function generateInterceptPluginContents(interceptRules) {
43
+ const rulesJson = JSON.stringify(interceptRules);
44
+ return `export default defineNuxtPlugin({
45
+ name: 'nuxt-scripts:intercept',
46
+ enforce: 'pre',
47
+ setup() {
48
+ const rules = ${rulesJson};
49
+ const origBeacon = typeof navigator !== 'undefined' && navigator.sendBeacon
50
+ ? navigator.sendBeacon.bind(navigator)
51
+ : () => false;
52
+ const origFetch = globalThis.fetch.bind(globalThis);
53
+
54
+ function rewriteUrl(url) {
55
+ try {
56
+ const parsed = new URL(url, location.origin);
57
+ for (const rule of rules) {
58
+ if (parsed.hostname === rule.pattern || parsed.hostname.endsWith('.' + rule.pattern)) {
59
+ if (rule.pathPrefix && !parsed.pathname.startsWith(rule.pathPrefix)) continue;
60
+ const path = rule.pathPrefix ? parsed.pathname.slice(rule.pathPrefix.length) : parsed.pathname;
61
+ return location.origin + rule.target + (path.startsWith('/') ? '' : '/') + path + parsed.search;
62
+ }
63
+ }
64
+ } catch {}
65
+ return url;
66
+ }
67
+
68
+ // XMLHttpRequest wrapper \u2014 intercepts .open() to rewrite URL
69
+ const OrigXHR = XMLHttpRequest;
70
+ class ProxiedXHR extends OrigXHR {
71
+ open() {
72
+ const args = Array.from(arguments);
73
+ if (typeof args[1] === 'string') args[1] = rewriteUrl(args[1]);
74
+ return super.open.apply(this, args);
75
+ }
76
+ }
77
+ // Image wrapper \u2014 intercepts .src setter to rewrite URL
78
+ const OrigImage = Image;
79
+ const origSrcDesc = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'src');
80
+ function ProxiedImage(w, h) {
81
+ const img = arguments.length === 2 ? new OrigImage(w, h)
82
+ : arguments.length === 1 ? new OrigImage(w) : new OrigImage();
83
+ if (origSrcDesc && origSrcDesc.set) {
84
+ Object.defineProperty(img, 'src', {
85
+ get() { return origSrcDesc.get.call(this); },
86
+ set(v) { origSrcDesc.set.call(this, typeof v === 'string' ? rewriteUrl(v) : v); },
87
+ configurable: true,
88
+ });
89
+ }
90
+ return img;
91
+ }
92
+
93
+ globalThis.__nuxtScripts = {
94
+ sendBeacon: (url, data) => origBeacon(rewriteUrl(url), data),
95
+ fetch: (url, opts) => origFetch(typeof url === 'string' ? rewriteUrl(url) : url, opts),
96
+ XMLHttpRequest: ProxiedXHR,
97
+ Image: ProxiedImage,
98
+ };
99
+ },
100
+ })
101
+ `;
102
+ }
103
+
104
+ async function setupFirstParty(config, resolvePath) {
105
+ const enabled = !!config.firstParty;
106
+ const proxyPrefix = typeof config.firstParty === "object" ? config.firstParty.proxyPrefix || "/_scripts/p" : "/_scripts/p";
107
+ const privacy = typeof config.firstParty === "object" ? config.firstParty.privacy : void 0;
108
+ const assetsPrefix = config.assets?.prefix || "/_scripts/assets";
109
+ const proxyConfigs = enabled ? getAllProxyConfigs(proxyPrefix) : {};
110
+ const firstParty = { enabled, proxyPrefix, privacy, assetsPrefix, proxyConfigs };
111
+ if (enabled) {
112
+ const proxyHandlerPath = await resolvePath("./runtime/server/proxy-handler");
113
+ logger.debug("[nuxt-scripts] Registering proxy handler:", `${proxyPrefix}/**`, "->", proxyHandlerPath);
114
+ addServerHandler({
115
+ route: `${proxyPrefix}/**`,
116
+ handler: proxyHandlerPath
37
117
  });
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, "")
118
+ }
119
+ return firstParty;
120
+ }
121
+ function applyAutoInject(registry, runtimeConfig, proxyPrefix, registryKey, autoInject) {
122
+ const entry = registry[registryKey];
123
+ if (!entry)
124
+ return;
125
+ const input = entry[0];
126
+ const rtScripts = runtimeConfig.public?.scripts;
127
+ const rtEntry = rtScripts?.[registryKey];
128
+ const config = rtEntry && typeof rtEntry === "object" ? rtEntry : input;
129
+ if (!config || config[autoInject.configField])
130
+ return;
131
+ const value = autoInject.computeValue(proxyPrefix, config);
132
+ input[autoInject.configField] = value;
133
+ if (rtEntry && typeof rtEntry === "object" && rtEntry !== input)
134
+ rtEntry[autoInject.configField] = value;
135
+ }
136
+ const DOMAIN_RE = /^https?:\/\/([^/]+)/;
137
+ function extractDomains(routes) {
138
+ const domains = /* @__PURE__ */ new Set();
139
+ for (const { proxy } of Object.values(routes)) {
140
+ const match = proxy.match(DOMAIN_RE);
141
+ if (match?.[1])
142
+ domains.add(match[1]);
143
+ }
144
+ return [...domains].sort();
145
+ }
146
+ function computePrivacyLevel(privacy) {
147
+ const flags = Object.values(privacy);
148
+ if (flags.every(Boolean))
149
+ return "full";
150
+ if (flags.some(Boolean))
151
+ return "partial";
152
+ return "none";
153
+ }
154
+ function finalizeFirstParty(opts) {
155
+ const { firstParty, registryScripts, nuxtOptions } = opts;
156
+ const { proxyConfigs, proxyPrefix } = firstParty;
157
+ const registryKeys = Object.keys(opts.registry || {});
158
+ const scriptByKey = /* @__PURE__ */ new Map();
159
+ for (const script of registryScripts) {
160
+ if (script.registryKey)
161
+ scriptByKey.set(script.registryKey, script);
162
+ }
163
+ const neededRoutes = {};
164
+ const routePrivacyOverrides = {};
165
+ const unsupportedScripts = [];
166
+ const unmatchedScripts = [];
167
+ const devtoolsScripts = [];
168
+ for (const key of registryKeys) {
169
+ const script = scriptByKey.get(key);
170
+ if (!script) {
171
+ unmatchedScripts.push(key);
172
+ continue;
173
+ }
174
+ if (script.proxy === false)
175
+ continue;
176
+ const configKey = script.proxy || key;
177
+ const proxyConfig = proxyConfigs[configKey];
178
+ if (!proxyConfig) {
179
+ if (script.scriptBundling !== false)
180
+ unsupportedScripts.push(key);
181
+ continue;
182
+ }
183
+ const scriptRoutes = proxyConfig.routes || {};
184
+ if (proxyConfig.routes) {
185
+ Object.assign(neededRoutes, proxyConfig.routes);
186
+ for (const routePath of Object.keys(proxyConfig.routes))
187
+ routePrivacyOverrides[routePath] = proxyConfig.privacy;
188
+ }
189
+ if (proxyConfig.autoInject && opts.registry)
190
+ applyAutoInject(opts.registry, nuxtOptions.runtimeConfig, proxyPrefix, key, proxyConfig.autoInject);
191
+ if (nuxtOptions.dev) {
192
+ const privacy = proxyConfig.privacy;
193
+ const normalizedPrivacy = {
194
+ ip: !!privacy.ip,
195
+ userAgent: !!privacy.userAgent,
196
+ language: !!privacy.language,
197
+ screen: !!privacy.screen,
198
+ timezone: !!privacy.timezone,
199
+ hardware: !!privacy.hardware
47
200
  };
48
- });
201
+ const logo = script.logo;
202
+ const logoStr = typeof logo === "object" ? logo.dark || logo.light : logo || "";
203
+ const interceptRules2 = routesToInterceptRules(scriptRoutes);
204
+ devtoolsScripts.push({
205
+ registryKey: key,
206
+ label: script.label || key,
207
+ logo: logoStr,
208
+ category: script.category || "unknown",
209
+ configKey,
210
+ mechanism: script.src === false ? "config-injection-proxy" : "bundle-rewrite-intercept",
211
+ hasAutoInject: !!proxyConfig.autoInject,
212
+ autoInjectField: proxyConfig.autoInject?.configField,
213
+ hasPostProcess: !!proxyConfig.postProcess,
214
+ privacy: normalizedPrivacy,
215
+ privacyLevel: computePrivacyLevel(normalizedPrivacy),
216
+ domains: extractDomains(scriptRoutes),
217
+ routes: Object.entries(scriptRoutes).map(([local, { proxy }]) => ({ local, target: proxy })),
218
+ interceptRules: interceptRules2
219
+ });
220
+ }
49
221
  }
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
222
+ if (unmatchedScripts.length) {
223
+ logger.warn(
224
+ `First-party mode: could not find registry scripts for: ${unmatchedScripts.join(", ")}.
225
+ These scripts will not have proxy routes registered. Check that the registry key matches a known script.`
226
+ );
227
+ }
228
+ if (unsupportedScripts.length && nuxtOptions.dev) {
229
+ logger.warn(
230
+ `First-party mode is enabled but these scripts don't support it yet: ${unsupportedScripts.join(", ")}.
231
+ They will load directly from third-party servers. Request support at https://github.com/nuxt/scripts/issues`
232
+ );
233
+ }
234
+ const interceptRules = routesToInterceptRules(neededRoutes);
235
+ addPluginTemplate({
236
+ filename: "nuxt-scripts-intercept.client.mjs",
237
+ getContents() {
238
+ return generateInterceptPluginContents(interceptRules);
61
239
  }
62
240
  });
63
- }
241
+ const flatRoutes = {};
242
+ for (const [path, config] of Object.entries(neededRoutes))
243
+ flatRoutes[path] = config.proxy;
244
+ nuxtOptions.runtimeConfig["nuxt-scripts-proxy"] = {
245
+ routes: flatRoutes,
246
+ privacy: firstParty.privacy,
247
+ routePrivacy: routePrivacyOverrides
248
+ };
249
+ const privacyLabel = firstParty.privacy === void 0 ? "per-script" : typeof firstParty.privacy === "boolean" ? firstParty.privacy ? "anonymize" : "passthrough" : "custom";
250
+ if (Object.keys(neededRoutes).length && nuxtOptions.dev) {
251
+ const routeCount = Object.keys(neededRoutes).length;
252
+ const scriptsCount = registryKeys.length;
253
+ logger.success(`First-party mode enabled for ${scriptsCount} script(s), ${routeCount} proxy route(s) configured (privacy: ${privacyLabel})`);
254
+ if (logger.level >= 4) {
255
+ for (const [path, config] of Object.entries(neededRoutes))
256
+ logger.debug(` ${path} \u2192 ${config.proxy}`);
257
+ }
258
+ }
259
+ const staticPresets = ["static", "github-pages", "cloudflare-pages-static", "netlify-static", "azure-static", "firebase-static"];
260
+ const preset = process.env.NITRO_PRESET || "";
261
+ if (staticPresets.includes(preset)) {
262
+ logger.warn(
263
+ `First-party collection endpoints require a server runtime (detected: ${preset || "static"}).
264
+ Scripts will be bundled, but collection requests will not be proxied.
64
265
 
65
- const logger = useLogger("@nuxt/scripts");
266
+ Options:
267
+ 1. Configure platform rewrites (Vercel, Netlify, Cloudflare)
268
+ 2. Switch to server-rendered mode (ssr: true)
269
+ 3. Disable with firstParty: false
270
+
271
+ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
272
+ );
273
+ }
274
+ let devtools;
275
+ if (nuxtOptions.dev) {
276
+ const allDomains = /* @__PURE__ */ new Set();
277
+ for (const s of devtoolsScripts) {
278
+ for (const d of s.domains)
279
+ allDomains.add(d);
280
+ }
281
+ devtools = {
282
+ enabled: true,
283
+ proxyPrefix,
284
+ privacyMode: privacyLabel,
285
+ scripts: devtoolsScripts,
286
+ totalRoutes: Object.keys(neededRoutes).length,
287
+ totalDomains: allDomains.size
288
+ };
289
+ }
290
+ return { interceptRules, devtools };
291
+ }
66
292
 
67
293
  const renderedScript = /* @__PURE__ */ new Map();
68
294
  const ONE_YEAR_IN_SECONDS = 60 * 60 * 24 * 365;
69
- const bundleStorage = () => {
295
+ function bundleStorage() {
70
296
  const nuxt = tryUseNuxt();
71
297
  return createStorage({
72
298
  driver: fsDriver({
73
299
  base: resolve(nuxt?.options.rootDir || "", "node_modules/.cache/nuxt/scripts")
74
300
  })
75
301
  });
76
- };
302
+ }
77
303
  function setupPublicAssetStrategy(options = {}) {
78
- const assetsBaseURL = options.prefix || "/_scripts";
304
+ const assetsBaseURL = options.prefix || "/_scripts/assets";
79
305
  const nuxt = useNuxt();
80
306
  const storage = bundleStorage();
81
307
  addDevServerHandler({
82
308
  route: assetsBaseURL,
83
309
  handler: lazyEventHandler(async () => {
84
310
  return eventHandler(async (event) => {
85
- const filename = event.path.slice(1);
86
- const scriptDescriptor = renderedScript.get(join(assetsBaseURL, event.path.slice(1)));
311
+ const cleanPath = (event.path || "").split("?")[0]?.slice(1) || "";
312
+ const filename = cleanPath;
313
+ const scriptDescriptor = renderedScript.get(join(assetsBaseURL, cleanPath));
87
314
  if (!scriptDescriptor || scriptDescriptor instanceof Error)
88
315
  throw createError({ statusCode: 404 });
89
316
  if (scriptDescriptor.content) {
@@ -123,242 +350,447 @@ function setupPublicAssetStrategy(options = {}) {
123
350
  };
124
351
  }
125
352
 
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/**" }
353
+ const DEVTOOLS_UI_ROUTE = "/__nuxt-scripts";
354
+ const DEVTOOLS_UI_LOCAL_PORT = 3300;
355
+
356
+ async function setupDevToolsUI(options, resolve, nuxt = useNuxt()) {
357
+ const clientPath = await resolve("./client");
358
+ const isProductionBuild = existsSync(clientPath);
359
+ if (isProductionBuild) {
360
+ nuxt.hook("vite:serverCreated", async (server) => {
361
+ const sirv = await import('sirv').then((r) => r.default || r);
362
+ server.middlewares.use(
363
+ DEVTOOLS_UI_ROUTE,
364
+ sirv(clientPath, { dev: true, single: true })
365
+ );
366
+ });
367
+ } else {
368
+ extendViteConfig((config) => {
369
+ config.server = config.server || {};
370
+ config.server.proxy = config.server.proxy || {};
371
+ config.server.proxy[DEVTOOLS_UI_ROUTE] = {
372
+ target: `http://localhost:${DEVTOOLS_UI_LOCAL_PORT}${DEVTOOLS_UI_ROUTE}`,
373
+ changeOrigin: true,
374
+ followRedirects: true,
375
+ rewrite: (path) => path.replace(DEVTOOLS_UI_ROUTE, "")
376
+ };
377
+ });
378
+ }
379
+ addCustomTab({
380
+ // unique identifier
381
+ name: "nuxt-scripts",
382
+ // title to display in the tab
383
+ title: "Scripts",
384
+ // any icon from Iconify, or a URL to an image
385
+ icon: "carbon:script",
386
+ // iframe view
387
+ view: {
388
+ type: "iframe",
389
+ src: DEVTOOLS_UI_ROUTE
390
+ }
391
+ });
392
+ }
393
+
394
+ const isStackblitz = provider === "stackblitz";
395
+ async function promptToInstall(name, installCommand, options) {
396
+ if (await resolvePackageJSON(name).catch(() => null))
397
+ return true;
398
+ logger$1.info(`Package ${name} is missing`);
399
+ if (isCI)
400
+ return false;
401
+ if (options.prompt === true || options.prompt !== false && !isStackblitz) {
402
+ const confirm = await logger$1.prompt(`Do you want to install ${name} package?`, {
403
+ type: "confirm",
404
+ name: "confirm",
405
+ initial: true
406
+ });
407
+ if (!confirm)
408
+ return false;
409
+ }
410
+ logger$1.info(`Installing ${name}...`);
411
+ try {
412
+ await installCommand();
413
+ logger$1.success(`Installed ${name}`);
414
+ return true;
415
+ } catch (err) {
416
+ logger$1.error(err);
417
+ return false;
418
+ }
419
+ }
420
+ const installPrompts = /* @__PURE__ */ new Set();
421
+ function installNuxtModule(name, options) {
422
+ if (installPrompts.has(name))
423
+ return;
424
+ installPrompts.add(name);
425
+ const nuxt = tryUseNuxt();
426
+ if (!nuxt)
427
+ return;
428
+ return promptToInstall(name, async () => {
429
+ const { runCommand } = await import(String("nuxi"));
430
+ await runCommand("module", ["add", name, "--cwd", nuxt.options.rootDir]);
431
+ }, { rootDir: nuxt.options.rootDir, searchPaths: nuxt.options.modulesDir, ...options });
432
+ }
433
+
434
+ function normalizeRegistryConfig(registry) {
435
+ for (const key of Object.keys(registry)) {
436
+ const entry = registry[key];
437
+ if (!entry) {
438
+ delete registry[key];
439
+ continue;
440
+ }
441
+ if (entry === true) {
442
+ registry[key] = [{}];
443
+ } else if (entry === "mock") {
444
+ registry[key] = [{}, { trigger: "manual", skipValidation: true }];
445
+ } else if (Array.isArray(entry)) {
446
+ if (!entry[0] && !entry[1]) {
447
+ delete registry[key];
448
+ continue;
188
449
  }
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/**" }
450
+ if (!entry[0])
451
+ entry[0] = {};
452
+ } else if (typeof entry === "object") {
453
+ registry[key] = [entry];
454
+ } else {
455
+ delete registry[key];
456
+ }
457
+ }
458
+ }
459
+
460
+ function isVue(id, opts = {}) {
461
+ const { search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
462
+ if (id.endsWith(".vue") && !search) {
463
+ return true;
464
+ }
465
+ if (!search) {
466
+ return false;
467
+ }
468
+ const query = parseQuery(search);
469
+ if (query.nuxt_component) {
470
+ return false;
471
+ }
472
+ if (query.macro && (search === "?macro=true" || !opts.type || opts.type.includes("script"))) {
473
+ return true;
474
+ }
475
+ const type = "setup" in query ? "script" : query.type;
476
+ if (!("vue" in query) || opts.type && !opts.type.includes(type)) {
477
+ return false;
478
+ }
479
+ return true;
480
+ }
481
+ const JS_RE$1 = /\.(?:[cm]?j|t)sx?$/;
482
+ function isJS(id) {
483
+ const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href));
484
+ return JS_RE$1.test(pathname);
485
+ }
486
+
487
+ const VUE_RE$1 = /\.vue/;
488
+ function NuxtScriptsCheckScripts() {
489
+ return createUnplugin(() => {
490
+ return {
491
+ name: "nuxt-scripts:check-scripts",
492
+ transform: {
493
+ filter: {
494
+ id: VUE_RE$1
495
+ },
496
+ handler(code, id) {
497
+ if (!isVue(id, { type: ["script"] }))
498
+ return;
499
+ if (!code.includes("useScript"))
500
+ return;
501
+ let nameNode;
502
+ let errorNode;
503
+ parseAndWalk(code, id, (_node) => {
504
+ if (_node.type === "VariableDeclaration" && _node.declarations?.[0]?.id?.type === "ObjectPattern") {
505
+ const objPattern = _node.declarations[0]?.id;
506
+ for (const property of objPattern.properties) {
507
+ if (property.type === "Property" && property.key.type === "Identifier" && property.key.name === "$script" && property.value.type === "Identifier") {
508
+ nameNode = _node;
509
+ }
510
+ }
511
+ }
512
+ if (nameNode) {
513
+ let sequence = _node.type === "SequenceExpression" ? _node : null;
514
+ let assignmentExpression;
515
+ if (_node.type === "VariableDeclaration") {
516
+ if (_node.declarations[0]?.init?.type === "SequenceExpression") {
517
+ sequence = _node.declarations[0]?.init;
518
+ assignmentExpression = _node.declarations[0]?.init?.expressions?.[0];
519
+ }
520
+ }
521
+ if (sequence && !assignmentExpression) {
522
+ assignmentExpression = sequence.expressions[0]?.type === "AssignmentExpression" ? sequence.expressions[0] : null;
523
+ }
524
+ if (assignmentExpression) {
525
+ const right = assignmentExpression?.right;
526
+ if (right.callee?.name === "_withAsyncContext") {
527
+ if (right.arguments[0]?.body?.name === "$script" || right.arguments[0]?.body?.callee?.object?.name === "$script") {
528
+ errorNode = nameNode;
529
+ }
530
+ }
531
+ }
532
+ }
533
+ });
534
+ if (errorNode) {
535
+ return this.error(new Error("You can't use a top-level await on $script as it will never resolve."));
536
+ }
537
+ }
198
538
  }
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/**" }
539
+ };
540
+ });
541
+ }
542
+
543
+ const WORD_OR_DOLLAR_RE = /[\w$]/;
544
+ const BLANK_CANVAS_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIHWNgAAIABQABNjN9GQAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAA0lEQVQI12NgYGBgAAAABQABXvMqOgAAAABJRU5ErkJggg==";
545
+ function isPropertyKeyAST(parent, ctx) {
546
+ return parent?.type === "Property" && ctx.key === "key" || parent?.type === "SwitchCase" && ctx.key === "test";
547
+ }
548
+ function matchAndRewrite(value, rewrites) {
549
+ for (const { from, to } of rewrites) {
550
+ const isSuffixMatch = from.startsWith(".");
551
+ const fromSlashIdx = from.indexOf("/");
552
+ const fromHost = fromSlashIdx > 0 ? from.slice(0, fromSlashIdx) : from;
553
+ const fromPath = fromSlashIdx > 0 ? from.slice(fromSlashIdx) : "";
554
+ if (!value.includes(fromHost))
555
+ continue;
556
+ const url = parseURL(value);
557
+ let shouldRewrite = false;
558
+ let rewriteSuffix = "";
559
+ if (url.host) {
560
+ const hostMatches = isSuffixMatch ? url.host.endsWith(fromHost) : url.host === fromHost;
561
+ if (hostMatches) {
562
+ const fullPath = url.pathname + (url.search || "") + (url.hash || "");
563
+ if (fromPath && fullPath.startsWith(fromPath)) {
564
+ shouldRewrite = true;
565
+ rewriteSuffix = fullPath.slice(fromPath.length);
566
+ } else if (!fromPath) {
567
+ shouldRewrite = true;
568
+ rewriteSuffix = fullPath;
569
+ }
210
570
  }
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/**" }
571
+ } else if (value.startsWith("//")) {
572
+ const hostPart = value.slice(2).split("/")[0];
573
+ const hostMatches = isSuffixMatch ? hostPart?.endsWith(fromHost) ?? false : hostPart === fromHost;
574
+ if (hostMatches) {
575
+ const remainder = value.slice(2 + (hostPart?.length ?? 0));
576
+ if (fromPath && remainder.startsWith(fromPath)) {
577
+ shouldRewrite = true;
578
+ rewriteSuffix = remainder.slice(fromPath.length);
579
+ } else if (!fromPath) {
580
+ shouldRewrite = true;
581
+ rewriteSuffix = remainder;
582
+ }
222
583
  }
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/**" }
584
+ } else if (fromPath && (value.startsWith(from) || isSuffixMatch && value.includes(from))) {
585
+ const domainEnd = value.indexOf(from) + from.length;
586
+ const nextChar = value[domainEnd];
587
+ if (!nextChar || nextChar === "/" || nextChar === "?" || nextChar === "#") {
588
+ shouldRewrite = true;
589
+ rewriteSuffix = value.slice(domainEnd);
232
590
  }
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/**" }
591
+ }
592
+ if (shouldRewrite) {
593
+ return rewriteSuffix === "/" || rewriteSuffix.startsWith("?") || rewriteSuffix.startsWith("#") ? to + rewriteSuffix : joinURL(to, rewriteSuffix);
594
+ }
595
+ }
596
+ return null;
597
+ }
598
+ const WINDOW_GLOBALS = /* @__PURE__ */ new Set(["window", "self", "globalThis"]);
599
+ const NAVIGATOR_GLOBALS = /* @__PURE__ */ new Set(["navigator"]);
600
+ function resolveToGlobal(name, scopeTracker, depth = 0) {
601
+ if (depth > 10)
602
+ return null;
603
+ const decl = scopeTracker.getDeclaration(name);
604
+ if (!decl)
605
+ return name;
606
+ if (decl instanceof ScopeTrackerFunctionParam)
607
+ return null;
608
+ if (decl instanceof ScopeTrackerVariable) {
609
+ const declarators = decl.variableNode.declarations;
610
+ if (!declarators)
611
+ return null;
612
+ for (const declarator of declarators) {
613
+ const id = declarator.id;
614
+ if (!id || id.name !== name)
615
+ continue;
616
+ const init = declarator.init;
617
+ if (!init)
618
+ return null;
619
+ if (init.type === "Identifier")
620
+ return resolveToGlobal(init.name, scopeTracker, depth + 1);
621
+ if (init.type === "MemberExpression" && init.object?.type === "Identifier") {
622
+ 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;
623
+ if (!memberProp)
624
+ return null;
625
+ const objGlobal = resolveToGlobal(init.object.name, scopeTracker, depth + 1);
626
+ if (!objGlobal)
627
+ return null;
628
+ if (WINDOW_GLOBALS.has(objGlobal) || objGlobal === "document")
629
+ return memberProp;
630
+ return null;
242
631
  }
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/**" }
632
+ return null;
633
+ }
634
+ }
635
+ return null;
636
+ }
637
+ function resolveCalleeTarget(callee, scopeTracker) {
638
+ if (callee?.type !== "MemberExpression")
639
+ return null;
640
+ const propName = callee.computed ? callee.property?.type === "Literal" && typeof callee.property.value === "string" ? callee.property.value : null : callee.property?.name;
641
+ if (!propName)
642
+ return null;
643
+ const obj = callee.object;
644
+ if (!obj || obj.type !== "Identifier")
645
+ return null;
646
+ const resolved = resolveToGlobal(obj.name, scopeTracker);
647
+ if (!resolved)
648
+ return null;
649
+ if (propName === "fetch" && WINDOW_GLOBALS.has(resolved))
650
+ return "fetch";
651
+ if (propName === "sendBeacon" && (NAVIGATOR_GLOBALS.has(resolved) || WINDOW_GLOBALS.has(resolved)))
652
+ return "sendBeacon";
653
+ if (propName === "sendBeacon" && resolved === "navigator")
654
+ return "sendBeacon";
655
+ if (propName === "XMLHttpRequest" && WINDOW_GLOBALS.has(resolved))
656
+ return "XMLHttpRequest";
657
+ if (propName === "Image" && WINDOW_GLOBALS.has(resolved))
658
+ return "Image";
659
+ return null;
660
+ }
661
+ function rewriteScriptUrlsAST(content, filename, rewrites, postProcess, options) {
662
+ const s = new MagicString(content);
663
+ function needsLeadingSpace(start) {
664
+ const prev = content[start - 1];
665
+ return prev && WORD_OR_DOLLAR_RE.test(prev) ? " " : "";
666
+ }
667
+ const scopeTracker = new ScopeTracker({ preserveExitedScopes: true });
668
+ const { program } = parseAndWalk(content, filename, { scopeTracker });
669
+ scopeTracker.freeze();
670
+ walk(program, {
671
+ scopeTracker,
672
+ enter(node, parent, ctx) {
673
+ if (node.type === "Literal" && typeof node.value === "string") {
674
+ const value = node.value;
675
+ const rewritten = matchAndRewrite(value, rewrites);
676
+ if (rewritten === null)
677
+ return;
678
+ const quote = content[node.start];
679
+ if (isPropertyKeyAST(parent, ctx)) {
680
+ s.overwrite(node.start, node.end, quote + rewritten + quote);
681
+ } else {
682
+ s.overwrite(node.start, node.end, `${needsLeadingSpace(node.start)}self.location.origin+${quote}${rewritten}${quote}`);
683
+ }
262
684
  }
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/**" }
685
+ if (node.type === "TemplateLiteral") {
686
+ const quasis = node.quasis;
687
+ const expressions = node.expressions;
688
+ if (expressions?.length === 0 && quasis?.length === 1) {
689
+ const value = quasis[0].value?.cooked ?? quasis[0].value?.raw;
690
+ if (typeof value !== "string")
691
+ return;
692
+ const rewritten = matchAndRewrite(value, rewrites);
693
+ if (rewritten === null)
694
+ return;
695
+ if (isPropertyKeyAST(parent, ctx)) {
696
+ s.overwrite(node.start, node.end, `\`${rewritten}\``);
697
+ } else {
698
+ s.overwrite(node.start, node.end, `${needsLeadingSpace(node.start)}self.location.origin+\`${rewritten}\``);
699
+ }
700
+ } else if (expressions?.length > 0 && quasis?.length > 0) {
701
+ const firstQuasi = quasis[0];
702
+ const value = firstQuasi.value?.cooked ?? firstQuasi.value?.raw;
703
+ if (typeof value !== "string" || isPropertyKeyAST(parent, ctx))
704
+ return;
705
+ const rewritten = matchAndRewrite(value, rewrites);
706
+ if (rewritten === null)
707
+ return;
708
+ s.overwrite(node.start, firstQuasi.end, `${needsLeadingSpace(node.start)}self.location.origin+\`${rewritten}`);
709
+ }
275
710
  }
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/**" }
711
+ if (node.type === "CallExpression" && !options?.skipApiRewrites) {
712
+ const callee = node.callee;
713
+ const canvasPropName = callee?.type === "MemberExpression" ? callee.computed ? callee.property?.type === "Literal" && typeof callee.property.value === "string" ? callee.property.value : null : callee.property?.name : null;
714
+ if (canvasPropName === "toDataURL" && callee.object) {
715
+ const blankCanvas = `"${BLANK_CANVAS_DATA_URL}"`;
716
+ if (callee.object.type === "Identifier") {
717
+ const decl = scopeTracker.getDeclaration(callee.object.name);
718
+ if (decl instanceof ScopeTrackerFunction || decl instanceof ScopeTrackerIdentifier) ; else {
719
+ s.overwrite(node.start, node.end, blankCanvas);
720
+ return;
721
+ }
722
+ } else {
723
+ s.overwrite(node.start, node.end, blankCanvas);
724
+ return;
725
+ }
726
+ }
727
+ if (canvasPropName === "getExtension") {
728
+ const args = node.arguments;
729
+ if (args?.length === 1 && args[0]?.type === "Literal" && args[0].value === "WEBGL_debug_renderer_info") {
730
+ s.overwrite(node.start, node.end, "null");
731
+ return;
732
+ }
733
+ }
734
+ if (callee?.type === "Identifier" && callee.name === "fetch") {
735
+ if (!scopeTracker.getDeclaration("fetch"))
736
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.fetch");
737
+ return;
738
+ }
739
+ const target = resolveCalleeTarget(callee, scopeTracker);
740
+ if (target === "fetch" || target === "sendBeacon") {
741
+ s.overwrite(callee.start, callee.end, `__nuxtScripts.${target}`);
742
+ return;
743
+ }
744
+ if (callee?.type === "MemberExpression" && !callee.computed && callee.property?.name === "sendBeacon" && callee.object?.type === "Identifier") {
745
+ const resolved = resolveToGlobal(callee.object.name, scopeTracker);
746
+ if (resolved === null) {
747
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.sendBeacon");
748
+ }
749
+ }
304
750
  }
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 });
751
+ if (node.type === "NewExpression" && !options?.skipApiRewrites) {
752
+ const callee = node.callee;
753
+ if (callee?.type === "Identifier" && callee.name === "XMLHttpRequest") {
754
+ if (!scopeTracker.getDeclaration("XMLHttpRequest"))
755
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.XMLHttpRequest");
756
+ return;
757
+ }
758
+ if (callee?.type === "Identifier" && callee.name === "Image") {
759
+ if (!scopeTracker.getDeclaration("Image"))
760
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.Image");
761
+ return;
762
+ }
763
+ const target = resolveCalleeTarget(callee, scopeTracker);
764
+ if (target === "XMLHttpRequest" || target === "Image") {
765
+ s.overwrite(callee.start, callee.end, `__nuxtScripts.${target}`);
766
+ return;
767
+ }
768
+ if (callee?.type === "MemberExpression" && callee.object?.type === "Identifier") {
769
+ const propName = callee.computed ? callee.property?.type === "Literal" && typeof callee.property.value === "string" ? callee.property.value : null : callee.property?.name;
770
+ if (propName === "XMLHttpRequest" || propName === "Image") {
771
+ const resolved = resolveToGlobal(callee.object.name, scopeTracker);
772
+ if (resolved === null) {
773
+ s.overwrite(callee.start, callee.end, `__nuxtScripts.${propName}`);
774
+ }
775
+ }
776
+ }
328
777
  }
329
778
  }
779
+ });
780
+ let output = s.toString();
781
+ if (postProcess) {
782
+ output = postProcess(output, rewrites);
330
783
  }
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);
784
+ return output;
359
785
  }
360
786
 
361
787
  const SEVEN_DAYS_IN_MS = 7 * 24 * 60 * 60 * 1e3;
788
+ const PROTOCOL_RELATIVE_RE = /^\/\//;
789
+ const VUE_RE = /\.vue/;
790
+ const JS_RE = /\.[cm]?[jt]sx?$/;
791
+ const TEST_RE = /\.(?:test|spec)\./;
792
+ const UPPERCASE_RE = /^[A-Z]$/;
793
+ const USE_SCRIPT_RE = /^useScript/;
362
794
  function calculateIntegrity(content, algorithm = "sha384") {
363
795
  const hash = createHash(algorithm).update(content).digest("base64");
364
796
  return `${algorithm}-${hash}`;
@@ -371,14 +803,12 @@ async function isCacheExpired(storage, filename, cacheMaxAge = SEVEN_DAYS_IN_MS)
371
803
  }
372
804
  return Date.now() - meta.timestamp > cacheMaxAge;
373
805
  }
374
- function normalizeScriptData(src, assetsBaseURL = "/_scripts") {
806
+ function normalizeScriptData(src, assetsBaseURL = "/_scripts/assets") {
375
807
  if (hasProtocol(src, { acceptRelative: true })) {
376
- src = src.replace(/^\/\//, "https://");
808
+ src = src.replace(PROTOCOL_RELATIVE_RE, "https://");
377
809
  const url = parseURL(src);
378
- const file = [
379
- `${hash(url)}.js`
380
- // force an extension
381
- ].filter(Boolean).join("-");
810
+ const h = hash(url);
811
+ const file = `${h.startsWith("-") ? `_${h.slice(1)}` : h}.js`;
382
812
  const nuxt = tryUseNuxt();
383
813
  const cdnURL = nuxt?.options.runtimeConfig?.app?.cdnURL || nuxt?.options.app?.cdnURL || "";
384
814
  const baseURL = cdnURL || nuxt?.options.app.baseURL || "";
@@ -387,7 +817,7 @@ function normalizeScriptData(src, assetsBaseURL = "/_scripts") {
387
817
  return { url: src };
388
818
  }
389
819
  async function downloadScript(opts, renderedScript, fetchOptions, cacheMaxAge) {
390
- const { src, url, filename, forceDownload, integrity, proxyRewrites } = opts;
820
+ const { src, url, filename, forceDownload, integrity, proxyRewrites, postProcess, skipApiRewrites } = opts;
391
821
  if (src === url || !filename) {
392
822
  return;
393
823
  }
@@ -425,7 +855,7 @@ async function downloadScript(opts, renderedScript, fetchOptions, cacheMaxAge) {
425
855
  await storage.setItemRaw(`bundle:${filename}`, res);
426
856
  if (proxyRewrites?.length && res) {
427
857
  const content = res.toString("utf-8");
428
- const rewritten = rewriteScriptUrls(content, proxyRewrites);
858
+ const rewritten = rewriteScriptUrlsAST(content, filename || "script.js", proxyRewrites, postProcess, { skipApiRewrites });
429
859
  res = Buffer.from(rewritten, "utf-8");
430
860
  logger.debug(`Rewrote ${proxyRewrites.length} URL patterns in ${filename}`);
431
861
  }
@@ -482,8 +912,8 @@ function NuxtScriptBundleTransformer(options = {
482
912
  transform: {
483
913
  filter: {
484
914
  id: {
485
- include: [/\.vue/, /\.[cm]?[jt]sx?$/],
486
- exclude: [/\.(?:test|spec)\./]
915
+ include: [VUE_RE, JS_RE],
916
+ exclude: [TEST_RE]
487
917
  }
488
918
  },
489
919
  async handler(code, id) {
@@ -493,11 +923,11 @@ function NuxtScriptBundleTransformer(options = {
493
923
  return;
494
924
  const s = new MagicString(code);
495
925
  const deferredOps = [];
496
- parseAndWalk(code, id, function(_node) {
926
+ parseAndWalk(code, id, (_node) => {
497
927
  const calleeName = _node.callee?.name;
498
928
  if (!calleeName)
499
929
  return;
500
- const isValidCallee = calleeName === "useScript" || calleeName?.startsWith("useScript") && /^[A-Z]$/.test(calleeName?.charAt(9)) && !calleeName.startsWith("useScriptTrigger") && !calleeName.startsWith("useScriptEvent");
930
+ const isValidCallee = calleeName === "useScript" || calleeName?.startsWith("useScript") && UPPERCASE_RE.test(calleeName?.charAt(9)) && !calleeName.startsWith("useScriptTrigger") && !calleeName.startsWith("useScriptEvent");
501
931
  if (_node.type === "CallExpression" && _node.callee.type === "Identifier" && isValidCallee) {
502
932
  const fnName = _node.callee?.name;
503
933
  const node = _node;
@@ -505,7 +935,7 @@ function NuxtScriptBundleTransformer(options = {
505
935
  let src;
506
936
  let registryKey;
507
937
  if (fnName !== "useScript") {
508
- const baseName = fnName.replace(/^useScript/, "");
938
+ const baseName = fnName.replace(USE_SCRIPT_RE, "");
509
939
  registryKey = baseName.length > 0 ? baseName.charAt(0).toLowerCase() + baseName.slice(1) : void 0;
510
940
  }
511
941
  if (fnName === "useScript") {
@@ -625,11 +1055,14 @@ function NuxtScriptBundleTransformer(options = {
625
1055
  const { url: _url, filename } = normalizeScriptData(src, options.assetsBaseURL);
626
1056
  const script = options.scripts?.find((s2) => s2.import.name === fnName);
627
1057
  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;
1058
+ const proxyConfig = !firstPartyOptOut && proxyConfigKey ? options.proxyConfigs?.[proxyConfigKey] : void 0;
1059
+ const proxyRewrites = proxyConfig?.rewrite;
1060
+ const postProcess = proxyConfig?.postProcess;
1061
+ const skipApiRewrites = !!(registryKey && options.partytownScripts?.has(registryKey));
629
1062
  deferredOps.push(async () => {
630
1063
  let url = _url;
631
1064
  try {
632
- await downloadScript({ src, url, filename, forceDownload, proxyRewrites, integrity: options.integrity }, renderedScript, options.fetchOptions, options.cacheMaxAge);
1065
+ await downloadScript({ src, url, filename, forceDownload, proxyRewrites, postProcess, integrity: options.integrity, skipApiRewrites }, renderedScript, options.fetchOptions, options.cacheMaxAge);
633
1066
  } catch (e) {
634
1067
  if (options.fallbackOnSrcOnBundleFail) {
635
1068
  logger.warn(`[Nuxt Scripts: Bundle Transformer] Failed to bundle ${src}. Fallback to remote loading.`);
@@ -685,7 +1118,7 @@ function NuxtScriptBundleTransformer(options = {
685
1118
  s.appendRight(node.arguments[0].start + 1, ` scriptInput: { src: '${url}'${integrityProps} }, `);
686
1119
  }
687
1120
  } else {
688
- s.appendRight(node.callee.end, `({ scriptInput: { src: '${url}'${integrityProps} } })`);
1121
+ s.overwrite(node.callee.end, node.end, `({ scriptInput: { src: '${url}'${integrityProps} } })`);
689
1122
  }
690
1123
  }
691
1124
  });
@@ -709,101 +1142,7 @@ function NuxtScriptBundleTransformer(options = {
709
1142
  });
710
1143
  }
711
1144
 
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
-
1145
+ const TRIGGER_PLACEHOLDER_RE = /"__TRIGGER_PLACEHOLDER__"/g;
807
1146
  function registerTypeTemplates({ nuxt, config, newScripts }) {
808
1147
  addTypeTemplate({
809
1148
  filename: "types/nuxt-scripts-augments.d.ts",
@@ -815,7 +1154,7 @@ function registerTypeTemplates({ nuxt, config, newScripts }) {
815
1154
  let augments = `// Generated by @nuxt/scripts
816
1155
  declare module '#app' {
817
1156
  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>
1157
+ $scripts: Record<${[...[...Object.keys(config.globals || {}), ...Object.keys(config.registry || {})].map((k) => `'${k}'`), ...["string"]].join(" | ")}, import('#nuxt-scripts/types').UseScriptContext<any> | undefined>
819
1158
  _scripts: Record<string, import('#nuxt-scripts/types').NuxtDevToolsScriptInstance>
820
1159
  }
821
1160
  interface RuntimeNuxtHooks {
@@ -896,28 +1235,30 @@ function templatePlugin(config, registry) {
896
1235
  let needsInteractionImport = false;
897
1236
  let needsServiceWorkerImport = false;
898
1237
  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)}`);
1238
+ if (c === false)
1239
+ continue;
1240
+ const importDefinition = registry.find((i) => i.import.name.toLowerCase() === `usescript${k.toLowerCase()}`);
900
1241
  if (importDefinition) {
901
1242
  resolvedRegistryKeys.push(k);
902
1243
  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);
1244
+ const [input, scriptOptions] = c;
1245
+ if (scriptOptions) {
1246
+ const opts = { ...scriptOptions };
1247
+ const triggerResolved = resolveTriggerForTemplate(opts.trigger);
909
1248
  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;
1249
+ opts.trigger = "__TRIGGER_PLACEHOLDER__";
1250
+ if (triggerResolved.includes("useScriptTriggerIdleTimeout"))
1251
+ needsIdleTimeoutImport = true;
1252
+ if (triggerResolved.includes("useScriptTriggerInteraction"))
1253
+ needsInteractionImport = true;
1254
+ if (triggerResolved.includes("useScriptTriggerServiceWorker"))
1255
+ needsServiceWorkerImport = true;
914
1256
  }
915
- const args = { ...input, scriptOptions };
916
- const argsJson = triggerResolved ? JSON.stringify(args).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved) : JSON.stringify(args);
1257
+ const args = { ...input, scriptOptions: opts };
1258
+ const argsJson = triggerResolved ? JSON.stringify(args).replace(TRIGGER_PLACEHOLDER_RE, triggerResolved) : JSON.stringify(args);
917
1259
  inits.push(`const ${k} = ${importDefinition.import.name}(${argsJson})`);
918
1260
  } else {
919
- const args = (typeof c !== "object" ? {} : c) || {};
920
- inits.push(`const ${k} = ${importDefinition.import.name}(${JSON.stringify(args)})`);
1261
+ inits.push(`const ${k} = ${importDefinition.import.name}(${JSON.stringify(input)})`);
921
1262
  }
922
1263
  }
923
1264
  }
@@ -928,11 +1269,14 @@ function templatePlugin(config, registry) {
928
1269
  const options = c[1];
929
1270
  const triggerResolved = resolveTriggerForTemplate(options?.trigger);
930
1271
  if (triggerResolved) {
931
- if (triggerResolved.includes("useScriptTriggerIdleTimeout")) needsIdleTimeoutImport = true;
932
- if (triggerResolved.includes("useScriptTriggerInteraction")) needsInteractionImport = true;
933
- if (triggerResolved.includes("useScriptTriggerServiceWorker")) needsServiceWorkerImport = true;
1272
+ if (triggerResolved.includes("useScriptTriggerIdleTimeout"))
1273
+ needsIdleTimeoutImport = true;
1274
+ if (triggerResolved.includes("useScriptTriggerInteraction"))
1275
+ needsInteractionImport = true;
1276
+ if (triggerResolved.includes("useScriptTriggerServiceWorker"))
1277
+ needsServiceWorkerImport = true;
934
1278
  const resolvedOptions = { ...options, trigger: "__TRIGGER_PLACEHOLDER__" };
935
- const optionsJson = JSON.stringify(resolvedOptions).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved);
1279
+ const optionsJson = JSON.stringify(resolvedOptions).replace(TRIGGER_PLACEHOLDER_RE, triggerResolved);
936
1280
  inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...typeof c[0] === "string" ? { src: c[0] } : c[0] })}, { ...${optionsJson}, use: () => ({ ${k}: window.${k} }) })`);
937
1281
  } else {
938
1282
  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 +1284,14 @@ function templatePlugin(config, registry) {
940
1284
  } else if (typeof c === "object" && c !== null) {
941
1285
  const triggerResolved = resolveTriggerForTemplate(c.trigger);
942
1286
  if (triggerResolved) {
943
- if (triggerResolved.includes("useScriptTriggerIdleTimeout")) needsIdleTimeoutImport = true;
944
- if (triggerResolved.includes("useScriptTriggerInteraction")) needsInteractionImport = true;
945
- if (triggerResolved.includes("useScriptTriggerServiceWorker")) needsServiceWorkerImport = true;
1287
+ if (triggerResolved.includes("useScriptTriggerIdleTimeout"))
1288
+ needsIdleTimeoutImport = true;
1289
+ if (triggerResolved.includes("useScriptTriggerInteraction"))
1290
+ needsInteractionImport = true;
1291
+ if (triggerResolved.includes("useScriptTriggerServiceWorker"))
1292
+ needsServiceWorkerImport = true;
946
1293
  const resolvedOptions = { ...c, trigger: "__TRIGGER_PLACEHOLDER__" };
947
- const argsJson = JSON.stringify({ key: k, ...resolvedOptions }).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved);
1294
+ const argsJson = JSON.stringify({ key: k, ...resolvedOptions }).replace(TRIGGER_PLACEHOLDER_RE, triggerResolved);
948
1295
  inits.push(`const ${k} = useScript(${argsJson}, { use: () => ({ ${k}: window.${k} }) })`);
949
1296
  } else {
950
1297
  inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...c })}, { use: () => ({ ${k}: window.${k} }) })`);
@@ -973,19 +1320,106 @@ function templatePlugin(config, registry) {
973
1320
  ` parallel: true,`,
974
1321
  ` setup() {`,
975
1322
  ...inits.map((i) => ` ${i}`),
976
- ` return { provide: { $scripts: { ${[...Object.keys(config.globals || {}), ...resolvedRegistryKeys].join(", ")} } } }`,
1323
+ ` return { provide: { scripts: { ${[...Object.keys(config.globals || {}), ...resolvedRegistryKeys].join(", ")} } } }`,
977
1324
  ` }`,
978
1325
  `})`
979
1326
  ].join("\n");
980
1327
  }
981
1328
 
1329
+ const SELF_CLOSING_SCRIPT_RE = /<((?:Script[A-Z]|script-)\w[\w-]*)\b([^>]*?)\/\s*>/g;
1330
+ function fixSelfClosingScriptComponents(nuxt) {
1331
+ function expandTags(content) {
1332
+ SELF_CLOSING_SCRIPT_RE.lastIndex = 0;
1333
+ if (!SELF_CLOSING_SCRIPT_RE.test(content))
1334
+ return null;
1335
+ SELF_CLOSING_SCRIPT_RE.lastIndex = 0;
1336
+ return content.replace(SELF_CLOSING_SCRIPT_RE, (_, tag, attrs) => `<${tag}${attrs.trimEnd()}></${tag}>`);
1337
+ }
1338
+ function fixFile(filePath) {
1339
+ if (!existsSync(filePath))
1340
+ return;
1341
+ const content = readFileSync(filePath, "utf-8");
1342
+ const fixed = expandTags(content);
1343
+ if (fixed)
1344
+ nuxt.vfs[filePath] = fixed;
1345
+ }
1346
+ function scanDir(dir) {
1347
+ if (!existsSync(dir))
1348
+ return;
1349
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1350
+ const fullPath = resolve(dir, entry.name);
1351
+ if (entry.isDirectory())
1352
+ scanDir(fullPath);
1353
+ else if (entry.name.endsWith(".vue"))
1354
+ fixFile(fullPath);
1355
+ }
1356
+ }
1357
+ const pagesDirs = /* @__PURE__ */ new Set();
1358
+ for (const layer of nuxt.options._layers) {
1359
+ pagesDirs.add(resolve(
1360
+ layer.config.srcDir,
1361
+ layer.config.dir?.pages || "pages"
1362
+ ));
1363
+ }
1364
+ for (const dir of pagesDirs) scanDir(dir);
1365
+ if (nuxt.options.dev) {
1366
+ nuxt.hook("builder:watch", (_event, relativePath) => {
1367
+ if (!relativePath.endsWith(".vue"))
1368
+ return;
1369
+ for (const layer of nuxt.options._layers) {
1370
+ const fullPath = resolve(layer.config.srcDir, relativePath);
1371
+ for (const dir of pagesDirs) {
1372
+ if (fullPath.startsWith(`${dir}/`)) {
1373
+ fixFile(fullPath);
1374
+ return;
1375
+ }
1376
+ }
1377
+ }
1378
+ });
1379
+ }
1380
+ }
1381
+ const REGISTRY_ENV_DEFAULTS = {
1382
+ bingUet: { id: "" },
1383
+ clarity: { id: "" },
1384
+ cloudflareWebAnalytics: { token: "" },
1385
+ crisp: { id: "" },
1386
+ databuddyAnalytics: { clientId: "" },
1387
+ fathomAnalytics: { site: "" },
1388
+ googleAdsense: { client: "" },
1389
+ googleAnalytics: { id: "" },
1390
+ googleMaps: { apiKey: "" },
1391
+ googleRecaptcha: { siteKey: "" },
1392
+ googleSignIn: { clientId: "" },
1393
+ googleTagManager: { id: "" },
1394
+ hotjar: { id: "" },
1395
+ intercom: { app_id: "" },
1396
+ matomoAnalytics: { matomoUrl: "" },
1397
+ mixpanelAnalytics: { token: "" },
1398
+ metaPixel: { id: "" },
1399
+ paypal: { clientId: "" },
1400
+ plausibleAnalytics: { domain: "" },
1401
+ posthog: { apiKey: "" },
1402
+ redditPixel: { id: "" },
1403
+ rybbitAnalytics: { siteId: "" },
1404
+ segment: { writeKey: "" },
1405
+ snapchatPixel: { id: "" },
1406
+ stripe: {},
1407
+ tiktokPixel: { id: "" },
1408
+ umamiAnalytics: { websiteId: "" },
1409
+ vercelAnalytics: {},
1410
+ xPixel: { id: "" }
1411
+ };
1412
+ const UPPER_RE = /([A-Z])/g;
1413
+ const toScreamingSnake = (s) => s.replace(UPPER_RE, "_$1").toUpperCase();
982
1414
  const PARTYTOWN_FORWARDS = {
1415
+ bingUet: ["uetq.push"],
983
1416
  googleAnalytics: ["dataLayer.push", "gtag"],
984
- plausible: ["plausible"],
985
- fathom: ["fathom", "fathom.trackEvent", "fathom.trackPageview"],
986
- umami: ["umami", "umami.track"],
987
- matomo: ["_paq.push"],
1417
+ plausibleAnalytics: ["plausible"],
1418
+ fathomAnalytics: ["fathom", "fathom.trackEvent", "fathom.trackPageview"],
1419
+ umamiAnalytics: ["umami", "umami.track"],
1420
+ matomoAnalytics: ["_paq.push"],
988
1421
  segment: ["analytics", "analytics.track", "analytics.page", "analytics.identify"],
1422
+ mixpanelAnalytics: ["mixpanel", "mixpanel.init", "mixpanel.track", "mixpanel.identify", "mixpanel.people.set", "mixpanel.reset", "mixpanel.register"],
989
1423
  metaPixel: ["fbq"],
990
1424
  xPixel: ["twq"],
991
1425
  tiktokPixel: ["ttq.track", "ttq.page", "ttq.identify"],
@@ -1039,39 +1473,51 @@ const module$1 = defineNuxtModule({
1039
1473
  if (unheadVersion?.startsWith("1")) {
1040
1474
  logger.error(`Nuxt Scripts requires Unhead >= 2, you are using v${unheadVersion}. Please run \`nuxi upgrade --clean\` to upgrade...`);
1041
1475
  }
1476
+ if (config.registry) {
1477
+ normalizeRegistryConfig(config.registry);
1478
+ nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
1479
+ const registryWithDefaults = {};
1480
+ for (const [key, entry] of Object.entries(config.registry)) {
1481
+ if (entry === false)
1482
+ continue;
1483
+ const input = entry[0];
1484
+ const envDefaults = REGISTRY_ENV_DEFAULTS[key];
1485
+ if (!envDefaults) {
1486
+ registryWithDefaults[key] = input;
1487
+ continue;
1488
+ }
1489
+ const envResolved = {};
1490
+ for (const [field, defaultValue] of Object.entries(envDefaults)) {
1491
+ const envKey = `NUXT_PUBLIC_SCRIPTS_${toScreamingSnake(key)}_${toScreamingSnake(field)}`;
1492
+ envResolved[field] = process.env[envKey] || defaultValue;
1493
+ }
1494
+ registryWithDefaults[key] = defu(input, envResolved);
1495
+ }
1496
+ nuxt.options.runtimeConfig.public.scripts = defu(
1497
+ nuxt.options.runtimeConfig.public.scripts || {},
1498
+ registryWithDefaults
1499
+ );
1500
+ }
1501
+ const googleMapsEnabled = config.googleStaticMapsProxy?.enabled || !!config.registry?.googleMaps;
1042
1502
  nuxt.options.runtimeConfig["nuxt-scripts"] = {
1043
1503
  version,
1044
1504
  // 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")
1505
+ googleStaticMapsProxy: googleMapsEnabled ? { apiKey: nuxt.options.runtimeConfig.public.scripts?.googleMaps?.apiKey } : void 0
1047
1506
  };
1048
1507
  nuxt.options.runtimeConfig.public["nuxt-scripts"] = {
1049
1508
  // expose for devtools
1050
1509
  version: nuxt.options.dev ? version : void 0,
1051
1510
  defaultScriptOptions: config.defaultScriptOptions,
1052
1511
  // Only expose enabled and cacheMaxAge to client, not apiKey
1053
- googleStaticMapsProxy: config.googleStaticMapsProxy?.enabled ? { enabled: true, cacheMaxAge: config.googleStaticMapsProxy.cacheMaxAge } : void 0
1512
+ googleStaticMapsProxy: googleMapsEnabled ? { enabled: true, cacheMaxAge: config.googleStaticMapsProxy?.cacheMaxAge ?? 3600 } : void 0
1054
1513
  };
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
1514
  if (config.defaultScriptOptions?.bundle !== void 0) {
1063
1515
  logger.warn(
1064
1516
  "`scripts.defaultScriptOptions.bundle` is deprecated. Use `scripts.firstParty: true` instead. First-party mode is now enabled by default."
1065
1517
  );
1066
1518
  }
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";
1519
+ const firstParty = await setupFirstParty(config, resolvePath);
1520
+ const assetsPrefix = firstParty.assetsPrefix;
1075
1521
  if (config.partytown?.length) {
1076
1522
  config.registry = config.registry || {};
1077
1523
  const requiredForwards = [];
@@ -1084,12 +1530,8 @@ const module$1 = defineNuxtModule({
1084
1530
  }
1085
1531
  const reg = config.registry;
1086
1532
  const existing = reg[scriptKey];
1087
- if (Array.isArray(existing)) {
1533
+ if (existing) {
1088
1534
  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
1535
  } else {
1094
1536
  reg[scriptKey] = [{}, { partytown: true }];
1095
1537
  }
@@ -1123,110 +1565,14 @@ const module$1 = defineNuxtModule({
1123
1565
  path: await resolvePath("./runtime/components"),
1124
1566
  pathPrefix: false
1125
1567
  });
1568
+ fixSelfClosingScriptComponents(nuxt);
1126
1569
  addTemplate({
1127
1570
  filename: "nuxt-scripts-trigger-resolver.mjs",
1128
1571
  getContents() {
1129
1572
  return templateTriggerResolver(config.defaultScriptOptions);
1130
1573
  }
1131
1574
  });
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
- }
1575
+ logger.debug("[nuxt-scripts] First-party config:", firstParty);
1230
1576
  const scripts = await registry(resolvePath);
1231
1577
  for (const script of scripts) {
1232
1578
  if (script.import?.name) {
@@ -1254,89 +1600,26 @@ export default defineNuxtPlugin({
1254
1600
  });
1255
1601
  }
1256
1602
  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
- }
1603
+ if (firstParty.enabled) {
1604
+ const { interceptRules, devtools: devtoolsData } = finalizeFirstParty({
1605
+ firstParty,
1606
+ registry: config.registry,
1607
+ registryScripts,
1608
+ nuxtOptions: nuxt.options
1609
+ });
1610
+ if (devtoolsData) {
1611
+ nuxt.options.runtimeConfig.public["nuxt-scripts-devtools"] = devtoolsData;
1305
1612
  }
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
- }
1613
+ if (config.partytown?.length && hasNuxtModule("@nuxtjs/partytown") && interceptRules.length) {
1614
+ const partytownConfig = nuxt.options.partytown || {};
1615
+ if (!partytownConfig.resolveUrl) {
1616
+ partytownConfig.resolveUrl = generatePartytownResolveUrl(interceptRules);
1617
+ nuxt.options.partytown = partytownConfig;
1618
+ logger.info("[partytown] Auto-configured resolveUrl for first-party proxy");
1619
+ } else {
1620
+ 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
1621
  }
1326
1622
  }
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
1623
  }
1341
1624
  const moduleInstallPromises = /* @__PURE__ */ new Map();
1342
1625
  addBuildPlugin(NuxtScriptsCheckScripts(), {
@@ -1345,9 +1628,9 @@ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
1345
1628
  addBuildPlugin(NuxtScriptBundleTransformer({
1346
1629
  scripts: registryScriptsWithImport,
1347
1630
  registryConfig: nuxt.options.runtimeConfig.public.scripts,
1348
- defaultBundle: firstPartyEnabled || config.defaultScriptOptions?.bundle,
1349
- firstPartyEnabled,
1350
- firstPartyCollectPrefix,
1631
+ defaultBundle: firstParty.enabled || config.defaultScriptOptions?.bundle,
1632
+ proxyConfigs: firstParty.proxyConfigs,
1633
+ partytownScripts: new Set(config.partytown || []),
1351
1634
  moduleDetected(module) {
1352
1635
  if (nuxt.options.dev && module !== "@nuxt/scripts" && !moduleInstallPromises.has(module) && !hasNuxtModule(module))
1353
1636
  moduleInstallPromises.set(module, () => installNuxtModule(module));
@@ -1360,37 +1643,44 @@ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
1360
1643
  renderedScript
1361
1644
  }));
1362
1645
  nuxt.hooks.hook("build:done", async () => {
1363
- const initPromise = Array.from(moduleInstallPromises.values());
1646
+ const initPromise = [...moduleInstallPromises.values()];
1364
1647
  for (const p of initPromise)
1365
1648
  await p?.();
1366
1649
  });
1367
1650
  });
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
- });
1651
+ const enabledEndpoints = {};
1652
+ for (const script of scripts) {
1653
+ if (!script.serverHandlers?.length || !script.registryKey)
1654
+ continue;
1655
+ const isEnabled = script.registryKey === "googleMaps" ? config.googleStaticMapsProxy?.enabled || config.registry?.googleMaps : config.registry?.[script.registryKey];
1656
+ if (!isEnabled)
1657
+ continue;
1658
+ enabledEndpoints[script.registryKey] = true;
1659
+ for (const handler of script.serverHandlers) {
1660
+ addServerHandler({
1661
+ route: handler.route,
1662
+ handler: handler.handler,
1663
+ middleware: handler.middleware
1664
+ });
1665
+ }
1666
+ if (script.registryKey === "gravatar") {
1667
+ const gravatarConfig = config.registry?.gravatar?.[0] || {};
1668
+ nuxt.options.runtimeConfig.public["nuxt-scripts"] = defu(
1669
+ { gravatarProxy: { cacheMaxAge: gravatarConfig.cacheMaxAge ?? 3600 } },
1670
+ nuxt.options.runtimeConfig.public["nuxt-scripts"]
1671
+ );
1672
+ }
1673
+ if (script.registryKey === "googleMaps") {
1674
+ nuxt.options.runtimeConfig["nuxt-scripts"] = defu(
1675
+ { googleMapsGeocodeProxy: { apiKey: nuxt.options.runtimeConfig.public.scripts?.googleMaps?.apiKey } },
1676
+ nuxt.options.runtimeConfig["nuxt-scripts"]
1677
+ );
1678
+ }
1373
1679
  }
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
- });
1680
+ nuxt.options.runtimeConfig.public["nuxt-scripts"] = defu(
1681
+ { endpoints: enabledEndpoints },
1682
+ nuxt.options.runtimeConfig.public["nuxt-scripts"]
1683
+ );
1394
1684
  if (nuxt.options.dev) {
1395
1685
  setupDevToolsUI(config, resolvePath);
1396
1686
  }