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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) 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/{B66N9HCo.js → 9LJPrOyI.js} +1 -1
  5. package/dist/client/_nuxt/DFEfk2pB.js +162 -0
  6. package/dist/client/_nuxt/{DvH517bE.js → DMlY-BNa.js} +1 -1
  7. package/dist/client/_nuxt/{DfLgoB--.js → __ZZTkMj.js} +1 -1
  8. package/dist/client/_nuxt/builds/latest.json +1 -1
  9. package/dist/client/_nuxt/builds/meta/8212d4fa-7985-421b-815a-03a886e667d4.json +1 -0
  10. package/dist/client/_nuxt/entry.CACgbLJl.css +1 -0
  11. package/dist/client/_nuxt/error-404.CHeaW3dp.css +1 -0
  12. package/dist/client/_nuxt/error-500.DvOvWme_.css +1 -0
  13. package/dist/client/index.html +1 -1
  14. package/dist/module.d.mts +27 -18
  15. package/dist/module.d.ts +178 -0
  16. package/dist/module.json +1 -1
  17. package/dist/module.mjs +747 -527
  18. package/dist/registry.d.ts +6 -0
  19. package/dist/registry.mjs +74 -21
  20. package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.d.vue.ts +2 -2
  21. package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +7 -7
  22. package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue.d.ts +2 -2
  23. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue +6 -6
  24. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsCircle.vue +7 -7
  25. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.vue +6 -6
  26. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue +12 -12
  27. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue +6 -6
  28. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.d.vue.ts +1 -1
  29. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue +6 -6
  30. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue.d.ts +1 -1
  31. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPinElement.vue +5 -5
  32. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolygon.vue +7 -7
  33. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolyline.vue +7 -7
  34. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsRectangle.vue +7 -7
  35. package/dist/runtime/components/ScriptCrisp.vue +1 -1
  36. package/dist/runtime/components/ScriptGoogleAdsense.vue +1 -1
  37. package/dist/runtime/components/ScriptGravatar.d.vue.ts +22 -0
  38. package/dist/runtime/components/ScriptGravatar.vue +46 -0
  39. package/dist/runtime/components/ScriptGravatar.vue.d.ts +22 -0
  40. package/dist/runtime/components/ScriptInstagramEmbed.vue +1 -1
  41. package/dist/runtime/components/ScriptIntercom.vue +4 -3
  42. package/dist/runtime/components/ScriptPayPalButtons.d.vue.ts +43 -32
  43. package/dist/runtime/components/ScriptPayPalButtons.vue +48 -79
  44. package/dist/runtime/components/ScriptPayPalButtons.vue.d.ts +43 -32
  45. package/dist/runtime/components/ScriptPayPalMessages.d.vue.ts +37 -23
  46. package/dist/runtime/components/ScriptPayPalMessages.vue +46 -50
  47. package/dist/runtime/components/ScriptPayPalMessages.vue.d.ts +37 -23
  48. package/dist/runtime/components/ScriptStripePricingTable.vue +2 -2
  49. package/dist/runtime/components/ScriptVimeoPlayer.d.vue.ts +9 -0
  50. package/dist/runtime/components/ScriptVimeoPlayer.vue +13 -10
  51. package/dist/runtime/components/ScriptVimeoPlayer.vue.d.ts +9 -0
  52. package/dist/runtime/components/ScriptXEmbed.d.vue.ts +1 -1
  53. package/dist/runtime/components/ScriptXEmbed.vue +1 -1
  54. package/dist/runtime/components/ScriptXEmbed.vue.d.ts +1 -1
  55. package/dist/runtime/components/ScriptYouTubePlayer.d.vue.ts +2 -2
  56. package/dist/runtime/components/ScriptYouTubePlayer.vue +11 -5
  57. package/dist/runtime/components/ScriptYouTubePlayer.vue.d.ts +2 -2
  58. package/dist/runtime/composables/useScript.js +11 -6
  59. package/dist/runtime/composables/useScriptEventPage.js +2 -2
  60. package/dist/runtime/composables/useScriptTriggerConsent.js +1 -1
  61. package/dist/runtime/composables/useScriptTriggerElement.js +1 -1
  62. package/dist/runtime/composables/useScriptTriggerIdleTimeout.js +1 -1
  63. package/dist/runtime/registry/clarity.d.ts +10 -15
  64. package/dist/runtime/registry/clarity.js +22 -31
  65. package/dist/runtime/registry/cloudflare-web-analytics.d.ts +2 -13
  66. package/dist/runtime/registry/cloudflare-web-analytics.js +2 -14
  67. package/dist/runtime/registry/crisp.d.ts +9 -39
  68. package/dist/runtime/registry/crisp.js +2 -33
  69. package/dist/runtime/registry/databuddy-analytics.d.ts +2 -35
  70. package/dist/runtime/registry/databuddy-analytics.js +20 -45
  71. package/dist/runtime/registry/fathom-analytics.d.ts +6 -25
  72. package/dist/runtime/registry/fathom-analytics.js +2 -24
  73. package/dist/runtime/registry/google-adsense.d.ts +2 -10
  74. package/dist/runtime/registry/google-adsense.js +2 -11
  75. package/dist/runtime/registry/google-analytics.d.ts +3 -5
  76. package/dist/runtime/registry/google-analytics.js +3 -8
  77. package/dist/runtime/registry/google-maps.d.ts +3 -9
  78. package/dist/runtime/registry/google-maps.js +2 -8
  79. package/dist/runtime/registry/google-recaptcha.d.ts +2 -6
  80. package/dist/runtime/registry/google-recaptcha.js +4 -12
  81. package/dist/runtime/registry/google-sign-in.d.ts +2 -13
  82. package/dist/runtime/registry/google-sign-in.js +2 -22
  83. package/dist/runtime/registry/google-tag-manager.d.ts +3 -28
  84. package/dist/runtime/registry/google-tag-manager.js +4 -27
  85. package/dist/runtime/registry/gravatar.d.ts +25 -0
  86. package/dist/runtime/registry/gravatar.js +32 -0
  87. package/dist/runtime/registry/hotjar.d.ts +3 -5
  88. package/dist/runtime/registry/hotjar.js +2 -5
  89. package/dist/runtime/registry/instagram-embed.d.ts +2 -17
  90. package/dist/runtime/registry/instagram-embed.js +4 -19
  91. package/dist/runtime/registry/intercom.d.ts +3 -11
  92. package/dist/runtime/registry/intercom.js +2 -12
  93. package/dist/runtime/registry/matomo-analytics.d.ts +2 -11
  94. package/dist/runtime/registry/matomo-analytics.js +3 -12
  95. package/dist/runtime/registry/meta-pixel.d.ts +3 -5
  96. package/dist/runtime/registry/meta-pixel.js +2 -4
  97. package/dist/runtime/registry/npm.d.ts +2 -6
  98. package/dist/runtime/registry/npm.js +2 -9
  99. package/dist/runtime/registry/paypal.d.ts +4 -25
  100. package/dist/runtime/registry/paypal.js +3 -66
  101. package/dist/runtime/registry/plausible-analytics.js +18 -13
  102. package/dist/runtime/registry/posthog.d.ts +10 -11
  103. package/dist/runtime/registry/posthog.js +7 -20
  104. package/dist/runtime/registry/reddit-pixel.d.ts +4 -5
  105. package/dist/runtime/registry/reddit-pixel.js +2 -4
  106. package/dist/runtime/registry/rybbit-analytics.d.ts +2 -14
  107. package/dist/runtime/registry/rybbit-analytics.js +7 -19
  108. package/dist/runtime/registry/schemas.d.ts +946 -0
  109. package/dist/runtime/registry/schemas.js +901 -0
  110. package/dist/runtime/registry/segment.d.ts +2 -5
  111. package/dist/runtime/registry/segment.js +2 -5
  112. package/dist/runtime/registry/snapchat-pixel.d.ts +3 -32
  113. package/dist/runtime/registry/snapchat-pixel.js +2 -20
  114. package/dist/runtime/registry/stripe.d.ts +3 -4
  115. package/dist/runtime/registry/stripe.js +2 -4
  116. package/dist/runtime/registry/tiktok-pixel.d.ts +3 -6
  117. package/dist/runtime/registry/tiktok-pixel.js +2 -6
  118. package/dist/runtime/registry/umami-analytics.d.ts +2 -31
  119. package/dist/runtime/registry/umami-analytics.js +2 -36
  120. package/dist/runtime/registry/vercel-analytics.d.ts +29 -0
  121. package/dist/runtime/registry/vercel-analytics.js +84 -0
  122. package/dist/runtime/registry/vimeo-player.d.ts +2 -2
  123. package/dist/runtime/registry/vimeo-player.js +1 -1
  124. package/dist/runtime/registry/x-embed.d.ts +2 -16
  125. package/dist/runtime/registry/x-embed.js +2 -17
  126. package/dist/runtime/registry/x-pixel.d.ts +3 -6
  127. package/dist/runtime/registry/x-pixel.js +2 -5
  128. package/dist/runtime/registry/youtube-player.d.ts +7 -7
  129. package/dist/runtime/registry/youtube-player.js +1 -1
  130. package/dist/runtime/server/google-static-maps-proxy.js +1 -1
  131. package/dist/runtime/server/{sw-handler.d.ts → gravatar-proxy.d.ts} +1 -1
  132. package/dist/runtime/server/gravatar-proxy.js +62 -0
  133. package/dist/runtime/server/instagram-embed-asset.js +2 -1
  134. package/dist/runtime/server/instagram-embed-image.js +2 -1
  135. package/dist/runtime/server/instagram-embed.js +22 -13
  136. package/dist/runtime/server/proxy-handler.js +161 -117
  137. package/dist/runtime/server/utils/privacy.d.ts +45 -1
  138. package/dist/runtime/server/utils/privacy.js +103 -40
  139. package/dist/runtime/server/x-embed.js +3 -2
  140. package/dist/runtime/types.d.ts +30 -24
  141. package/dist/runtime/utils/pure.d.ts +0 -4
  142. package/dist/runtime/utils/pure.js +0 -67
  143. package/dist/runtime/utils.d.ts +3 -3
  144. package/dist/runtime/utils.js +12 -7
  145. package/dist/shared/scripts.Crpn87WB.mjs +318 -0
  146. package/dist/stats.d.mts +39 -0
  147. package/dist/stats.d.ts +39 -0
  148. package/dist/stats.mjs +772 -0
  149. package/dist/types-source.d.mts +19 -0
  150. package/dist/types-source.d.ts +19 -0
  151. package/dist/types-source.mjs +975 -0
  152. package/package.json +42 -31
  153. package/dist/client/_nuxt/B8XOar-X.js +0 -162
  154. package/dist/client/_nuxt/builds/meta/133a46c5-a5c1-4a63-87d1-037947a5bcdb.json +0 -1
  155. package/dist/client/_nuxt/entry.D45OuV0w.css +0 -1
  156. package/dist/client/_nuxt/error-404.B57D-jUQ.css +0 -1
  157. package/dist/client/_nuxt/error-500.DTHUW7BI.css +0 -1
  158. package/dist/runtime/components/ScriptPayPalMarks.d.vue.ts +0 -52
  159. package/dist/runtime/components/ScriptPayPalMarks.vue +0 -69
  160. package/dist/runtime/components/ScriptPayPalMarks.vue.d.ts +0 -52
  161. package/dist/runtime/plugins/sw-register.client.d.ts +0 -2
  162. package/dist/runtime/plugins/sw-register.client.js +0 -12
  163. package/dist/runtime/server/sw-handler.js +0 -25
  164. package/dist/runtime/sw/proxy-sw.template.d.ts +0 -1
  165. package/dist/runtime/sw/proxy-sw.template.js +0 -54
