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

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 (70) hide show
  1. package/README.md +6 -0
  2. package/dist/client/200.html +1 -1
  3. package/dist/client/404.html +1 -1
  4. package/dist/client/_nuxt/CD5B-xvT.js +1 -0
  5. package/dist/client/_nuxt/D-kOnTuH.js +162 -0
  6. package/dist/client/_nuxt/DdVDSbUA.js +1 -0
  7. package/dist/client/_nuxt/{DTDyDxvR.js → Ds2G8aQM.js} +1 -1
  8. package/dist/client/_nuxt/builds/latest.json +1 -1
  9. package/dist/client/_nuxt/builds/meta/f1474569-6922-450d-bc3f-4fd5f3e1391a.json +1 -0
  10. package/dist/client/_nuxt/entry.D45OuV0w.css +1 -0
  11. package/dist/client/_nuxt/error-404.B57D-jUQ.css +1 -0
  12. package/dist/client/_nuxt/error-500.DTHUW7BI.css +1 -0
  13. package/dist/client/index.html +1 -1
  14. package/dist/module.d.mts +87 -2
  15. package/dist/module.json +1 -1
  16. package/dist/module.mjs +678 -142
  17. package/dist/registry.mjs +11 -0
  18. package/dist/runtime/components/ScriptInstagramEmbed.d.vue.ts +53 -0
  19. package/dist/runtime/components/ScriptInstagramEmbed.vue +38 -0
  20. package/dist/runtime/components/ScriptInstagramEmbed.vue.d.ts +53 -0
  21. package/dist/runtime/components/ScriptXEmbed.d.vue.ts +82 -0
  22. package/dist/runtime/components/ScriptXEmbed.vue +76 -0
  23. package/dist/runtime/components/ScriptXEmbed.vue.d.ts +82 -0
  24. package/dist/runtime/composables/useScript.js +24 -3
  25. package/dist/runtime/composables/useScriptTriggerServiceWorker.d.ts +7 -0
  26. package/dist/runtime/composables/useScriptTriggerServiceWorker.js +39 -0
  27. package/dist/runtime/plugins/sw-register.client.d.ts +2 -0
  28. package/dist/runtime/plugins/sw-register.client.js +12 -0
  29. package/dist/runtime/registry/instagram-embed.d.ts +23 -0
  30. package/dist/runtime/registry/instagram-embed.js +22 -0
  31. package/dist/runtime/registry/lemon-squeezy.d.ts +0 -1
  32. package/dist/runtime/registry/plausible-analytics.js +2 -2
  33. package/dist/runtime/registry/posthog.d.ts +1 -0
  34. package/dist/runtime/registry/posthog.js +2 -8
  35. package/dist/runtime/registry/tiktok-pixel.d.ts +1 -0
  36. package/dist/runtime/registry/tiktok-pixel.js +1 -0
  37. package/dist/runtime/registry/x-embed.d.ts +77 -0
  38. package/dist/runtime/registry/x-embed.js +41 -0
  39. package/dist/runtime/server/instagram-embed-asset.d.ts +2 -0
  40. package/dist/runtime/server/instagram-embed-asset.js +42 -0
  41. package/dist/runtime/server/instagram-embed-image.d.ts +2 -0
  42. package/dist/runtime/server/instagram-embed-image.js +54 -0
  43. package/dist/runtime/server/instagram-embed.d.ts +2 -0
  44. package/dist/runtime/server/instagram-embed.js +91 -0
  45. package/dist/runtime/server/proxy-handler.d.ts +6 -0
  46. package/dist/runtime/server/proxy-handler.js +246 -0
  47. package/dist/runtime/server/sw-handler.d.ts +2 -0
  48. package/dist/runtime/server/sw-handler.js +25 -0
  49. package/dist/runtime/server/utils/privacy.d.ts +141 -0
  50. package/dist/runtime/server/utils/privacy.js +309 -0
  51. package/dist/runtime/server/x-embed-image.d.ts +2 -0
  52. package/dist/runtime/server/x-embed-image.js +53 -0
  53. package/dist/runtime/server/x-embed.d.ts +49 -0
  54. package/dist/runtime/server/x-embed.js +31 -0
  55. package/dist/runtime/sw/proxy-sw.template.d.ts +1 -0
  56. package/dist/runtime/sw/proxy-sw.template.js +54 -0
  57. package/dist/runtime/types.d.ts +29 -0
  58. package/dist/runtime/utils/pure.d.ts +13 -0
  59. package/dist/runtime/utils/pure.js +67 -0
  60. package/dist/runtime/utils.d.ts +1 -1
  61. package/dist/runtime/utils.js +2 -1
  62. package/dist/types.d.mts +1 -1
  63. package/package.json +27 -26
  64. package/dist/client/_nuxt/Bdf7Qtwg.js +0 -1
  65. package/dist/client/_nuxt/CoyZWCgl.js +0 -162
  66. package/dist/client/_nuxt/Ds1k3yKJ.js +0 -1
  67. package/dist/client/_nuxt/builds/meta/62574f80-71d4-4f9e-8b96-145c85230d99.json +0 -1
  68. package/dist/client/_nuxt/entry.BjfcJo5q.css +0 -1
  69. package/dist/client/_nuxt/error-404.D45Vtjcx.css +0 -1
  70. package/dist/client/_nuxt/error-500.BOm1rWQf.css +0 -1