package/dist/module.mjs CHANGED
@@ -1,79 +1,36 @@
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 { useNuxt, addDevServerHandler, extendRouteRules, tryUseNuxt, extendViteConfig, useLogger, addPluginTemplate, addServerHandler, logger as logger$1, addTypeTemplate, defineNuxtModule, createResolver, hasNuxtModule, addImports, addComponentsDir, addTemplate, addBuildPlugin } from '@nuxt/kit';
3
3
  import { defu } from 'defu';
4
+ 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, 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';
21
+ import { g as getAllProxyConfigs, r as routesToInterceptRules, a as getProxyConfig } from './shared/scripts.Crpn87WB.mjs';
22
22
  import { registry } from './registry.mjs';
23
23
 
24
- const DEVTOOLS_UI_ROUTE = "/__nuxt-scripts";
25
- const DEVTOOLS_UI_LOCAL_PORT = 3300;
26
-
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
- );
37
- });
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, "")
47
- };
48
- });
49
- }
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
61
- }
62
- });
63
- }
64
-
65
- const logger = useLogger("@nuxt/scripts");
66
-
67
24
  const renderedScript = /* @__PURE__ */ new Map();
68
25
  const ONE_YEAR_IN_SECONDS = 60 * 60 * 24 * 365;
69
- const bundleStorage = () => {
26
+ function bundleStorage() {
70
27
  const nuxt = tryUseNuxt();
71
28
  return createStorage({
72
29
  driver: fsDriver({
73
30
  base: resolve(nuxt?.options.rootDir || "", "node_modules/.cache/nuxt/scripts")
74
31
  })
75
32
  });
76
- };
33
+ }
77
34
  function setupPublicAssetStrategy(options = {}) {
78
35
  const assetsBaseURL = options.prefix || "/_scripts";
79
36
  const nuxt = useNuxt();
@@ -123,179 +80,291 @@ function setupPublicAssetStrategy(options = {}) {
123
80
  };
124
81
  }
125
82
 