package/dist/module.mjs CHANGED
@@ -1,12 +1,13 @@
1
- import { useNuxt, extendViteConfig, useLogger, addDevServerHandler, tryUseNuxt, logger as logger$1, addTypeTemplate, defineNuxtModule, createResolver, addImports, addComponentsDir, addTemplate, addPluginTemplate, addBuildPlugin, addServerHandler, hasNuxtModule } from '@nuxt/kit';
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';
2
3
  import { defu } from 'defu';
3
4
  import { resolvePackageJSON, readPackageJSON } from 'pkg-types';
4
- import { existsSync } from 'node:fs';
5
+ import { addCustomTab } from '@nuxt/devtools-kit';
5
6
  import { createHash } from 'node:crypto';
6
7
  import fsp from 'node:fs/promises';
7
8
  import { createUnplugin } from 'unplugin';
8
9
  import MagicString from 'magic-string';
9
- import { asyncWalk, walk } from 'estree-walker';
10
+ import { parseAndWalk } from 'oxc-walker';
10
11
  import { joinURL, parseURL, parseQuery, hasProtocol } from 'ufo';
11
12
  import { hash } from 'ohash';
12
13
  import { join, resolve, relative } from 'pathe';
@@ -15,6 +16,7 @@ import { fetch, $fetch } from 'ofetch';
15
16
  import { lazyEventHandler, eventHandler, createError } from 'h3';
16
17
  import { createStorage } from 'unstorage';
17
18
  import fsDriver from 'unstorage/drivers/fs-lite';
19
+ import { rewriteScriptUrls } from '../dist/runtime/utils/pure.js';
18
20
  import { pathToFileURL } from 'node:url';
19
21
  import { isCI, provider } from 'std-env';
20
22
  import { registry } from './registry.mjs';
@@ -45,20 +47,18 @@ async function setupDevToolsUI(options, resolve, nuxt = useNuxt()) {
45
47
  };
46
48
  });
47
49
  }
48
- nuxt.hook("devtools:customTabs", (tabs) => {
49
- tabs.push({
50
- // unique identifier
51
- name: "nuxt-scripts",
52
- // title to display in the tab
53
- title: "Scripts",
54
- // any icon from Iconify, or a URL to an image
55
- icon: "carbon:script",
56
- // iframe view
57
- view: {
58
- type: "iframe",
59
- src: DEVTOOLS_UI_ROUTE
60
- }
61
- });
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
62
  });
63
63
  }
64
64
 
@@ -86,6 +86,9 @@ function setupPublicAssetStrategy(options = {}) {
86
86
  const scriptDescriptor = renderedScript.get(join(assetsBaseURL, event.path.slice(1)));
87
87
  if (!scriptDescriptor || scriptDescriptor instanceof Error)
88
88
  throw createError({ statusCode: 404 });
89
+ if (scriptDescriptor.content) {
90
+ return scriptDescriptor.content;
91
+ }
89
92
  const key = `bundle:${filename}`;
90
93
  let res = await storage.getItemRaw(key);
91
94
  if (!res) {
@@ -97,31 +100,237 @@ function setupPublicAssetStrategy(options = {}) {
97
100
  })
98
101
  });
99
102
  if (nuxt.options.dev) {
100
- nuxt.options.routeRules ||= {};
101
- nuxt.options.routeRules[joinURL(assetsBaseURL, "**")] = {
103
+ extendRouteRules(joinURL(assetsBaseURL, "**"), {
102
104
  cache: {
103
105
  maxAge: ONE_YEAR_IN_SECONDS
104
106
  }
105
- };
107
+ });
106
108
  }
107
- nuxt.options.nitro.publicAssets ||= [];
108
109
  const cacheDir = join(nuxt.options.buildDir, "cache", "scripts");
109
- nuxt.options.nitro.publicAssets.push();
110
- nuxt.options.nitro = defu(nuxt.options.nitro, {
111
- publicAssets: [{
110
+ nuxt.hook("nitro:config", (nitroConfig) => {
111
+ nitroConfig.publicAssets ||= [];
112
+ nitroConfig.publicAssets.push({
112
113
  dir: cacheDir,
113
114
  maxAge: ONE_YEAR_IN_SECONDS,
114
115
  baseURL: assetsBaseURL
115
- }],
116
- prerender: {
117
- ignore: [assetsBaseURL]
118
- }
116
+ });
117
+ nitroConfig.prerender ||= {};
118
+ nitroConfig.prerender.ignore ||= [];
119
+ nitroConfig.prerender.ignore.push(assetsBaseURL);
119
120
  });
120
121
  return {
121
122
  renderedScript
122
123
  };
123
124
  }
124
125
 
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/**" }
188
+ }
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/**" }
198
+ }
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/**" }
210
+ }
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/**" }
222
+ }
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/**" }
232
+ }
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/**" }
242
+ }
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/**" }
262
+ }
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/**" }
275
+ }
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/**" }
304
+ }
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 });
328
+ }
329
+ }
330
+ }
331
+ return rules;
332
+ }
333
+
125
334
  function isVue(id, opts = {}) {
126
335
  const { search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
127
336
  if (id.endsWith(".vue") && !search) {
@@ -178,7 +387,7 @@ function normalizeScriptData(src, assetsBaseURL = "/_scripts") {
178
387
  return { url: src };
179
388
  }
180
389
  async function downloadScript(opts, renderedScript, fetchOptions, cacheMaxAge) {
181
- const { src, url, filename, forceDownload, integrity } = opts;
390
+ const { src, url, filename, forceDownload, integrity, proxyRewrites } = opts;
182
391
  if (src === url || !filename) {
183
392
  return;
184
393
  }
@@ -186,7 +395,8 @@ async function downloadScript(opts, renderedScript, fetchOptions, cacheMaxAge) {
186
395
  const scriptContent = renderedScript.get(src);
187
396
  let res = scriptContent instanceof Error ? void 0 : scriptContent?.content;
188
397
  if (!res) {
189
- const cacheKey = `bundle:${filename}`;
398
+ const proxyRewritesHash = proxyRewrites?.length ? `-${hash(proxyRewrites)}` : "";
399
+ const cacheKey = proxyRewrites?.length ? `bundle-proxy:${filename.replace(".js", `${proxyRewritesHash}.js`)}` : `bundle:${filename}`;
190
400
  const shouldUseCache = !forceDownload && await storage.hasItem(cacheKey) && !await isCacheExpired(storage, filename, cacheMaxAge);
191
401
  if (shouldUseCache) {
192
402
  const cachedContent = await storage.getItemRaw(cacheKey);
@@ -212,8 +422,15 @@ async function downloadScript(opts, renderedScript, fetchOptions, cacheMaxAge) {
212
422
  size = contentLength ? Number(contentLength) / 1024 : 0;
213
423
  return Buffer.from(r._data || await r.arrayBuffer());
214
424
  });
215
- const integrityHash = integrity && res ? calculateIntegrity(res, integrity === true ? "sha384" : integrity) : void 0;
216
425
  await storage.setItemRaw(`bundle:${filename}`, res);
426
+ if (proxyRewrites?.length && res) {
427
+ const content = res.toString("utf-8");
428
+ const rewritten = rewriteScriptUrls(content, proxyRewrites);
429
+ res = Buffer.from(rewritten, "utf-8");
430
+ logger.debug(`Rewrote ${proxyRewrites.length} URL patterns in ${filename}`);
431
+ }
432
+ const integrityHash = integrity && res ? calculateIntegrity(res, integrity === true ? "sha384" : integrity) : void 0;
433
+ await storage.setItemRaw(cacheKey, res);
217
434
  await storage.setItem(`bundle-meta:${filename}`, {
218
435
  timestamp: Date.now(),
219
436
  src,
@@ -262,16 +479,21 @@ function NuxtScriptBundleTransformer(options = {
262
479
  return createUnplugin(() => {
263
480
  return {
264
481
  name: "nuxt:scripts:bundler-transformer",
265
- transformInclude(id) {
266
- return isVue(id, { type: ["template", "script"] }) || isJS(id);
267
- },
268
- async transform(code, id) {
269
- if (!code.includes("useScript"))
270
- return;
271
- const ast = this.parse(code);
272
- const s = new MagicString(code);
273
- await asyncWalk(ast, {
274
- async enter(_node) {
482
+ transform: {
483
+ filter: {
484
+ id: {
485
+ include: [/\.vue/, /\.[cm]?[jt]sx?$/],
486
+ exclude: [/\.(?:test|spec)\./]
487
+ }
488
+ },
489
+ async handler(code, id) {
490
+ if (!isVue(id, { type: ["template", "script"] }) && !isJS(id))
491
+ return;
492
+ if (!code.includes("useScript"))
493
+ return;
494
+ const s = new MagicString(code);
495
+ const deferredOps = [];
496
+ parseAndWalk(code, id, function(_node) {
275
497
  const calleeName = _node.callee?.name;
276
498
  if (!calleeName)
277
499
  return;
@@ -281,6 +503,11 @@ function NuxtScriptBundleTransformer(options = {
281
503
  const node = _node;
282
504
  let scriptSrcNode;
283
505
  let src;
506
+ let registryKey;
507
+ if (fnName !== "useScript") {
508
+ const baseName = fnName.replace(/^useScript/, "");
509
+ registryKey = baseName.length > 0 ? baseName.charAt(0).toLowerCase() + baseName.slice(1) : void 0;
510
+ }
284
511
  if (fnName === "useScript") {
285
512
  if (node.arguments[0]?.type === "Literal") {
286
513
  scriptSrcNode = node.arguments[0];
@@ -297,9 +524,7 @@ function NuxtScriptBundleTransformer(options = {
297
524
  }
298
525
  if (!registryNode.scriptBundling && !registryNode.src)
299
526
  return;
300
- const baseName = fnName.replace(/^useScript/, "");
301
- const registryKey = baseName.length > 0 ? baseName.charAt(0).toLowerCase() + baseName.slice(1) : "";
302
- const registryConfig = options.registryConfig?.[registryKey] || {};
527
+ const registryConfig = options.registryConfig?.[registryKey || ""] || {};
303
528
  const fnArg0 = {};
304
529
  if (node.arguments[0]?.type === "ObjectExpression") {
305
530
  const optionsNode = node.arguments[0];
@@ -319,6 +544,8 @@ function NuxtScriptBundleTransformer(options = {
319
544
  src = registryNode.scriptBundling && registryNode.scriptBundling(mergedOptions);
320
545
  if (src === false)
321
546
  return;
547
+ if (!src && registryNode.src)
548
+ src = registryNode.src;
322
549
  }
323
550
  }
324
551
  if (!scriptSrcNode && !src) {
@@ -333,8 +560,7 @@ function NuxtScriptBundleTransformer(options = {
333
560
  if (bundleProperty && bundleProperty.value.type === "Literal") {
334
561
  const bundleValue = bundleProperty.value.value;
335
562
  if (bundleValue === true || bundleValue === "force" || String(bundleValue) === "true") {
336
- const valueNode = bundleProperty.value;
337
- s.overwrite(valueNode.start, valueNode.end, `'unsupported'`);
563
+ s.overwrite(bundleProperty.value.start, bundleProperty.value.end, `'unsupported'`);
338
564
  }
339
565
  }
340
566
  }
@@ -351,8 +577,7 @@ function NuxtScriptBundleTransformer(options = {
351
577
  (p) => (p.key?.name === "bundle" || p.key?.value === "bundle") && p.type === "Property"
352
578
  );
353
579
  if (bundleProperty && bundleProperty.value.type === "Literal") {
354
- const value = bundleProperty.value;
355
- const bundleValue = value.value;
580
+ const bundleValue = bundleProperty.value.value;
356
581
  if (bundleValue !== true && bundleValue !== "force" && String(bundleValue) !== "true") {
357
582
  canBundle = false;
358
583
  return;
@@ -380,81 +605,104 @@ function NuxtScriptBundleTransformer(options = {
380
605
  canBundle = bundleValue === true || bundleValue === "force" || String(bundleValue) === "true";
381
606
  forceDownload = bundleValue === "force";
382
607
  }
608
+ const firstPartyOption = scriptOptions?.value.properties?.find((prop) => {
609
+ return prop.type === "Property" && prop.key?.name === "firstParty" && prop.value.type === "Literal";
610
+ });
611
+ let firstPartyOptOut = firstPartyOption?.value.value === false;
612
+ if (!firstPartyOptOut && node.arguments[1]?.type === "ObjectExpression") {
613
+ const secondArgFirstPartyProp = node.arguments[1].properties.find(
614
+ (p) => p.type === "Property" && p.key?.name === "firstParty" && p.value.type === "Literal"
615
+ );
616
+ firstPartyOptOut = secondArgFirstPartyProp?.value.value === false;
617
+ }
618
+ if (!firstPartyOptOut && node.arguments[0]?.type === "ObjectExpression") {
619
+ const firstArgFirstPartyProp = node.arguments[0].properties.find(
620
+ (p) => p.type === "Property" && p.key?.name === "firstParty" && p.value.type === "Literal"
621
+ );
622
+ firstPartyOptOut = firstArgFirstPartyProp?.value.value === false;
623
+ }
383
624
  if (canBundle) {
384
625
  const { url: _url, filename } = normalizeScriptData(src, options.assetsBaseURL);
385
- let url = _url;
386
- try {
387
- await downloadScript({ src, url, filename, forceDownload, integrity: options.integrity }, renderedScript, options.fetchOptions, options.cacheMaxAge);
388
- } catch (e) {
389
- if (options.fallbackOnSrcOnBundleFail) {
390
- logger.warn(`[Nuxt Scripts: Bundle Transformer] Failed to bundle ${src}. Fallback to remote loading.`);
391
- url = src;
392
- } else {
393
- const errorMessage = e?.message || "Unknown error";
394
- if (errorMessage.includes("timeout") || errorMessage.includes("network") || errorMessage.includes("ENOTFOUND")) {
395
- logger.error(`[Nuxt Scripts: Bundle Transformer] Network issue while bundling ${src}: ${errorMessage}`);
396
- logger.error(`[Nuxt Scripts: Bundle Transformer] Tip: Set 'fallbackOnSrcOnBundleFail: true' in module options or disable bundling in Docker environments`);
626
+ const script = options.scripts?.find((s2) => s2.import.name === fnName);
627
+ 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;
629
+ deferredOps.push(async () => {
630
+ let url = _url;
631
+ try {
632
+ await downloadScript({ src, url, filename, forceDownload, proxyRewrites, integrity: options.integrity }, renderedScript, options.fetchOptions, options.cacheMaxAge);
633
+ } catch (e) {
634
+ if (options.fallbackOnSrcOnBundleFail) {
635
+ logger.warn(`[Nuxt Scripts: Bundle Transformer] Failed to bundle ${src}. Fallback to remote loading.`);
636
+ url = src;
637
+ } else {
638
+ const errorMessage = e?.message || "Unknown error";
639
+ if (errorMessage.includes("timeout") || errorMessage.includes("network") || errorMessage.includes("ENOTFOUND") || errorMessage.includes("certificate")) {
640
+ logger.error(`[Nuxt Scripts: Bundle Transformer] Network issue while bundling ${src}: ${errorMessage}`);
641
+ logger.error(`[Nuxt Scripts: Bundle Transformer] Tip: Set 'fallbackOnSrcOnBundleFail: true' in module options or disable bundling in Docker environments`);
642
+ }
643
+ throw e;
397
644
  }
398
- throw e;
399
645
  }
400
- }
401
- if (src === url) {
402
- if (src && src.startsWith("/"))
403
- logger.warn(`[Nuxt Scripts: Bundle Transformer] Relative scripts are already bundled. Skipping bundling for \`${src}\`.`);
404
- else
405
- logger.warn(`[Nuxt Scripts: Bundle Transformer] Failed to bundle ${src}.`);
406
- }
407
- const scriptMeta = renderedScript.get(url);
408
- const integrityHash = scriptMeta instanceof Error ? void 0 : scriptMeta?.integrity;
409
- if (scriptSrcNode) {
410
- if (integrityHash && fnName === "useScript" && node.arguments[0]?.type === "Literal") {
411
- s.overwrite(scriptSrcNode.start, scriptSrcNode.end, `{ src: '${url}', integrity: '${integrityHash}', crossorigin: 'anonymous' }`);
412
- } else if (integrityHash && fnName === "useScript" && node.arguments[0]?.type === "ObjectExpression") {
413
- s.overwrite(scriptSrcNode.start, scriptSrcNode.end, `'${url}'`);
414
- const objArg = node.arguments[0];
415
- s.appendLeft(objArg.end - 1, `, integrity: '${integrityHash}', crossorigin: 'anonymous'`);
416
- } else {
417
- s.overwrite(scriptSrcNode.start, scriptSrcNode.end, `'${url}'`);
646
+ if (src === url) {
647
+ if (src && src.startsWith("/"))
648
+ logger.warn(`[Nuxt Scripts: Bundle Transformer] Relative scripts are already bundled. Skipping bundling for \`${src}\`.`);
649
+ else
650
+ logger.warn(`[Nuxt Scripts: Bundle Transformer] Failed to bundle ${src}.`);
418
651
  }
419
- } else {
420
- const integrityProps = integrityHash ? `, integrity: '${integrityHash}', crossorigin: 'anonymous'` : "";
421
- if (node.arguments[0]) {
422
- const optionsNode = node.arguments[0];
423
- const scriptInputProperty = optionsNode.properties.find(
424
- (p) => p.key?.name === "scriptInput" || p.key?.value === "scriptInput"
425
- );
426
- if (scriptInputProperty) {
427
- const scriptInput = scriptInputProperty.value;
428
- if (scriptInput.type === "ObjectExpression") {
429
- const srcProperty = scriptInput.properties.find(
430
- (p) => p.key?.name === "src" || p.key?.value === "src"
431
- );
432
- if (srcProperty) {
433
- s.overwrite(srcProperty.value.start, srcProperty.value.end, `'${url}'`);
434
- if (integrityHash)
435
- s.appendLeft(scriptInput.end - 1, integrityProps);
436
- } else {
437
- s.appendRight(scriptInput.end - 1, `, src: '${url}'${integrityProps}`);
652
+ const scriptMeta = renderedScript.get(url);
653
+ const integrityHash = scriptMeta instanceof Error ? void 0 : scriptMeta?.integrity;
654
+ if (scriptSrcNode) {
655
+ if (integrityHash && fnName === "useScript" && node.arguments[0]?.type === "Literal") {
656
+ s.overwrite(scriptSrcNode.start, scriptSrcNode.end, `{ src: '${url}', integrity: '${integrityHash}', crossorigin: 'anonymous' }`);
657
+ } else if (integrityHash && fnName === "useScript" && node.arguments[0]?.type === "ObjectExpression") {
658
+ s.overwrite(scriptSrcNode.start, scriptSrcNode.end, `'${url}'`);
659
+ s.appendLeft(node.arguments[0].end - 1, `, integrity: '${integrityHash}', crossorigin: 'anonymous'`);
660
+ } else {
661
+ s.overwrite(scriptSrcNode.start, scriptSrcNode.end, `'${url}'`);
662
+ }
663
+ } else {
664
+ const integrityProps = integrityHash ? `, integrity: '${integrityHash}', crossorigin: 'anonymous'` : "";
665
+ if (node.arguments[0]) {
666
+ const optionsNode = node.arguments[0];
667
+ const scriptInputProperty = optionsNode.properties.find(
668
+ (p) => p.key?.name === "scriptInput" || p.key?.value === "scriptInput"
669
+ );
670
+ if (scriptInputProperty) {
671
+ const scriptInput = scriptInputProperty.value;
672
+ if (scriptInput.type === "ObjectExpression") {
673
+ const srcProperty = scriptInput.properties.find(
674
+ (p) => p.key?.name === "src" || p.key?.value === "src"
675
+ );
676
+ if (srcProperty) {
677
+ s.overwrite(srcProperty.value.start, srcProperty.value.end, `'${url}'`);
678
+ if (integrityHash)
679
+ s.appendLeft(scriptInput.end - 1, integrityProps);
680
+ } else {
681
+ s.appendRight(scriptInput.end - 1, `, src: '${url}'${integrityProps}`);
682
+ }
438
683
  }
684
+ } else {
685
+ s.appendRight(node.arguments[0].start + 1, ` scriptInput: { src: '${url}'${integrityProps} }, `);
439
686
  }
440
687
  } else {
441
- s.appendRight(node.arguments[0].start + 1, ` scriptInput: { src: '${url}'${integrityProps} }, `);
688
+ s.appendRight(node.callee.end, `({ scriptInput: { src: '${url}'${integrityProps} } })`);
442
689
  }
443
- } else {
444
- s.appendRight(node.callee.end, `({ scriptInput: { src: '${url}'${integrityProps} } })`);
445
690
  }
446
- }
691
+ });
447
692
  }
448
693
  }
449
694
  }
450
695
  }
696
+ });
697
+ for (const op of deferredOps) {
698
+ await op();
699
+ }
700
+ if (s.hasChanged()) {
701
+ return {
702
+ code: s.toString(),
703
+ map: s.generateMap({ includeContent: true, source: id })
704
+ };
451
705
  }
452
- });
453
- if (s.hasChanged()) {
454
- return {
455
- code: s.toString(),
456
- map: s.generateMap({ includeContent: true, source: id })
457
- };
458
706
  }
459
707
  }
460
708
  };
@@ -505,17 +753,18 @@ function NuxtScriptsCheckScripts() {
505
753
  return createUnplugin(() => {
506
754
  return {
507
755
  name: "nuxt-scripts:check-scripts",
508
- transformInclude(id) {
509
- return isVue(id, { type: ["script"] });
510
- },
511
- async transform(code) {
512
- if (!code.includes("useScript"))
513
- return;
514
- const ast = this.parse(code);
515
- let nameNode;
516
- let errorNode;
517
- walk(ast, {
518
- enter(_node) {
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) {
519
768
  if (_node.type === "VariableDeclaration" && _node.declarations?.[0]?.id?.type === "ObjectPattern") {
520
769
  const objPattern = _node.declarations[0]?.id;
521
770
  for (const property of objPattern.properties) {
@@ -545,10 +794,10 @@ function NuxtScriptsCheckScripts() {
545
794
  }
546
795
  }
547
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."));
548
800
  }
549
- });
550
- if (errorNode) {
551
- return this.error(new Error("You can't use a top-level await on $script as it will never resolve."));
552
801
  }
553
802
  }
554
803
  };
@@ -596,6 +845,7 @@ export {}`;
596
845
  function templateTriggerResolver(defaultScriptOptions) {
597
846
  const needsIdleTimeout = defaultScriptOptions?.trigger && typeof defaultScriptOptions.trigger === "object" && "idleTimeout" in defaultScriptOptions.trigger;
598
847
  const needsInteraction = defaultScriptOptions?.trigger && typeof defaultScriptOptions.trigger === "object" && "interaction" in defaultScriptOptions.trigger;
848
+ const needsServiceWorker = defaultScriptOptions?.trigger && typeof defaultScriptOptions.trigger === "object" && "serviceWorker" in defaultScriptOptions.trigger;
599
849
  const imports = [];
600
850
  if (needsIdleTimeout) {
601
851
  imports.push(`import { useScriptTriggerIdleTimeout } from '#nuxt-scripts/composables/useScriptTriggerIdleTimeout'`);
@@ -603,11 +853,15 @@ function templateTriggerResolver(defaultScriptOptions) {
603
853
  if (needsInteraction) {
604
854
  imports.push(`import { useScriptTriggerInteraction } from '#nuxt-scripts/composables/useScriptTriggerInteraction'`);
605
855
  }
856
+ if (needsServiceWorker) {
857
+ imports.push(`import { useScriptTriggerServiceWorker } from '#nuxt-scripts/composables/useScriptTriggerServiceWorker'`);
858
+ }
606
859
  return [
607
860
  ...imports,
608
861
  `export function resolveTrigger(trigger) {`,
609
862
  needsIdleTimeout ? ` if ('idleTimeout' in trigger) return useScriptTriggerIdleTimeout({ timeout: trigger.idleTimeout })` : "",
610
863
  needsInteraction ? ` if ('interaction' in trigger) return useScriptTriggerInteraction({ events: trigger.interaction })` : "",
864
+ needsServiceWorker ? ` if ('serviceWorker' in trigger) return useScriptTriggerServiceWorker()` : "",
611
865
  ` return null`,
612
866
  `}`
613
867
  ].filter(Boolean).join("\n");
@@ -624,6 +878,9 @@ function resolveTriggerForTemplate(trigger) {
624
878
  if ("interaction" in trigger) {
625
879
  return `useScriptTriggerInteraction({ events: ${JSON.stringify(trigger.interaction)} })`;
626
880
  }
881
+ if ("serviceWorker" in trigger) {
882
+ return `useScriptTriggerServiceWorker()`;
883
+ }
627
884
  }
628
885
  return null;
629
886
  }
@@ -634,27 +891,34 @@ function templatePlugin(config, registry) {
634
891
  }
635
892
  const imports = [];
636
893
  const inits = [];
894
+ const resolvedRegistryKeys = [];
637
895
  let needsIdleTimeoutImport = false;
638
896
  let needsInteractionImport = false;
897
+ let needsServiceWorkerImport = false;
639
898
  for (const [k, c] of Object.entries(config.registry || {})) {
640
- const importDefinition = registry.find((i) => i.import.name === `useScript${k.substring(0, 1).toUpperCase() + k.substring(1)}`);
899
+ const importDefinition = registry.find((i) => i.proxy === k || i.import.name === `useScript${k.substring(0, 1).toUpperCase() + k.substring(1)}`);
641
900
  if (importDefinition) {
901
+ resolvedRegistryKeys.push(k);
642
902
  imports.unshift(`import { ${importDefinition.import.name} } from '${importDefinition.import.from}'`);
643
- const args = (typeof c !== "object" ? {} : c) || {};
644
903
  if (c === "mock") {
645
- args.scriptOptions = { trigger: "manual", skipValidation: true };
646
- } else if (Array.isArray(c) && c.length === 2 && c[1]?.trigger) {
647
- const triggerResolved = resolveTriggerForTemplate(c[1].trigger);
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);
648
909
  if (triggerResolved) {
649
- args.scriptOptions = { ...c[1] };
650
- if (args.scriptOptions) {
651
- args.scriptOptions.trigger = `__TRIGGER_${triggerResolved}__`;
652
- }
910
+ scriptOptions.trigger = "__TRIGGER_PLACEHOLDER__";
653
911
  if (triggerResolved.includes("useScriptTriggerIdleTimeout")) needsIdleTimeoutImport = true;
654
912
  if (triggerResolved.includes("useScriptTriggerInteraction")) needsInteractionImport = true;
913
+ if (triggerResolved.includes("useScriptTriggerServiceWorker")) needsServiceWorkerImport = true;
655
914
  }
915
+ const args = { ...input, scriptOptions };
916
+ const argsJson = triggerResolved ? JSON.stringify(args).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved) : JSON.stringify(args);
917
+ inits.push(`const ${k} = ${importDefinition.import.name}(${argsJson})`);
918
+ } else {
919
+ const args = (typeof c !== "object" ? {} : c) || {};
920
+ inits.push(`const ${k} = ${importDefinition.import.name}(${JSON.stringify(args)})`);
656
921
  }
657
- inits.push(`const ${k} = ${importDefinition.import.name}(${JSON.stringify(args).replace(/"__TRIGGER_(.*?)__"/g, "$1")})`);
658
922
  }
659
923
  }
660
924
  for (const [k, c] of Object.entries(config.globals || {})) {
@@ -666,8 +930,10 @@ function templatePlugin(config, registry) {
666
930
  if (triggerResolved) {
667
931
  if (triggerResolved.includes("useScriptTriggerIdleTimeout")) needsIdleTimeoutImport = true;
668
932
  if (triggerResolved.includes("useScriptTriggerInteraction")) needsInteractionImport = true;
669
- const resolvedOptions = { ...options, trigger: `__TRIGGER_${triggerResolved}__` };
670
- inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...typeof c[0] === "string" ? { src: c[0] } : c[0] })}, { ...${JSON.stringify(resolvedOptions).replace(/"__TRIGGER_(.*?)__"/g, "$1")}, use: () => ({ ${k}: window.${k} }) })`);
933
+ if (triggerResolved.includes("useScriptTriggerServiceWorker")) needsServiceWorkerImport = true;
934
+ const resolvedOptions = { ...options, trigger: "__TRIGGER_PLACEHOLDER__" };
935
+ const optionsJson = JSON.stringify(resolvedOptions).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved);
936
+ inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...typeof c[0] === "string" ? { src: c[0] } : c[0] })}, { ...${optionsJson}, use: () => ({ ${k}: window.${k} }) })`);
671
937
  } else {
672
938
  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} }) })`);
673
939
  }
@@ -676,8 +942,10 @@ function templatePlugin(config, registry) {
676
942
  if (triggerResolved) {
677
943
  if (triggerResolved.includes("useScriptTriggerIdleTimeout")) needsIdleTimeoutImport = true;
678
944
  if (triggerResolved.includes("useScriptTriggerInteraction")) needsInteractionImport = true;
679
- const resolvedOptions = { ...c, trigger: `__TRIGGER_${triggerResolved}__` };
680
- inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...resolvedOptions }).replace(/"__TRIGGER_(.*?)__"/g, "$1")}, { use: () => ({ ${k}: window.${k} }) })`);
945
+ if (triggerResolved.includes("useScriptTriggerServiceWorker")) needsServiceWorkerImport = true;
946
+ const resolvedOptions = { ...c, trigger: "__TRIGGER_PLACEHOLDER__" };
947
+ const argsJson = JSON.stringify({ key: k, ...resolvedOptions }).replace(/"__TRIGGER_PLACEHOLDER__"/g, triggerResolved);
948
+ inits.push(`const ${k} = useScript(${argsJson}, { use: () => ({ ${k}: window.${k} }) })`);
681
949
  } else {
682
950
  inits.push(`const ${k} = useScript(${JSON.stringify({ key: k, ...c })}, { use: () => ({ ${k}: window.${k} }) })`);
683
951
  }
@@ -690,6 +958,9 @@ function templatePlugin(config, registry) {
690
958
  if (needsInteractionImport) {
691
959
  triggerImports.push(`import { useScriptTriggerInteraction } from '#nuxt-scripts/composables/useScriptTriggerInteraction'`);
692
960
  }
961
+ if (needsServiceWorkerImport) {
962
+ triggerImports.push(`import { useScriptTriggerServiceWorker } from '#nuxt-scripts/composables/useScriptTriggerServiceWorker'`);
963
+ }
693
964
  return [
694
965
  `import { useScript } from '#nuxt-scripts/composables/useScript'`,
695
966
  `import { defineNuxtPlugin } from 'nuxt/app'`,
@@ -702,12 +973,26 @@ function templatePlugin(config, registry) {
702
973
  ` parallel: true,`,
703
974
  ` setup() {`,
704
975
  ...inits.map((i) => ` ${i}`),
705
- ` return { provide: { $scripts: { ${[...Object.keys(config.globals || {}), ...Object.keys(config.registry || {})].join(", ")} } } }`,
976
+ ` return { provide: { $scripts: { ${[...Object.keys(config.globals || {}), ...resolvedRegistryKeys].join(", ")} } } }`,
706
977
  ` }`,
707
978
  `})`
708
979
  ].join("\n");
709
980
  }
710
981
 
982
+ const PARTYTOWN_FORWARDS = {
983
+ googleAnalytics: ["dataLayer.push", "gtag"],
984
+ plausible: ["plausible"],
985
+ fathom: ["fathom", "fathom.trackEvent", "fathom.trackPageview"],
986
+ umami: ["umami", "umami.track"],
987
+ matomo: ["_paq.push"],
988
+ segment: ["analytics", "analytics.track", "analytics.page", "analytics.identify"],
989
+ metaPixel: ["fbq"],
990
+ xPixel: ["twq"],
991
+ tiktokPixel: ["ttq.track", "ttq.page", "ttq.identify"],
992
+ snapchatPixel: ["snaptr"],
993
+ redditPixel: ["rdt"],
994
+ cloudflareWebAnalytics: ["__cfBeacon"]
995
+ };
711
996
  const module$1 = defineNuxtModule({
712
997
  meta: {
713
998
  name: "@nuxt/scripts",
@@ -717,6 +1002,7 @@ const module$1 = defineNuxtModule({
717
1002
  }
718
1003
  },
719
1004
  defaults: {
1005
+ firstParty: true,
720
1006
  defaultScriptOptions: {
721
1007
  trigger: "onNuxtReady"
722
1008
  },
@@ -756,7 +1042,8 @@ const module$1 = defineNuxtModule({
756
1042
  nuxt.options.runtimeConfig["nuxt-scripts"] = {
757
1043
  version,
758
1044
  // Private proxy config with API key (server-side only)
759
- googleStaticMapsProxy: config.googleStaticMapsProxy?.enabled ? { apiKey: nuxt.options.runtimeConfig.public.scripts?.googleMaps?.apiKey } : void 0
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")
760
1047
  };
761
1048
  nuxt.options.runtimeConfig.public["nuxt-scripts"] = {
762
1049
  // expose for devtools
@@ -772,13 +1059,57 @@ const module$1 = defineNuxtModule({
772
1059
  config.registry
773
1060
  );
774
1061
  }
1062
+ if (config.defaultScriptOptions?.bundle !== void 0) {
1063
+ logger.warn(
1064
+ "`scripts.defaultScriptOptions.bundle` is deprecated. Use `scripts.firstParty: true` instead. First-party mode is now enabled by default."
1065
+ );
1066
+ }
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";
1075
+ if (config.partytown?.length) {
1076
+ config.registry = config.registry || {};
1077
+ const requiredForwards = [];
1078
+ for (const scriptKey of config.partytown) {
1079
+ const forwards = PARTYTOWN_FORWARDS[scriptKey];
1080
+ if (forwards) {
1081
+ requiredForwards.push(...forwards);
1082
+ } else if (import.meta.dev) {
1083
+ logger.warn(`[partytown] "${scriptKey}" has no known Partytown forwards configured. It may not work correctly or may require manual forward configuration.`);
1084
+ }
1085
+ const reg = config.registry;
1086
+ const existing = reg[scriptKey];
1087
+ if (Array.isArray(existing)) {
1088
+ 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
+ } else {
1094
+ reg[scriptKey] = [{}, { partytown: true }];
1095
+ }
1096
+ }
1097
+ if (requiredForwards.length && hasNuxtModule("@nuxtjs/partytown")) {
1098
+ const partytownConfig = nuxt.options.partytown || {};
1099
+ const existingForwards = partytownConfig.forward || [];
1100
+ const newForwards = [.../* @__PURE__ */ new Set([...existingForwards, ...requiredForwards])];
1101
+ nuxt.options.partytown = { ...partytownConfig, forward: newForwards };
1102
+ logger.info(`[partytown] Auto-configured forwards: ${requiredForwards.join(", ")}`);
1103
+ }
1104
+ }
775
1105
  const composables = [
776
1106
  "useScript",
777
1107
  "useScriptEventPage",
778
1108
  "useScriptTriggerConsent",
779
1109
  "useScriptTriggerElement",
780
1110
  "useScriptTriggerIdleTimeout",
781
- "useScriptTriggerInteraction"
1111
+ "useScriptTriggerInteraction",
1112
+ "useScriptTriggerServiceWorker"
782
1113
  ];