126
- function buildProxyConfig(collectPrefix) {
127
- return {
128
- googleAnalytics: {
129
- rewrite: [
130
- // Modern gtag.js uses www.google.com/g/collect
131
- { from: "www.google.com/g/collect", to: `${collectPrefix}/ga/g/collect` },
132
- // Older gtag.js constructs URLs dynamically: "https://"+(subdomain)+".google-analytics.com/g/collect"
133
- // We need to catch the suffix pattern with leading dot
134
- { from: ".google-analytics.com/g/collect", to: `${collectPrefix}/ga/g/collect` },
135
- { from: ".analytics.google.com/g/collect", to: `${collectPrefix}/ga/g/collect` },
136
- // Full domain patterns for static URLs
137
- { from: "www.google-analytics.com/g/collect", to: `${collectPrefix}/ga/g/collect` },
138
- { from: "analytics.google.com/g/collect", to: `${collectPrefix}/ga/g/collect` },
139
- // Legacy endpoints still used by some scripts
140
- { from: "www.google-analytics.com", to: `${collectPrefix}/ga` },
141
- { from: "analytics.google.com", to: `${collectPrefix}/ga` },
142
- // DoubleClick tracking (used by GA4 for ads/conversions)
143
- { from: "stats.g.doubleclick.net/g/collect", to: `${collectPrefix}/ga/g/collect` },
144
- { from: "stats.g.doubleclick.net", to: `${collectPrefix}/ga-dc` },
145
- // Google Ads/Syndication tracking
146
- { from: "pagead2.googlesyndication.com", to: `${collectPrefix}/ga-syn` },
147
- { from: "www.googleadservices.com", to: `${collectPrefix}/ga-ads` },
148
- { from: "googleads.g.doubleclick.net", to: `${collectPrefix}/ga-gads` }
149
- ],
150
- routes: {
151
- [`${collectPrefix}/ga/**`]: { proxy: "https://www.google-analytics.com/**" },
152
- [`${collectPrefix}/ga-dc/**`]: { proxy: "https://stats.g.doubleclick.net/**" },
153
- [`${collectPrefix}/ga-syn/**`]: { proxy: "https://pagead2.googlesyndication.com/**" },
154
- [`${collectPrefix}/ga-ads/**`]: { proxy: "https://www.googleadservices.com/**" },
155
- [`${collectPrefix}/ga-gads/**`]: { proxy: "https://googleads.g.doubleclick.net/**" }
156
- }
157
- },
158
- googleTagManager: {
159
- rewrite: [
160
- { from: "www.googletagmanager.com", to: `${collectPrefix}/gtm` }
161
- ],
162
- routes: {
163
- [`${collectPrefix}/gtm/**`]: { proxy: "https://www.googletagmanager.com/**" }
164
- }
165
- },
166
- metaPixel: {
167
- rewrite: [
168
- // SDK script loading
169
- { from: "connect.facebook.net", to: `${collectPrefix}/meta` },
170
- // Tracking pixel endpoint (www and non-www)
171
- { from: "www.facebook.com/tr", to: `${collectPrefix}/meta-tr` },
172
- { from: "facebook.com/tr", to: `${collectPrefix}/meta-tr` },
173
- // Additional Meta tracking domains
174
- { from: "pixel.facebook.com", to: `${collectPrefix}/meta-px` },
175
- { from: "www.facebook.com/plugins", to: `${collectPrefix}/meta-plugins` }
176
- ],
177
- routes: {
178
- [`${collectPrefix}/meta/**`]: { proxy: "https://connect.facebook.net/**" },
179
- [`${collectPrefix}/meta-tr/**`]: { proxy: "https://www.facebook.com/tr/**" },
180
- [`${collectPrefix}/meta-px/**`]: { proxy: "https://pixel.facebook.com/**" },
181
- [`${collectPrefix}/meta-plugins/**`]: { proxy: "https://www.facebook.com/plugins/**" }
182
- }
183
- },
184
- tiktokPixel: {
185
- rewrite: [
186
- { from: "analytics.tiktok.com", to: `${collectPrefix}/tiktok` }
187
- ],
188
- routes: {
189
- [`${collectPrefix}/tiktok/**`]: { proxy: "https://analytics.tiktok.com/**" }
190
- }
191
- },
192
- segment: {
193
- rewrite: [
194
- { from: "api.segment.io", to: `${collectPrefix}/segment` },
195
- { from: "cdn.segment.com", to: `${collectPrefix}/segment-cdn` }
196
- ],
197
- routes: {
198
- [`${collectPrefix}/segment/**`]: { proxy: "https://api.segment.io/**" },
199
- [`${collectPrefix}/segment-cdn/**`]: { proxy: "https://cdn.segment.com/**" }
200
- }
201
- },
202
- xPixel: {
203
- rewrite: [
204
- { from: "analytics.twitter.com", to: `${collectPrefix}/x` },
205
- { from: "t.co", to: `${collectPrefix}/x-t` }
206
- ],
207
- routes: {
208
- [`${collectPrefix}/x/**`]: { proxy: "https://analytics.twitter.com/**" },
209
- [`${collectPrefix}/x-t/**`]: { proxy: "https://t.co/**" }
210
- }
211
- },
212
- snapchatPixel: {
213
- rewrite: [
214
- { from: "tr.snapchat.com", to: `${collectPrefix}/snap` }
215
- ],
216
- routes: {
217
- [`${collectPrefix}/snap/**`]: { proxy: "https://tr.snapchat.com/**" }
218
- }
219
- },
220
- redditPixel: {
221
- rewrite: [
222
- { from: "alb.reddit.com", to: `${collectPrefix}/reddit` }
223
- ],
224
- routes: {
225
- [`${collectPrefix}/reddit/**`]: { proxy: "https://alb.reddit.com/**" }
226
- }
227
- },
228
- clarity: {
229
- rewrite: [
230
- // Main clarity domain
231
- { from: "www.clarity.ms", to: `${collectPrefix}/clarity` },
232
- // Script loader (the actual SDK is loaded from here)
233
- { from: "scripts.clarity.ms", to: `${collectPrefix}/clarity-scripts` },
234
- // Data collection endpoint
235
- { from: "d.clarity.ms", to: `${collectPrefix}/clarity-data` },
236
- // Event collection endpoint
237
- { from: "e.clarity.ms", to: `${collectPrefix}/clarity-events` }
238
- ],
239
- routes: {
240
- [`${collectPrefix}/clarity/**`]: { proxy: "https://www.clarity.ms/**" },
241
- [`${collectPrefix}/clarity-scripts/**`]: { proxy: "https://scripts.clarity.ms/**" },
242
- [`${collectPrefix}/clarity-data/**`]: { proxy: "https://d.clarity.ms/**" },
243
- [`${collectPrefix}/clarity-events/**`]: { proxy: "https://e.clarity.ms/**" }
83
+ const DEVTOOLS_UI_ROUTE = "/__nuxt-scripts";
84
+ const DEVTOOLS_UI_LOCAL_PORT = 3300;
85
+
86
+ async function setupDevToolsUI(options, resolve, nuxt = useNuxt()) {
87
+ const clientPath = await resolve("./client");
88
+ const isProductionBuild = existsSync(clientPath);
89
+ if (isProductionBuild) {
90
+ nuxt.hook("vite:serverCreated", async (server) => {
91
+ const sirv = await import('sirv').then((r) => r.default || r);
92
+ server.middlewares.use(
93
+ DEVTOOLS_UI_ROUTE,
94
+ sirv(clientPath, { dev: true, single: true })
95
+ );
96
+ });
97
+ } else {
98
+ extendViteConfig((config) => {
99
+ config.server = config.server || {};
100
+ config.server.proxy = config.server.proxy || {};
101
+ config.server.proxy[DEVTOOLS_UI_ROUTE] = {
102
+ target: `http://localhost:${DEVTOOLS_UI_LOCAL_PORT}${DEVTOOLS_UI_ROUTE}`,
103
+ changeOrigin: true,
104
+ followRedirects: true,
105
+ rewrite: (path) => path.replace(DEVTOOLS_UI_ROUTE, "")
106
+ };
107
+ });
108
+ }
109
+ addCustomTab({
110
+ // unique identifier
111
+ name: "nuxt-scripts",
112
+ // title to display in the tab
113
+ title: "Scripts",
114
+ // any icon from Iconify, or a URL to an image
115
+ icon: "carbon:script",
116
+ // iframe view
117
+ view: {
118
+ type: "iframe",
119
+ src: DEVTOOLS_UI_ROUTE
120
+ }
121
+ });
122
+ }
123
+
124
+ const logger = useLogger("@nuxt/scripts");
125
+
126
+ const AUTO_INJECT_DEFS = [
127
+ {
128
+ registryKey: "posthog",
129
+ configField: "apiHost",
130
+ computeValue: (collectPrefix, config) => {
131
+ const region = config.region || "us";
132
+ return region === "eu" ? `${collectPrefix}/ph-eu` : `${collectPrefix}/ph`;
133
+ }
134
+ },
135
+ {
136
+ registryKey: "plausibleAnalytics",
137
+ configField: "endpoint",
138
+ computeValue: (collectPrefix) => `${collectPrefix}/plausible/api/event`
139
+ },
140
+ {
141
+ registryKey: "umamiAnalytics",
142
+ configField: "hostUrl",
143
+ computeValue: (collectPrefix) => `${collectPrefix}/umami`
144
+ },
145
+ {
146
+ registryKey: "rybbitAnalytics",
147
+ configField: "analyticsHost",
148
+ computeValue: (collectPrefix) => `${collectPrefix}/rybbit/api`
149
+ },
150
+ {
151
+ registryKey: "databuddyAnalytics",
152
+ configField: "apiUrl",
153
+ computeValue: (collectPrefix) => `${collectPrefix}/databuddy-api`
154
+ }
155
+ ];
156
+ function autoInjectProxyEndpoints(registry, runtimeConfig, collectPrefix) {
157
+ for (const def of AUTO_INJECT_DEFS) {
158
+ const entry = registry[def.registryKey];
159
+ if (!entry || typeof entry !== "object")
160
+ continue;
161
+ const config = Array.isArray(entry) ? entry[0] : entry;
162
+ if (!config || config[def.configField])
163
+ continue;
164
+ config[def.configField] = def.computeValue(collectPrefix, config);
165
+ const rtScripts = runtimeConfig.public?.scripts;
166
+ const rtEntry = rtScripts?.[def.registryKey];
167
+ if (rtEntry && typeof rtEntry === "object") {
168
+ const rtConfig = Array.isArray(rtEntry) ? rtEntry[0] : rtEntry;
169
+ if (rtConfig)
170
+ rtConfig[def.configField] = config[def.configField];
171
+ }
172
+ }
173
+ }
174
+
175
+ function generateInterceptPluginContents(interceptRules) {
176
+ const rulesJson = JSON.stringify(interceptRules);
177
+ return `export default defineNuxtPlugin({
178
+ name: 'nuxt-scripts:intercept',
179
+ enforce: 'pre',
180
+ setup() {
181
+ const rules = ${rulesJson};
182
+ const origBeacon = typeof navigator !== 'undefined' && navigator.sendBeacon
183
+ ? navigator.sendBeacon.bind(navigator)
184
+ : () => false;
185
+ const origFetch = globalThis.fetch.bind(globalThis);
186
+
187
+ function rewriteUrl(url) {
188
+ try {
189
+ const parsed = new URL(url, location.origin);
190
+ for (const rule of rules) {
191
+ if (parsed.hostname === rule.pattern || parsed.hostname.endsWith('.' + rule.pattern)) {
192
+ if (rule.pathPrefix && !parsed.pathname.startsWith(rule.pathPrefix)) continue;
193
+ const path = rule.pathPrefix ? parsed.pathname.slice(rule.pathPrefix.length) : parsed.pathname;
194
+ return location.origin + rule.target + (path.startsWith('/') ? '' : '/') + path + parsed.search;
195
+ }
196
+ }
197
+ } catch {}
198
+ return url;
199
+ }
200
+
201
+ // XMLHttpRequest wrapper \u2014 intercepts .open() to rewrite URL
202
+ const OrigXHR = XMLHttpRequest;
203
+ class ProxiedXHR extends OrigXHR {
204
+ open() {
205
+ const args = Array.from(arguments);
206
+ if (typeof args[1] === 'string') args[1] = rewriteUrl(args[1]);
207
+ return super.open.apply(this, args);
244
208
  }
245
- },
246
- hotjar: {
247
- rewrite: [
248
- // Static assets
249
- { from: "static.hotjar.com", to: `${collectPrefix}/hotjar` },
250
- // Script loader (bootstrap script loads the main SDK from here)
251
- { from: "script.hotjar.com", to: `${collectPrefix}/hotjar-script` },
252
- // Configuration/variables
253
- { from: "vars.hotjar.com", to: `${collectPrefix}/hotjar-vars` },
254
- // Data ingestion endpoint
255
- { from: "in.hotjar.com", to: `${collectPrefix}/hotjar-in` },
256
- // Video capture
257
- { from: "vc.hotjar.com", to: `${collectPrefix}/hotjar-vc` },
258
- // Metrics/telemetry
259
- { from: "metrics.hotjar.io", to: `${collectPrefix}/hotjar-metrics` },
260
- // Insights (ContentSquare integration)
261
- { from: "insights.hotjar.com", to: `${collectPrefix}/hotjar-insights` }
262
- ],
263
- routes: {
264
- [`${collectPrefix}/hotjar/**`]: { proxy: "https://static.hotjar.com/**" },
265
- [`${collectPrefix}/hotjar-script/**`]: { proxy: "https://script.hotjar.com/**" },
266
- [`${collectPrefix}/hotjar-vars/**`]: { proxy: "https://vars.hotjar.com/**" },
267
- [`${collectPrefix}/hotjar-in/**`]: { proxy: "https://in.hotjar.com/**" },
268
- [`${collectPrefix}/hotjar-vc/**`]: { proxy: "https://vc.hotjar.com/**" },
269
- [`${collectPrefix}/hotjar-metrics/**`]: { proxy: "https://metrics.hotjar.io/**" },
270
- [`${collectPrefix}/hotjar-insights/**`]: { proxy: "https://insights.hotjar.com/**" }
209
+ }
210
+ // Image wrapper \u2014 intercepts .src setter to rewrite URL
211
+ const OrigImage = Image;
212
+ const origSrcDesc = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'src');
213
+ function ProxiedImage(w, h) {
214
+ const img = arguments.length === 2 ? new OrigImage(w, h)
215
+ : arguments.length === 1 ? new OrigImage(w) : new OrigImage();
216
+ if (origSrcDesc && origSrcDesc.set) {
217
+ Object.defineProperty(img, 'src', {
218
+ get() { return origSrcDesc.get.call(this); },
219
+ set(v) { origSrcDesc.set.call(this, typeof v === 'string' ? rewriteUrl(v) : v); },
220
+ configurable: true,
221
+ });
271
222
  }
223
+ return img;
272
224
  }
273
- };
225
+
226
+ globalThis.__nuxtScripts = {
227
+ sendBeacon: (url, data) => origBeacon(rewriteUrl(url), data),
228
+ fetch: (url, opts) => origFetch(typeof url === 'string' ? rewriteUrl(url) : url, opts),
229
+ XMLHttpRequest: ProxiedXHR,
230
+ Image: ProxiedImage,
231
+ };
232
+ },
233
+ })
234
+ `;
274
235
  }
275
- function getProxyConfig(key, collectPrefix) {
276
- const configs = buildProxyConfig(collectPrefix);
277
- return configs[key];
236
+
237
+ function resolveFirstPartyConfig(config) {
238
+ const enabled = !!config.firstParty;
239
+ const prefix = typeof config.firstParty === "object" ? config.firstParty.prefix : void 0;
240
+ const collectPrefix = typeof config.firstParty === "object" ? config.firstParty.collectPrefix || "/_proxy" : "/_proxy";
241
+ const privacy = typeof config.firstParty === "object" ? config.firstParty.privacy : void 0;
242
+ const assetsPrefix = prefix || config.assets?.prefix || "/_scripts";
243
+ return { enabled, prefix, collectPrefix, privacy, assetsPrefix };
278
244
  }
279
- function getAllProxyConfigs(collectPrefix) {
280
- return buildProxyConfig(collectPrefix);
245
+ async function setupFirstPartyHandlers(firstParty, resolvePath) {
246
+ const interceptRules = [];
247
+ addPluginTemplate({
248
+ filename: "nuxt-scripts-intercept.client.mjs",
249
+ getContents() {
250
+ return generateInterceptPluginContents(interceptRules);
251
+ }
252
+ });
253
+ const proxyHandlerPath = await resolvePath("./runtime/server/proxy-handler");
254
+ logger.debug("[nuxt-scripts] Registering proxy handler:", `${firstParty.collectPrefix}/**`, "->", proxyHandlerPath);
255
+ addServerHandler({
256
+ route: `${firstParty.collectPrefix}/**`,
257
+ handler: proxyHandlerPath
258
+ });
259
+ return interceptRules;
281
260
  }
282
- function getSWInterceptRules(collectPrefix) {
283
- const configs = buildProxyConfig(collectPrefix);
284
- const rules = [];
285
- for (const config of Object.values(configs)) {
286
- if (!config.routes)
287
- continue;
288
- for (const [localPath, { proxy }] of Object.entries(config.routes)) {
289
- const match = proxy.match(/^https?:\/\/([^/]+)(\/.*)?\/\*\*$/);
290
- if (match?.[1]) {
291
- const domain = match[1];
292
- const pathPrefix = match[2] || "";
293
- const target = localPath.replace(/\/\*\*$/, "");
294
- rules.push({ pattern: domain, pathPrefix, target });
261
+ function finalizeFirstParty(opts) {
262
+ const { firstParty, interceptRules, registryScriptsWithImport, nuxtOptions } = opts;
263
+ const proxyConfigs = getAllProxyConfigs(firstParty.collectPrefix);
264
+ const registryKeys = Object.keys(opts.registry || {});
265
+ const neededRoutes = {};
266
+ const routePrivacyOverrides = {};
267
+ const unsupportedScripts = [];
268
+ for (const key of registryKeys) {
269
+ const script = registryScriptsWithImport.find((s) => s.import.name.toLowerCase() === `usescript${key.toLowerCase()}`);
270
+ const proxyKey = script?.proxy || void 0;
271
+ if (proxyKey) {
272
+ const proxyConfig = proxyConfigs[proxyKey];
273
+ if (proxyConfig?.routes) {
274
+ Object.assign(neededRoutes, proxyConfig.routes);
275
+ for (const routePath of Object.keys(proxyConfig.routes)) {
276
+ routePrivacyOverrides[routePath] = proxyConfig.privacy;
277
+ }
278
+ } else {
279
+ unsupportedScripts.push(key);
280
+ }
281
+ }
282
+ }
283
+ if (opts.registry) {
284
+ autoInjectProxyEndpoints(opts.registry, nuxtOptions.runtimeConfig, firstParty.collectPrefix);
285
+ }
286
+ if (unsupportedScripts.length && nuxtOptions.dev) {
287
+ logger.warn(
288
+ `First-party mode is enabled but these scripts don't support it yet: ${unsupportedScripts.join(", ")}.
289
+ They will load directly from third-party servers. Request support at https://github.com/nuxt/scripts/issues`
290
+ );
291
+ }
292
+ interceptRules.push(...routesToInterceptRules(neededRoutes));
293
+ const flatRoutes = {};
294
+ for (const [path, config] of Object.entries(neededRoutes)) {
295
+ flatRoutes[path] = config.proxy;
296
+ }
297
+ nuxtOptions.runtimeConfig["nuxt-scripts-proxy"] = {
298
+ routes: flatRoutes,
299
+ privacy: firstParty.privacy,
300
+ routePrivacy: routePrivacyOverrides
301
+ };
302
+ if (Object.keys(neededRoutes).length && nuxtOptions.dev) {
303
+ const routeCount = Object.keys(neededRoutes).length;
304
+ const scriptsCount = registryKeys.length;
305
+ const privacyLabel = firstParty.privacy === void 0 ? "per-script" : typeof firstParty.privacy === "boolean" ? firstParty.privacy ? "anonymize" : "passthrough" : "custom";
306
+ logger.success(`First-party mode enabled for ${scriptsCount} script(s), ${routeCount} proxy route(s) configured (privacy: ${privacyLabel})`);
307
+ if (logger.level >= 4) {
308
+ for (const [path, config] of Object.entries(neededRoutes)) {
309
+ logger.debug(` ${path} \u2192 ${config.proxy}`);
295
310
  }
296
311
  }
297
312
  }
298
- return rules;
313
+ const staticPresets = ["static", "github-pages", "cloudflare-pages-static"];
314
+ const preset = process.env.NITRO_PRESET || "";
315
+ if (staticPresets.includes(preset)) {
316
+ logger.warn(
317
+ `First-party collection endpoints require a server runtime (detected: ${preset || "static"}).
318
+ Scripts will be bundled, but collection requests will not be proxied.
319
+
320
+ Options:
321
+ 1. Configure platform rewrites (Vercel, Netlify, Cloudflare)
322
+ 2. Switch to server-rendered mode (ssr: true)
323
+ 3. Disable with firstParty: false
324
+
325
+ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
326
+ );
327
+ }
328
+ }
329
+
330
+ const isStackblitz = provider === "stackblitz";
331
+ async function promptToInstall(name, installCommand, options) {
332
+ if (await resolvePackageJSON(name).catch(() => null))
333
+ return true;
334
+ logger$1.info(`Package ${name} is missing`);
335
+ if (isCI)
336
+ return false;
337
+ if (options.prompt === true || options.prompt !== false && !isStackblitz) {
338
+ const confirm = await logger$1.prompt(`Do you want to install ${name} package?`, {
339
+ type: "confirm",
340
+ name: "confirm",
341
+ initial: true
342
+ });
343
+ if (!confirm)
344
+ return false;
345
+ }
346
+ logger$1.info(`Installing ${name}...`);
347
+ try {
348
+ await installCommand();
349
+ logger$1.success(`Installed ${name}`);
350
+ return true;
351
+ } catch (err) {
352
+ logger$1.error(err);
353
+ return false;
354
+ }
355
+ }
356
+ const installPrompts = /* @__PURE__ */ new Set();
357
+ function installNuxtModule(name, options) {
358
+ if (installPrompts.has(name))
359
+ return;
360
+ installPrompts.add(name);
361
+ const nuxt = tryUseNuxt();
362
+ if (!nuxt)
363
+ return;
364
+ return promptToInstall(name, async () => {
365
+ const { runCommand } = await import(String("nuxi"));
366
+ await runCommand("module", ["add", name, "--cwd", nuxt.options.rootDir]);
367
+ }, { rootDir: nuxt.options.rootDir, searchPaths: nuxt.options.modulesDir, ...options });
299
368
  }
300
369
 
301
370
  function isVue(id, opts = {}) {
@@ -319,13 +388,301 @@ function isVue(id, opts = {}) {
319
388
  }
320
389
  return true;
321
390
  }
322
- const JS_RE = /\.(?:[cm]?j|t)sx?$/;
391
+ const JS_RE$1 = /\.(?:[cm]?j|t)sx?$/;
323
392
  function isJS(id) {
324
393
  const { pathname } = parseURL(decodeURIComponent(pathToFileURL(id).href));
325
- return JS_RE.test(pathname);
394
+ return JS_RE$1.test(pathname);
395
+ }
396
+
397
+ const VUE_RE$1 = /\.vue/;
398
+ function NuxtScriptsCheckScripts() {
399
+ return createUnplugin(() => {
400
+ return {
401
+ name: "nuxt-scripts:check-scripts",
402
+ transform: {
403
+ filter: {
404
+ id: VUE_RE$1
405
+ },
406
+ handler(code, id) {
407
+ if (!isVue(id, { type: ["script"] }))
408
+ return;
409
+ if (!code.includes("useScript"))
410
+ return;
411
+ let nameNode;
412
+ let errorNode;
413
+ parseAndWalk(code, id, (_node) => {
414
+ if (_node.type === "VariableDeclaration" && _node.declarations?.[0]?.id?.type === "ObjectPattern") {
415
+ const objPattern = _node.declarations[0]?.id;
416
+ for (const property of objPattern.properties) {
417
+ if (property.type === "Property" && property.key.type === "Identifier" && property.key.name === "$script" && property.value.type === "Identifier") {
418
+ nameNode = _node;
419
+ }
420
+ }
421
+ }
422
+ if (nameNode) {
423
+ let sequence = _node.type === "SequenceExpression" ? _node : null;
424
+ let assignmentExpression;
425
+ if (_node.type === "VariableDeclaration") {
426
+ if (_node.declarations[0]?.init?.type === "SequenceExpression") {
427
+ sequence = _node.declarations[0]?.init;
428
+ assignmentExpression = _node.declarations[0]?.init?.expressions?.[0];
429
+ }
430
+ }
431
+ if (sequence && !assignmentExpression) {
432
+ assignmentExpression = sequence.expressions[0]?.type === "AssignmentExpression" ? sequence.expressions[0] : null;
433
+ }
434
+ if (assignmentExpression) {
435
+ const right = assignmentExpression?.right;
436
+ if (right.callee?.name === "_withAsyncContext") {
437
+ if (right.arguments[0]?.body?.name === "$script" || right.arguments[0]?.body?.callee?.object?.name === "$script") {
438
+ errorNode = nameNode;
439
+ }
440
+ }
441
+ }
442
+ }
443
+ });
444
+ if (errorNode) {
445
+ return this.error(new Error("You can't use a top-level await on $script as it will never resolve."));
446
+ }
447
+ }
448
+ }
449
+ };
450
+ });
451
+ }
452
+
453
+ const WORD_OR_DOLLAR_RE = /[\w$]/;
454
+ const GA_COLLECT_RE = /([\w$])?"https:\/\/"\+\(.*?\)\+"\.google-analytics\.com\/g\/collect"/g;
455
+ const GA_ANALYTICS_COLLECT_RE = /([\w$])?"https:\/\/"\+\(.*?\)\+"\.analytics\.google\.com\/g\/collect"/g;
456
+ const FATHOM_SELF_HOSTED_RE = /\.src\.indexOf\("cdn\.usefathom\.com"\)\s*<\s*0/;
457
+ function isPropertyKeyAST(parent, ctx) {
458
+ return parent?.type === "Property" && ctx.key === "key" || parent?.type === "SwitchCase" && ctx.key === "test";
459
+ }
460
+ function matchAndRewrite(value, rewrites) {
461
+ for (const { from, to } of rewrites) {
462
+ const isSuffixMatch = from.startsWith(".");
463
+ const fromSlashIdx = from.indexOf("/");
464
+ const fromHost = fromSlashIdx > 0 ? from.slice(0, fromSlashIdx) : from;
465
+ const fromPath = fromSlashIdx > 0 ? from.slice(fromSlashIdx) : "";
466
+ if (!value.includes(fromHost))
467
+ continue;
468
+ const url = parseURL(value);
469
+ let shouldRewrite = false;
470
+ let rewriteSuffix = "";
471
+ if (url.host) {
472
+ const hostMatches = isSuffixMatch ? url.host.endsWith(fromHost) : url.host === fromHost;
473
+ if (hostMatches) {
474
+ const fullPath = url.pathname + (url.search || "") + (url.hash || "");
475
+ if (fromPath && fullPath.startsWith(fromPath)) {
476
+ shouldRewrite = true;
477
+ rewriteSuffix = fullPath.slice(fromPath.length);
478
+ } else if (!fromPath) {
479
+ shouldRewrite = true;
480
+ rewriteSuffix = fullPath;
481
+ }
482
+ }
483
+ } else if (value.startsWith("//")) {
484
+ const hostPart = value.slice(2).split("/")[0];
485
+ const hostMatches = isSuffixMatch ? hostPart?.endsWith(fromHost) ?? false : hostPart === fromHost;
486
+ if (hostMatches) {
487
+ const remainder = value.slice(2 + (hostPart?.length ?? 0));
488
+ if (fromPath && remainder.startsWith(fromPath)) {
489
+ shouldRewrite = true;
490
+ rewriteSuffix = remainder.slice(fromPath.length);
491
+ } else if (!fromPath) {
492
+ shouldRewrite = true;
493
+ rewriteSuffix = remainder;
494
+ }
495
+ }
496
+ } else if (fromPath && (value.startsWith(from) || isSuffixMatch && value.includes(from))) {
497
+ const domainEnd = value.indexOf(from) + from.length;
498
+ const nextChar = value[domainEnd];
499
+ if (!nextChar || nextChar === "/" || nextChar === "?" || nextChar === "#") {
500
+ shouldRewrite = true;
501
+ rewriteSuffix = value.slice(domainEnd);
502
+ }
503
+ }
504
+ if (shouldRewrite) {
505
+ return rewriteSuffix === "/" || rewriteSuffix.startsWith("?") || rewriteSuffix.startsWith("#") ? to + rewriteSuffix : joinURL(to, rewriteSuffix);
506
+ }
507
+ }
508
+ return null;
509
+ }
510
+ const WINDOW_GLOBALS = /* @__PURE__ */ new Set(["window", "self", "globalThis"]);
511
+ const NAVIGATOR_GLOBALS = /* @__PURE__ */ new Set(["navigator"]);
512
+ function resolveToGlobal(name, scopeTracker, depth = 0) {
513
+ if (depth > 10)
514
+ return null;
515
+ const decl = scopeTracker.getDeclaration(name);
516
+ if (!decl)
517
+ return name;
518
+ if (decl instanceof ScopeTrackerFunctionParam)
519
+ return null;
520
+ if (decl instanceof ScopeTrackerVariable) {
521
+ const declarators = decl.variableNode.declarations;
522
+ if (!declarators)
523
+ return null;
524
+ for (const declarator of declarators) {
525
+ const id = declarator.id;
526
+ if (!id || id.name !== name)
527
+ continue;
528
+ const init = declarator.init;
529
+ if (!init)
530
+ return null;
531
+ if (init.type === "Identifier")
532
+ return resolveToGlobal(init.name, scopeTracker, depth + 1);
533
+ if (init.type === "MemberExpression" && !init.computed && init.object?.type === "Identifier" && init.property?.type === "Identifier") {
534
+ const objGlobal = resolveToGlobal(init.object.name, scopeTracker, depth + 1);
535
+ if (!objGlobal)
536
+ return null;
537
+ if (WINDOW_GLOBALS.has(objGlobal) || objGlobal === "document")
538
+ return init.property.name;
539
+ return null;
540
+ }
541
+ return null;
542
+ }
543
+ }
544
+ return null;
545
+ }
546
+ function resolveCalleeTarget(callee, scopeTracker) {
547
+ if (callee?.type !== "MemberExpression")
548
+ return null;
549
+ const propName = callee.computed ? callee.property?.type === "Literal" && typeof callee.property.value === "string" ? callee.property.value : null : callee.property?.name;
550
+ if (!propName)
551
+ return null;
552
+ const obj = callee.object;
553
+ if (!obj || obj.type !== "Identifier")
554
+ return null;
555
+ const resolved = resolveToGlobal(obj.name, scopeTracker);
556
+ if (!resolved)
557
+ return null;
558
+ if (propName === "fetch" && WINDOW_GLOBALS.has(resolved))
559
+ return "fetch";
560
+ if (propName === "sendBeacon" && (NAVIGATOR_GLOBALS.has(resolved) || WINDOW_GLOBALS.has(resolved)))
561
+ return "sendBeacon";
562
+ if (propName === "sendBeacon" && resolved === "navigator")
563
+ return "sendBeacon";
564
+ if (propName === "XMLHttpRequest" && WINDOW_GLOBALS.has(resolved))
565
+ return "XMLHttpRequest";
566
+ if (propName === "Image" && WINDOW_GLOBALS.has(resolved))
567
+ return "Image";
568
+ return null;
569
+ }
570
+ function rewriteScriptUrlsAST(content, filename, rewrites) {
571
+ const s = new MagicString(content);
572
+ function needsLeadingSpace(start) {
573
+ const prev = content[start - 1];
574
+ return prev && WORD_OR_DOLLAR_RE.test(prev) ? " " : "";
575
+ }
576
+ const scopeTracker = new ScopeTracker({ preserveExitedScopes: true });
577
+ const { program } = parseAndWalk(content, filename, { scopeTracker });
578
+ scopeTracker.freeze();
579
+ walk(program, {
580
+ scopeTracker,
581
+ enter(node, parent, ctx) {
582
+ if (node.type === "Literal" && typeof node.value === "string") {
583
+ const value = node.value;
584
+ const rewritten = matchAndRewrite(value, rewrites);
585
+ if (rewritten === null)
586
+ return;
587
+ const quote = content[node.start];
588
+ if (isPropertyKeyAST(parent, ctx)) {
589
+ s.overwrite(node.start, node.end, quote + rewritten + quote);
590
+ } else {
591
+ s.overwrite(node.start, node.end, `${needsLeadingSpace(node.start)}self.location.origin+${quote}${rewritten}${quote}`);
592
+ }
593
+ }
594
+ if (node.type === "TemplateLiteral" && node.expressions?.length === 0) {
595
+ const quasis = node.quasis;
596
+ if (quasis?.length === 1) {
597
+ const value = quasis[0].value?.cooked ?? quasis[0].value?.raw;
598
+ if (typeof value !== "string")
599
+ return;
600
+ const rewritten = matchAndRewrite(value, rewrites);
601
+ if (rewritten === null)
602
+ return;
603
+ if (isPropertyKeyAST(parent, ctx)) {
604
+ s.overwrite(node.start, node.end, `\`${rewritten}\``);
605
+ } else {
606
+ s.overwrite(node.start, node.end, `${needsLeadingSpace(node.start)}self.location.origin+\`${rewritten}\``);
607
+ }
608
+ }
609
+ }
610
+ if (node.type === "CallExpression") {
611
+ const callee = node.callee;
612
+ if (callee?.type === "Identifier" && callee.name === "fetch") {
613
+ if (!scopeTracker.getDeclaration("fetch"))
614
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.fetch");
615
+ return;
616
+ }
617
+ const target = resolveCalleeTarget(callee, scopeTracker);
618
+ if (target === "fetch" || target === "sendBeacon") {
619
+ s.overwrite(callee.start, callee.end, `__nuxtScripts.${target}`);
620
+ return;
621
+ }
622
+ if (callee?.type === "MemberExpression" && !callee.computed && callee.property?.name === "sendBeacon" && callee.object?.type === "Identifier") {
623
+ const resolved = resolveToGlobal(callee.object.name, scopeTracker);
624
+ if (resolved === null) {
625
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.sendBeacon");
626
+ }
627
+ }
628
+ }
629
+ if (node.type === "NewExpression") {
630
+ const callee = node.callee;
631
+ if (callee?.type === "Identifier" && callee.name === "XMLHttpRequest") {
632
+ if (!scopeTracker.getDeclaration("XMLHttpRequest"))
633
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.XMLHttpRequest");
634
+ return;
635
+ }
636
+ if (callee?.type === "Identifier" && callee.name === "Image") {
637
+ if (!scopeTracker.getDeclaration("Image"))
638
+ s.overwrite(callee.start, callee.end, "__nuxtScripts.Image");
639
+ return;
640
+ }
641
+ const target = resolveCalleeTarget(callee, scopeTracker);
642
+ if (target === "XMLHttpRequest" || target === "Image") {
643
+ s.overwrite(callee.start, callee.end, `__nuxtScripts.${target}`);
644
+ return;
645
+ }
646
+ if (callee?.type === "MemberExpression" && callee.object?.type === "Identifier") {
647
+ const propName = callee.computed ? callee.property?.type === "Literal" && typeof callee.property.value === "string" ? callee.property.value : null : callee.property?.name;
648
+ if (propName === "XMLHttpRequest" || propName === "Image") {
649
+ const resolved = resolveToGlobal(callee.object.name, scopeTracker);
650
+ if (resolved === null) {
651
+ s.overwrite(callee.start, callee.end, `__nuxtScripts.${propName}`);
652
+ }
653
+ }
654
+ }
655
+ }
656
+ }
657
+ });
658
+ let output = s.toString();
659
+ const gaRewrite = rewrites.find((r) => r.from.includes("google-analytics.com/g/collect"));
660
+ if (gaRewrite) {
661
+ output = output.replace(
662
+ GA_COLLECT_RE,
663
+ (_, prevChar) => `${prevChar ? `${prevChar} ` : ""}self.location.origin+"${gaRewrite.to}"`
664
+ );
665
+ output = output.replace(
666
+ GA_ANALYTICS_COLLECT_RE,
667
+ (_, prevChar) => `${prevChar ? `${prevChar} ` : ""}self.location.origin+"${gaRewrite.to}"`
668
+ );
669
+ }
670
+ if (rewrites.some((r) => r.from === "cdn.usefathom.com")) {
671
+ output = output.replace(
672
+ FATHOM_SELF_HOSTED_RE,
673
+ '.src.indexOf("cdn.usefathom.com")<-1'
674
+ );
675
+ }
676
+ return output;
326
677
  }
327
678
 
328
679
  const SEVEN_DAYS_IN_MS = 7 * 24 * 60 * 60 * 1e3;
680
+ const PROTOCOL_RELATIVE_RE = /^\/\//;
681
+ const VUE_RE = /\.vue/;
682
+ const JS_RE = /\.[cm]?[jt]sx?$/;
683
+ const TEST_RE = /\.(?:test|spec)\./;
684
+ const UPPERCASE_RE = /^[A-Z]$/;
685
+ const USE_SCRIPT_RE = /^useScript/;
329
686
  function calculateIntegrity(content, algorithm = "sha384") {
330
687
  const hash = createHash(algorithm).update(content).digest("base64");
331
688
  return `${algorithm}-${hash}`;
@@ -340,12 +697,10 @@ async function isCacheExpired(storage, filename, cacheMaxAge = SEVEN_DAYS_IN_MS)
340
697
  }
341
698
  function normalizeScriptData(src, assetsBaseURL = "/_scripts") {
342
699
  if (hasProtocol(src, { acceptRelative: true })) {
343
- src = src.replace(/^\/\//, "https://");
700
+ src = src.replace(PROTOCOL_RELATIVE_RE, "https://");
344
701
  const url = parseURL(src);
345
- const file = [
346
- `${hash(url)}.js`
347
- // force an extension
348
- ].filter(Boolean).join("-");
702
+ const h = hash(url);
703
+ const file = `${h.startsWith("-") ? `_${h.slice(1)}` : h}.js`;
349
704
  const nuxt = tryUseNuxt();
350
705
  const cdnURL = nuxt?.options.runtimeConfig?.app?.cdnURL || nuxt?.options.app?.cdnURL || "";
351
706
  const baseURL = cdnURL || nuxt?.options.app.baseURL || "";
@@ -392,7 +747,7 @@ async function downloadScript(opts, renderedScript, fetchOptions, cacheMaxAge) {
392
747
  await storage.setItemRaw(`bundle:${filename}`, res);
393
748
  if (proxyRewrites?.length && res) {
394
749
  const content = res.toString("utf-8");
395
- const rewritten = rewriteScriptUrls(content, proxyRewrites);
750
+ const rewritten = rewriteScriptUrlsAST(content, filename || "script.js", proxyRewrites);
396
751
  res = Buffer.from(rewritten, "utf-8");
397
752
  logger.debug(`Rewrote ${proxyRewrites.length} URL patterns in ${filename}`);
398
753
  }
@@ -449,8 +804,8 @@ function NuxtScriptBundleTransformer(options = {
449
804
  transform: {
450
805
  filter: {
451
806
  id: {
452
- include: [/\.vue/, /\.[cm]?[jt]sx?$/],
453
- exclude: [/\.(?:test|spec)\./]
807
+ include: [VUE_RE, JS_RE],
808
+ exclude: [TEST_RE]
454
809
  }
455
810
  },
456
811
  async handler(code, id) {
@@ -460,11 +815,11 @@ function NuxtScriptBundleTransformer(options = {
460
815
  return;
461
816
  const s = new MagicString(code);
462
817
  const deferredOps = [];
463
- parseAndWalk(code, id, function(_node) {
818
+ parseAndWalk(code, id, (_node) => {
464
819
  const calleeName = _node.callee?.name;
465
820
  if (!calleeName)
466
821
  return;
467
- const isValidCallee = calleeName === "useScript" || calleeName?.startsWith("useScript") && /^[A-Z]$/.test(calleeName?.charAt(9)) && !calleeName.startsWith("useScriptTrigger") && !calleeName.startsWith("useScriptEvent");
822
+ const isValidCallee = calleeName === "useScript" || calleeName?.startsWith("useScript") && UPPERCASE_RE.test(calleeName?.charAt(9)) && !calleeName.startsWith("useScriptTrigger") && !calleeName.startsWith("useScriptEvent");
468
823
  if (_node.type === "CallExpression" && _node.callee.type === "Identifier" && isValidCallee) {
469
824
  const fnName = _node.callee?.name;
470
825
  const node = _node;
@@ -472,7 +827,7 @@ function NuxtScriptBundleTransformer(options = {
472
827
  let src;
473
828
  let registryKey;
474
829
  if (fnName !== "useScript") {
475
- const baseName = fnName.replace(/^useScript/, "");
830
+ const baseName = fnName.replace(USE_SCRIPT_RE, "");
476
831
  registryKey = baseName.length > 0 ? baseName.charAt(0).toLowerCase() + baseName.slice(1) : void 0;
477
832
  }
478
833
  if (fnName === "useScript") {
@@ -652,7 +1007,7 @@ function NuxtScriptBundleTransformer(options = {
652
1007
  s.appendRight(node.arguments[0].start + 1, ` scriptInput: { src: '${url}'${integrityProps} }, `);
653
1008
  }
654
1009
  } else {
655
- s.appendRight(node.callee.end, `({ scriptInput: { src: '${url}'${integrityProps} } })`);
1010
+ s.overwrite(node.callee.end, node.end, `({ scriptInput: { src: '${url}'${integrityProps} } })`);
656
1011
  }
657
1012
  }
658
1013
  });
@@ -676,101 +1031,7 @@ function NuxtScriptBundleTransformer(options = {
676
1031
  });
677
1032
  }
678
1033
 
679
- const isStackblitz = provider === "stackblitz";
680
- async function promptToInstall(name, installCommand, options) {
681
- if (await resolvePackageJSON(name).catch(() => null))
682
- return true;
683
- logger$1.info(`Package ${name} is missing`);
684
- if (isCI)
685
- return false;
686
- if (options.prompt === true || options.prompt !== false && !isStackblitz) {
687
- const confirm = await logger$1.prompt(`Do you want to install ${name} package?`, {
688
- type: "confirm",
689
- name: "confirm",
690
- initial: true
691
- });
692
- if (!confirm)
693
- return false;
694
- }
695
- logger$1.info(`Installing ${name}...`);
696
- try {
697
- await installCommand();
698
- logger$1.success(`Installed ${name}`);
699
- return true;
700
- } catch (err) {
701
- logger$1.error(err);
702
- return false;
703
- }
704
- }
705
- const installPrompts = /* @__PURE__ */ new Set();
706
- function installNuxtModule(name, options) {
707
- if (installPrompts.has(name))
708
- return;
709
- installPrompts.add(name);
710
- const nuxt = tryUseNuxt();
711
- if (!nuxt)
712
- return;
713
- return promptToInstall(name, async () => {
714
- const { runCommand } = await import(String("nuxi"));
715
- await runCommand("module", ["add", name, "--cwd", nuxt.options.rootDir]);
716
- }, { rootDir: nuxt.options.rootDir, searchPaths: nuxt.options.modulesDir, ...options });
717
- }
718
-
719
- function NuxtScriptsCheckScripts() {
720
- return createUnplugin(() => {
721
- return {
722
- name: "nuxt-scripts:check-scripts",
723
- transform: {
724
- filter: {
725
- id: /\.vue/
726
- },
727
- handler(code, id) {
728
- if (!isVue(id, { type: ["script"] }))
729
- return;
730
- if (!code.includes("useScript"))
731
- return;
732
- let nameNode;
733
- let errorNode;
734
- parseAndWalk(code, id, function(_node) {
735
- if (_node.type === "VariableDeclaration" && _node.declarations?.[0]?.id?.type === "ObjectPattern") {
736
- const objPattern = _node.declarations[0]?.id;
737
- for (const property of objPattern.properties) {
738
- if (property.type === "Property" && property.key.type === "Identifier" && property.key.name === "$script" && property.value.type === "Identifier") {
739
- nameNode = _node;
740
- }
741
- }
742
- }
743
- if (nameNode) {
744
- let sequence = _node.type === "SequenceExpression" ? _node : null;
745
- let assignmentExpression;
746
- if (_node.type === "VariableDeclaration") {
747
- if (_node.declarations[0]?.init?.type === "SequenceExpression") {
748
- sequence = _node.declarations[0]?.init;
749
- assignmentExpression = _node.declarations[0]?.init?.expressions?.[0];
750
- }
751
- }
752
- if (sequence && !assignmentExpression) {
753
- assignmentExpression = sequence.expressions[0]?.type === "AssignmentExpression" ? sequence.expressions[0] : null;
754
- }
755
- if (assignmentExpression) {
756
- const right = assignmentExpression?.right;
757
- if (right.callee?.name === "_withAsyncContext") {
758
- if (right.arguments[0]?.body?.name === "$script" || right.arguments[0]?.body?.callee?.object?.name === "$script") {
759
- errorNode = nameNode;
760
- }
761
- }
762
- }
763
- }
764
- });
765
- if (errorNode) {
766
- return this.error(new Error("You can't use a top-level await on $script as it will never resolve."));
767
- }
768
- }
769
- }
770
- };
771
- });
772
- }
773
-
1034
+ const TRIGGER_PLACEHOLDER_RE = /"__TRIGGER_PLACEHOLDER__"/g;
774
1035
  function registerTypeTemplates({ nuxt, config, newScripts }) {
775
1036
  addTypeTemplate({
776
1037
  filename: "types/nuxt-scripts-augments.d.ts",
@@ -782,7 +1043,7 @@ function registerTypeTemplates({ nuxt, config, newScripts }) {
782
1043
  let augments = `// Generated by @nuxt/scripts
783
1044
  declare module '#app' {
784
1045
  interface NuxtApp {
785
- $scripts: Record<${[...Object.keys(config.globals || {}), ...Object.keys(config.registry || {})].map((k) => `'${k}'`).concat(["string"]).join(" | ")}, import('#nuxt-scripts/types').UseScriptContext<any> | undefined>
1046
+ $scripts: Record<${[...[...Object.keys(config.globals || {}), ...Object.keys(config.registry || {})].map((k) => `'${k}'`), ...["string"]].join(" | ")}, import('#nuxt-scripts/types').UseScriptContext<any> | undefined>
786
1047
  _scripts: Record<string, import('#nuxt-scripts/types').NuxtDevToolsScriptInstance>
787
1048
  }
788
1049
  interface RuntimeNuxtHooks {
@@ -863,7 +1124,7 @@ function templatePlugin(config, registry) {
863
1124
  let needsInteractionImport = false;
864
1125
  let needsServiceWorkerImport = false;
865
1126
  for (const [k, c] of Object.entries(config.registry || {})) {
866
- const importDefinition = registry.find((i) => i.proxy === k || i.import.name === `useScript${k.substring(0, 1).toUpperCase() + k.substring(1)}`);
1127
+ const importDefinition = registry.find((i) => i.import.name.toLowerCase() === `usescript${k.toLowerCase()}`);
867
1128
  if (importDefinition) {
868
1129
  resolvedRegistryKeys.push(k);
869
1130
  imports.unshift(`import { ${importDefinition.import.name} } from '${importDefinition.import.from}'`);
@@ -875,12 +1136,15 @@ function templatePlugin(config, registry) {
875
1136
  const triggerResolved = resolveTriggerForTemplate(scriptOptions?.trigger);
876
1137
  if (triggerResolved) {
877
1138
  scriptOptions.trigger = "__TRIGGER_PLACEHOLDER__";
878
- if (triggerResolved.includes("useScriptTriggerIdleTimeout")) needsIdleTimeoutImport = true;
879
- if (triggerResolved.includes("useScriptTriggerInteraction")) needsInteractionImport = true;
880
- if (triggerResolved.includes("useScriptTriggerServiceWorker")) needsServiceWorkerImport = true;
1139
+ if (triggerResolved.includes("useScriptTriggerIdleTimeout"))
1140
+ needsIdleTimeoutImport = true;
1141
+ if (triggerResolved.includes("useScriptTriggerInteraction"))
1142
+ needsInteractionImport = true;
1143
+ if (triggerResolved.includes("useScriptTriggerServiceWorker"))
1144
+ needsServiceWorkerImport = true;
881
1145
  }
882
1146
  const args = { ...input, scriptOptions };
883
- const argsJson = triggerResolved ? JSON.stringify(args).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved) : JSON.stringify(args);
1147
+ const argsJson = triggerResolved ? JSON.stringify(args).replace(TRIGGER_PLACEHOLDER_RE, triggerResolved) : JSON.stringify(args);
884
1148
  inits.push(`const ${k} = ${importDefinition.import.name}(${argsJson})`);
885
1149
  } else {
886
1150
  const args = (typeof c !== "object" ? {} : c) || {};
@@ -895,11 +1159,14 @@ function templatePlugin(config, registry) {
895
1159
  const options = c[1];
896
1160
  const triggerResolved = resolveTriggerForTemplate(options?.trigger);
897
1161
  if (triggerResolved) {
898
- if (triggerResolved.includes("useScriptTriggerIdleTimeout")) needsIdleTimeoutImport = true;
899
- if (triggerResolved.includes("useScriptTriggerInteraction")) needsInteractionImport = true;
900
- if (triggerResolved.includes("useScriptTriggerServiceWorker")) needsServiceWorkerImport = true;
1162
+ if (triggerResolved.includes("useScriptTriggerIdleTimeout"))
1163
+ needsIdleTimeoutImport = true;
1164
+ if (triggerResolved.includes("useScriptTriggerInteraction"))
1165
+ needsInteractionImport = true;
1166
+ if (triggerResolved.includes("useScriptTriggerServiceWorker"))
1167
+ needsServiceWorkerImport = true;
901
1168
  const resolvedOptions = { ...options, trigger: "__TRIGGER_PLACEHOLDER__" };
902
- const optionsJson = JSON.stringify(resolvedOptions).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved);
1169
+ const optionsJson = JSON.stringify(resolvedOptions).replace(TRIGGER_PLACEHOLDER_RE, triggerResolved);
903
1170
  inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...typeof c[0] === "string" ? { src: c[0] } : c[0] })}, { ...${optionsJson}, use: () => ({ ${k}: window.${k} }) })`);
904
1171
  } else {
905
1172
  inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...typeof c[0] === "string" ? { src: c[0] } : c[0] })}, { ...${JSON.stringify(c[1])}, use: () => ({ ${k}: window.${k} }) })`);
@@ -907,11 +1174,14 @@ function templatePlugin(config, registry) {
907
1174
  } else if (typeof c === "object" && c !== null) {
908
1175
  const triggerResolved = resolveTriggerForTemplate(c.trigger);
909
1176
  if (triggerResolved) {
910
- if (triggerResolved.includes("useScriptTriggerIdleTimeout")) needsIdleTimeoutImport = true;
911
- if (triggerResolved.includes("useScriptTriggerInteraction")) needsInteractionImport = true;
912
- if (triggerResolved.includes("useScriptTriggerServiceWorker")) needsServiceWorkerImport = true;
1177
+ if (triggerResolved.includes("useScriptTriggerIdleTimeout"))
1178
+ needsIdleTimeoutImport = true;
1179
+ if (triggerResolved.includes("useScriptTriggerInteraction"))
1180
+ needsInteractionImport = true;
1181
+ if (triggerResolved.includes("useScriptTriggerServiceWorker"))
1182
+ needsServiceWorkerImport = true;
913
1183
  const resolvedOptions = { ...c, trigger: "__TRIGGER_PLACEHOLDER__" };
914
- const argsJson = JSON.stringify({ key: k, ...resolvedOptions }).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved);
1184
+ const argsJson = JSON.stringify({ key: k, ...resolvedOptions }).replace(TRIGGER_PLACEHOLDER_RE, triggerResolved);
915
1185
  inits.push(`const ${k} = useScript(${argsJson}, { use: () => ({ ${k}: window.${k} }) })`);
916
1186
  } else {
917
1187
  inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...c })}, { use: () => ({ ${k}: window.${k} }) })`);
@@ -940,12 +1210,93 @@ function templatePlugin(config, registry) {
940
1210
  ` parallel: true,`,
941
1211
  ` setup() {`,
942
1212
  ...inits.map((i) => ` ${i}`),
943
- ` return { provide: { $scripts: { ${[...Object.keys(config.globals || {}), ...resolvedRegistryKeys].join(", ")} } } }`,
1213
+ ` return { provide: { scripts: { ${[...Object.keys(config.globals || {}), ...resolvedRegistryKeys].join(", ")} } } }`,
944
1214
  ` }`,
945
1215
  `})`
946
1216
  ].join("\n");
947
1217
  }
948
1218
 
1219
+ const SELF_CLOSING_SCRIPT_RE = /<((?:Script[A-Z]|script-)\w[\w-]*)\b([^>]*?)\/\s*>/g;
1220
+ function fixSelfClosingScriptComponents(nuxt) {
1221
+ function expandTags(content) {
1222
+ SELF_CLOSING_SCRIPT_RE.lastIndex = 0;
1223
+ if (!SELF_CLOSING_SCRIPT_RE.test(content))
1224
+ return null;
1225
+ SELF_CLOSING_SCRIPT_RE.lastIndex = 0;
1226
+ return content.replace(SELF_CLOSING_SCRIPT_RE, (_, tag, attrs) => `<${tag}${attrs.trimEnd()}></${tag}>`);
1227
+ }
1228
+ function fixFile(filePath) {
1229
+ if (!existsSync(filePath))
1230
+ return;
1231
+ const content = readFileSync(filePath, "utf-8");
1232
+ const fixed = expandTags(content);
1233
+ if (fixed)
1234
+ nuxt.vfs[filePath] = fixed;
1235
+ }
1236
+ function scanDir(dir) {
1237
+ if (!existsSync(dir))
1238
+ return;
1239
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1240
+ const fullPath = resolve(dir, entry.name);
1241
+ if (entry.isDirectory())
1242
+ scanDir(fullPath);
1243
+ else if (entry.name.endsWith(".vue"))
1244
+ fixFile(fullPath);
1245
+ }
1246
+ }
1247
+ const pagesDirs = /* @__PURE__ */ new Set();
1248
+ for (const layer of nuxt.options._layers) {
1249
+ pagesDirs.add(resolve(
1250
+ layer.config.srcDir,
1251
+ layer.config.dir?.pages || "pages"
1252
+ ));
1253
+ }
1254
+ for (const dir of pagesDirs) scanDir(dir);
1255
+ if (nuxt.options.dev) {
1256
+ nuxt.hook("builder:watch", (_event, relativePath) => {
1257
+ if (!relativePath.endsWith(".vue"))
1258
+ return;
1259
+ for (const layer of nuxt.options._layers) {
1260
+ const fullPath = resolve(layer.config.srcDir, relativePath);
1261
+ for (const dir of pagesDirs) {
1262
+ if (fullPath.startsWith(`${dir}/`)) {
1263
+ fixFile(fullPath);
1264
+ return;
1265
+ }
1266
+ }
1267
+ }
1268
+ });
1269
+ }
1270
+ }
1271
+ const REGISTRY_ENV_DEFAULTS = {
1272
+ clarity: { id: "" },
1273
+ cloudflareWebAnalytics: { token: "" },
1274
+ crisp: { id: "" },
1275
+ databuddyAnalytics: { clientId: "" },
1276
+ fathomAnalytics: { site: "" },
1277
+ googleAdsense: { client: "" },
1278
+ googleAnalytics: { id: "" },
1279
+ googleMaps: { apiKey: "" },
1280
+ googleRecaptcha: { siteKey: "" },
1281
+ googleSignIn: { clientId: "" },
1282
+ googleTagManager: { id: "" },
1283
+ hotjar: { id: "" },
1284
+ intercom: { app_id: "" },
1285
+ matomoAnalytics: { matomoUrl: "" },
1286
+ metaPixel: { id: "" },
1287
+ paypal: { clientId: "" },
1288
+ plausibleAnalytics: { domain: "" },
1289
+ posthog: { apiKey: "" },
1290
+ redditPixel: { id: "" },
1291
+ rybbitAnalytics: { siteId: "" },
1292
+ segment: { writeKey: "" },
1293
+ snapchatPixel: { id: "" },
1294
+ stripe: {},
1295
+ tiktokPixel: { id: "" },
1296
+ umamiAnalytics: { websiteId: "" },
1297
+ vercelAnalytics: {},
1298
+ xPixel: { id: "" }
1299
+ };
949
1300
  const PARTYTOWN_FORWARDS = {
950
1301
  googleAnalytics: ["dataLayer.push", "gtag"],
951
1302
  plausible: ["plausible"],
@@ -1009,8 +1360,7 @@ const module$1 = defineNuxtModule({
1009
1360
  nuxt.options.runtimeConfig["nuxt-scripts"] = {
1010
1361
  version,
1011
1362
  // Private proxy config with API key (server-side only)
1012
- googleStaticMapsProxy: config.googleStaticMapsProxy?.enabled ? { apiKey: nuxt.options.runtimeConfig.public.scripts?.googleMaps?.apiKey } : void 0,
1013
- swTemplate: readFileSync(await resolvePath("./runtime/sw/proxy-sw.template.js"), "utf-8")
1363
+ googleStaticMapsProxy: config.googleStaticMapsProxy?.enabled ? { apiKey: nuxt.options.runtimeConfig.public.scripts?.googleMaps?.apiKey } : void 0
1014
1364
  };
1015
1365
  nuxt.options.runtimeConfig.public["nuxt-scripts"] = {
1016
1366
  // expose for devtools
@@ -1021,9 +1371,26 @@ const module$1 = defineNuxtModule({
1021
1371
  };
1022
1372
  if (config.registry) {
1023
1373
  nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
1374
+ const registryWithDefaults = {};
1375
+ for (const [key, value] of Object.entries(config.registry)) {
1376
+ if (value && REGISTRY_ENV_DEFAULTS[key]) {
1377
+ const envDefaults = REGISTRY_ENV_DEFAULTS[key];
1378
+ if (value === true || value === "mock") {
1379
+ registryWithDefaults[key] = { ...envDefaults };
1380
+ } else if (typeof value === "object" && !Array.isArray(value)) {
1381
+ registryWithDefaults[key] = defu(value, envDefaults);
1382
+ } else if (Array.isArray(value)) {
1383
+ registryWithDefaults[key] = defu(value[0] || {}, envDefaults);
1384
+ } else {
1385
+ registryWithDefaults[key] = value;
1386
+ }
1387
+ } else {
1388
+ registryWithDefaults[key] = value;
1389
+ }
1390
+ }
1024
1391
  nuxt.options.runtimeConfig.public.scripts = defu(
1025
1392
  nuxt.options.runtimeConfig.public.scripts || {},
1026
- config.registry
1393
+ registryWithDefaults
1027
1394
  );
1028
1395
  }
1029
1396
  if (config.defaultScriptOptions?.bundle !== void 0) {
@@ -1031,14 +1398,8 @@ const module$1 = defineNuxtModule({
1031
1398
  "`scripts.defaultScriptOptions.bundle` is deprecated. Use `scripts.firstParty: true` instead. First-party mode is now enabled by default."
1032
1399
  );
1033
1400
  }
1034
- const staticPresets = ["static", "github-pages", "cloudflare-pages-static"];
1035
- const preset = process.env.NITRO_PRESET || "";
1036
- const isStaticPreset = staticPresets.includes(preset);
1037
- const firstPartyEnabled = !!config.firstParty;
1038
- const firstPartyPrefix = typeof config.firstParty === "object" ? config.firstParty.prefix : void 0;
1039
- const firstPartyCollectPrefix = typeof config.firstParty === "object" ? config.firstParty.collectPrefix || "/_proxy" : "/_proxy";
1040
- const firstPartyPrivacy = typeof config.firstParty === "object" ? config.firstParty.privacy ?? "anonymize" : "anonymize";
1041
- const assetsPrefix = firstPartyPrefix || config.assets?.prefix || "/_scripts";
1401
+ const firstParty = resolveFirstPartyConfig(config);
1402
+ const assetsPrefix = firstParty.assetsPrefix;
1042
1403
  if (config.partytown?.length) {
1043
1404
  config.registry = config.registry || {};
1044
1405
  const requiredForwards = [];
@@ -1090,109 +1451,17 @@ const module$1 = defineNuxtModule({
1090
1451
  path: await resolvePath("./runtime/components"),
1091
1452
  pathPrefix: false
1092
1453
  });
1454
+ fixSelfClosingScriptComponents(nuxt);
1093
1455
  addTemplate({
1094
1456
  filename: "nuxt-scripts-trigger-resolver.mjs",
1095
1457
  getContents() {
1096
1458
  return templateTriggerResolver(config.defaultScriptOptions);
1097
1459
  }
1098
1460
  });
1099
- const swHandlerPath = await resolvePath("./runtime/server/sw-handler");
1100
- logger.debug("[nuxt-scripts] First-party config:", { firstPartyEnabled, firstPartyPrivacy, firstPartyCollectPrefix });
1101
- if (firstPartyEnabled && !nuxt.options.dev) {
1102
- const swPath = "/_nuxt-scripts-sw.js";
1103
- const swRules = getSWInterceptRules(firstPartyCollectPrefix);
1104
- addServerHandler({
1105
- route: swPath,
1106
- handler: swHandlerPath
1107
- });
1108
- addPluginTemplate({
1109
- filename: "nuxt-scripts-sw-register.client.mjs",
1110
- getContents() {
1111
- return `import { defineNuxtPlugin } from 'nuxt/app'
1112
-
1113
- export default defineNuxtPlugin({
1114
- name: 'nuxt-scripts:sw-register',
1115
- enforce: 'pre',
1116
- async setup() {
1117
- if (!('serviceWorker' in navigator)) return;
1118
-
1119
- try {
1120
- const reg = await navigator.serviceWorker.register('${swPath}', { scope: '/' });
1121
-
1122
- // Wait for SW to be active and controlling this page
1123
- if (!navigator.serviceWorker.controller) {
1124
- await new Promise((resolve) => {
1125
- const onControllerChange = () => {
1126
- navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange);
1127
- resolve();
1128
- };
1129
- navigator.serviceWorker.addEventListener('controllerchange', onControllerChange);
1130
-
1131
- // Fallback timeout
1132
- setTimeout(resolve, 2000);
1133
- });
1134
- }
1135
- } catch (err) {
1136
- console.warn('[nuxt-scripts] SW registration failed:', err);
1137
- }
1138
- },
1139
- })
1140
- `;
1141
- }
1142
- });
1143
- addPluginTemplate({
1144
- filename: "nuxt-scripts-beacon-intercept.client.mjs",
1145
- getContents() {
1146
- const rulesJson = JSON.stringify(swRules);
1147
- return `export default defineNuxtPlugin({
1148
- name: 'nuxt-scripts:beacon-intercept',
1149
- enforce: 'pre',
1150
- setup() {
1151
- if (typeof navigator === 'undefined' || !navigator.sendBeacon) return;
1152
-
1153
- const rules = ${rulesJson};
1154
- const originalBeacon = navigator.sendBeacon.bind(navigator);
1155
-
1156
- navigator.sendBeacon = (url, data) => {
1157
- try {
1158
- const parsed = new URL(url, window.location.origin);
1159
-
1160
- // Check if this URL matches any of our proxy rules
1161
- for (const rule of rules) {
1162
- if (parsed.hostname === rule.pattern || parsed.hostname.endsWith('.' + rule.pattern)) {
1163
- // Check path prefix if specified
1164
- if (rule.pathPrefix && !parsed.pathname.startsWith(rule.pathPrefix)) {
1165
- continue;
1166
- }
1167
-
1168
- // Rewrite to proxy: strip pathPrefix from original, prepend target
1169
- const pathWithoutPrefix = rule.pathPrefix
1170
- ? parsed.pathname.slice(rule.pathPrefix.length)
1171
- : parsed.pathname;
1172
- const separator = pathWithoutPrefix.startsWith('/') ? '' : '/';
1173
- const proxyUrl = rule.target + separator + pathWithoutPrefix + parsed.search;
1174
-
1175
- return originalBeacon(proxyUrl, data);
1176
- }
1177
- }
1178
- } catch (e) {
1179
- // URL parsing failed, pass through
1180
- }
1181
-
1182
- return originalBeacon(url, data);
1183
- };
1184
- },
1185
- })
1186
- `;
1187
- }
1188
- });
1189
- nuxt.options.runtimeConfig.public["nuxt-scripts-sw"] = { path: swPath };
1190
- const proxyHandlerPath = await resolvePath("./runtime/server/proxy-handler");
1191
- logger.debug("[nuxt-scripts] Registering proxy handler:", `${firstPartyCollectPrefix}/**`, "->", proxyHandlerPath);
1192
- addServerHandler({
1193
- route: `${firstPartyCollectPrefix}/**`,
1194
- handler: proxyHandlerPath
1195
- });
1461
+ logger.debug("[nuxt-scripts] First-party config:", firstParty);
1462
+ let interceptRules = [];
1463
+ if (firstParty.enabled) {
1464
+ interceptRules = await setupFirstPartyHandlers(firstParty, resolvePath);
1196
1465
  }
1197
1466
  const scripts = await registry(resolvePath);
1198
1467
  for (const script of scripts) {
@@ -1221,74 +1490,14 @@ export default defineNuxtPlugin({
1221
1490
  });
1222
1491
  }
1223
1492
  const { renderedScript } = setupPublicAssetStrategy(config.assets);
1224
- if (firstPartyEnabled) {
1225
- const proxyConfigs = getAllProxyConfigs(firstPartyCollectPrefix);
1226
- const registryKeys = Object.keys(config.registry || {});
1227
- const neededRoutes = {};
1228
- const unsupportedScripts = [];
1229
- for (const key of registryKeys) {
1230
- const script = registryScriptsWithImport.find((s) => s.import.name.toLowerCase() === `usescript${key.toLowerCase()}`);
1231
- const proxyKey = script?.proxy || void 0;
1232
- if (proxyKey) {
1233
- const proxyConfig = proxyConfigs[proxyKey];
1234
- if (proxyConfig?.routes) {
1235
- Object.assign(neededRoutes, proxyConfig.routes);
1236
- } else {
1237
- unsupportedScripts.push(key);
1238
- }
1239
- }
1240
- }
1241
- if (unsupportedScripts.length && nuxt.options.dev) {
1242
- logger.warn(
1243
- `First-party mode is enabled but these scripts don't support it yet: ${unsupportedScripts.join(", ")}.
1244
- They will load directly from third-party servers. Request support at https://github.com/nuxt/scripts/issues`
1245
- );
1246
- }
1247
- const flatRoutes = {};
1248
- for (const [path, config2] of Object.entries(neededRoutes)) {
1249
- flatRoutes[path] = config2.proxy;
1250
- }
1251
- const allRewrites = [];
1252
- for (const key of registryKeys) {
1253
- const script = registryScriptsWithImport.find((s) => s.import.name.toLowerCase() === `usescript${key.toLowerCase()}`);
1254
- const proxyKey = script?.proxy !== false ? script?.proxy || key : void 0;
1255
- if (proxyKey) {
1256
- const proxyConfig = proxyConfigs[proxyKey];
1257
- if (proxyConfig?.rewrite) {
1258
- allRewrites.push(...proxyConfig.rewrite);
1259
- }
1260
- }
1261
- }
1262
- nuxt.options.runtimeConfig["nuxt-scripts-proxy"] = {
1263
- routes: flatRoutes,
1264
- privacy: firstPartyPrivacy,
1265
- rewrites: allRewrites
1266
- };
1267
- if (Object.keys(neededRoutes).length) {
1268
- if (nuxt.options.dev) {
1269
- const routeCount = Object.keys(neededRoutes).length;
1270
- const scriptsCount = registryKeys.length;
1271
- logger.success(`First-party mode enabled for ${scriptsCount} script(s), ${routeCount} proxy route(s) configured (privacy: ${firstPartyPrivacy})`);
1272
- if (logger.level >= 4) {
1273
- for (const [path, config2] of Object.entries(neededRoutes)) {
1274
- logger.debug(` ${path} \u2192 ${config2.proxy}`);
1275
- }
1276
- }
1277
- }
1278
- }
1279
- if (isStaticPreset) {
1280
- logger.warn(
1281
- `First-party collection endpoints require a server runtime (detected: ${preset || "static"}).
1282
- Scripts will be bundled, but collection requests will not be proxied.
1283
-
1284
- Options:
1285
- 1. Configure platform rewrites (Vercel, Netlify, Cloudflare)
1286
- 2. Switch to server-rendered mode (ssr: true)
1287
- 3. Disable with firstParty: false
1288
-
1289
- See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
1290
- );
1291
- }
1493
+ if (firstParty.enabled) {
1494
+ finalizeFirstParty({
1495
+ firstParty,
1496
+ interceptRules,
1497
+ registry: config.registry,
1498
+ registryScriptsWithImport,
1499
+ nuxtOptions: nuxt.options
1500
+ });
1292
1501
  }
1293
1502
  const moduleInstallPromises = /* @__PURE__ */ new Map();
1294
1503
  addBuildPlugin(NuxtScriptsCheckScripts(), {
@@ -1297,9 +1506,9 @@ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
1297
1506
  addBuildPlugin(NuxtScriptBundleTransformer({
1298
1507
  scripts: registryScriptsWithImport,
1299
1508
  registryConfig: nuxt.options.runtimeConfig.public.scripts,
1300
- defaultBundle: firstPartyEnabled || config.defaultScriptOptions?.bundle,
1301
- firstPartyEnabled,
1302
- firstPartyCollectPrefix,
1509
+ defaultBundle: firstParty.enabled || config.defaultScriptOptions?.bundle,
1510
+ firstPartyEnabled: firstParty.enabled,
1511
+ firstPartyCollectPrefix: firstParty.collectPrefix,
1303
1512
  moduleDetected(module) {
1304
1513
  if (nuxt.options.dev && module !== "@nuxt/scripts" && !moduleInstallPromises.has(module) && !hasNuxtModule(module))
1305
1514
  moduleInstallPromises.set(module, () => installNuxtModule(module));
@@ -1312,7 +1521,7 @@ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
1312
1521
  renderedScript
1313
1522
  }));
1314
1523
  nuxt.hooks.hook("build:done", async () => {
1315
- const initPromise = Array.from(moduleInstallPromises.values());
1524
+ const initPromise = [...moduleInstallPromises.values()];
1316
1525
  for (const p of initPromise)
1317
1526
  await p?.();
1318
1527
  });
@@ -1323,6 +1532,17 @@ See: https://scripts.nuxt.com/docs/guides/first-party#static-hosting`
1323
1532
  handler: await resolvePath("./runtime/server/google-static-maps-proxy")
1324
1533
  });
1325
1534
  }
1535
+ if (config.registry?.gravatar) {
1536
+ const gravatarConfig = typeof config.registry.gravatar === "object" && !Array.isArray(config.registry.gravatar) ? config.registry.gravatar : {};
1537
+ nuxt.options.runtimeConfig.public["nuxt-scripts"] = defu(
1538
+ { gravatarProxy: { cacheMaxAge: gravatarConfig.cacheMaxAge ?? 3600 } },
1539
+ nuxt.options.runtimeConfig.public["nuxt-scripts"]
1540
+ );
1541
+ addServerHandler({
1542
+ route: "/_scripts/gravatar-proxy",
1543
+ handler: await resolvePath("./runtime/server/gravatar-proxy")
1544
+ });
1545
+ }
1326
1546
  addServerHandler({
1327
1547
  route: "/api/_scripts/x-embed",
1328
1548
  handler: await resolvePath("./runtime/server/x-embed")