783
1114
  for (const composable of composables) {
784
1115
  addImports({
@@ -798,6 +1129,104 @@ const module$1 = defineNuxtModule({
798
1129
  return templateTriggerResolver(config.defaultScriptOptions);
799
1130
  }
800
1131
  });
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
+ }
801
1230
  const scripts = await registry(resolvePath);
802
1231
  for (const script of scripts) {
803
1232
  if (script.import?.name) {
@@ -825,6 +1254,90 @@ const module$1 = defineNuxtModule({
825
1254
  });
826
1255
  }
827
1256
  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
+ }
1305
+ }
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
+ }
1325
+ }
1326
+ }
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
+ }
828
1341
  const moduleInstallPromises = /* @__PURE__ */ new Map();
829
1342
  addBuildPlugin(NuxtScriptsCheckScripts(), {
830
1343
  dev: true
@@ -832,12 +1345,14 @@ const module$1 = defineNuxtModule({
832
1345
  addBuildPlugin(NuxtScriptBundleTransformer({
833
1346
  scripts: registryScriptsWithImport,
834
1347
  registryConfig: nuxt.options.runtimeConfig.public.scripts,
835
- defaultBundle: config.defaultScriptOptions?.bundle,
1348
+ defaultBundle: firstPartyEnabled || config.defaultScriptOptions?.bundle,
1349
+ firstPartyEnabled,
1350
+ firstPartyCollectPrefix,
836
1351
  moduleDetected(module) {
837
1352
  if (nuxt.options.dev && module !== "@nuxt/scripts" && !moduleInstallPromises.has(module) && !hasNuxtModule(module))
838
1353
  moduleInstallPromises.set(module, () => installNuxtModule(module));
839
1354
  },
840
- assetsBaseURL: config.assets?.prefix,
1355
+ assetsBaseURL: assetsPrefix,
841
1356
  fallbackOnSrcOnBundleFail: config.assets?.fallbackOnSrcOnBundleFail,
842
1357
  fetchOptions: config.assets?.fetchOptions,
843
1358
  cacheMaxAge: config.assets?.cacheMaxAge,
@@ -856,8 +1371,29 @@ const module$1 = defineNuxtModule({
856
1371
  handler: await resolvePath("./runtime/server/google-static-maps-proxy")
857
1372
  });
858
1373
  }
859
- if (nuxt.options.dev)
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
+ });
1394
+ if (nuxt.options.dev) {
860
1395
  setupDevToolsUI(config, resolvePath);
1396
+ }
861
1397
  }
862
1398
  });
863
1399