-
Notifications
You must be signed in to change notification settings - Fork 75
feat: experimental nuxt/partytown support #576
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
- Add `partytown?: boolean` option to useScript - Sets `type="text/partytown"` when enabled for web worker execution - Add `partytown: ['googleAnalytics', ...]` shorthand in module config - Add dev warnings for incompatible scripts (GTM, Hotjar, chat widgets, etc) - Add docs for partytown option and compatibility notes - Add e2e tests with @nuxtjs/partytown integration Closes #182 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add experimental badge to partytown config option - Document incompatible scripts (GTM, Hotjar, chat widgets, etc) - Add general limitations section (DOM access, cookies, debugging) - Update e2e test to verify partytown library integration - Update test fixture to set up forwarded function Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
| it('registry with partytown option', async () => { | ||
| const res = templatePlugin({ | ||
| globals: {}, | ||
| registry: { | ||
| googleAnalytics: [ | ||
| { id: 'G-XXXXX' }, | ||
| { partytown: true }, | ||
| ], | ||
| }, | ||
| }, [ | ||
| { | ||
| import: { | ||
| name: 'useScriptGoogleAnalytics', | ||
| }, | ||
| }, | ||
| ]) | ||
| expect(res).toContain('useScriptGoogleAnalytics([{"id":"G-XXXXX"},{"partytown":true}])') | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test expects incorrect behavior - it verifies that the template generates an array being passed as a single argument to registry scripts, when it should verify that the array is properly unpacked into two arguments.
View Details
π Patch Details
diff --git a/src/templates.ts b/src/templates.ts
index f1b2cd4..7256b64 100644
--- a/src/templates.ts
+++ b/src/templates.ts
@@ -110,21 +110,29 @@ export function templatePlugin(config: Partial<ModuleOptions>, registry: Require
if (importDefinition) {
// title case
imports.unshift(`import { ${importDefinition.import.name} } from '${importDefinition.import.from}'`)
- const args = (typeof c !== 'object' ? {} : c) || {}
+ let args: any
if (c === 'mock') {
- args.scriptOptions = { trigger: 'manual', skipValidation: true }
+ args = { scriptOptions: { trigger: 'manual', skipValidation: true } }
}
- else if (Array.isArray(c) && c.length === 2 && c[1]?.trigger) {
- const triggerResolved = resolveTriggerForTemplate(c[1].trigger)
+ else if (Array.isArray(c) && c.length === 2) {
+ // Unpack array [input, scriptOptions] into merged options
+ const input = c[0]
+ const scriptOptions = c[1]
+ const triggerResolved = resolveTriggerForTemplate(scriptOptions?.trigger)
+
if (triggerResolved) {
- args.scriptOptions = { ...c[1] } as any
- // Store the resolved trigger as a string that will be replaced later
- if (args.scriptOptions) {
- args.scriptOptions.trigger = `__TRIGGER_${triggerResolved}__` as any
- }
if (triggerResolved.includes('useScriptTriggerIdleTimeout')) needsIdleTimeoutImport = true
if (triggerResolved.includes('useScriptTriggerInteraction')) needsInteractionImport = true
+ const resolvedOptions = { ...scriptOptions, trigger: `__TRIGGER_${triggerResolved}__` } as any
+ args = { ...input, ...resolvedOptions }
}
+ else {
+ args = { ...input, ...scriptOptions }
+ }
+ }
+ else {
+ // Single object or other type
+ args = (typeof c !== 'object' ? {} : c) || {}
}
inits.push(`const ${k} = ${importDefinition.import.name}(${JSON.stringify(args).replace(/"__TRIGGER_(.*?)__"/g, '$1')})`)
}
diff --git a/test/unit/templates.test.ts b/test/unit/templates.test.ts
index fe030bb..ef7855b 100644
--- a/test/unit/templates.test.ts
+++ b/test/unit/templates.test.ts
@@ -139,7 +139,7 @@ describe('template plugin file', () => {
},
},
])
- expect(res).toContain('useScriptStripe([{"id":"test"},{"trigger":"onNuxtReady"}])')
+ expect(res).toContain('useScriptStripe({"id":"test","trigger":"onNuxtReady"})')
})
it('registry with partytown option', async () => {
@@ -158,7 +158,7 @@ describe('template plugin file', () => {
},
},
])
- expect(res).toContain('useScriptGoogleAnalytics([{"id":"G-XXXXX"},{"partytown":true}])')
+ expect(res).toContain('useScriptGoogleAnalytics({"id":"G-XXXXX","partytown":true})')
})
// Test idleTimeout trigger in globals
@@ -203,8 +203,10 @@ describe('template plugin file', () => {
},
},
])
- // Registry scripts pass trigger objects directly, they don't resolve triggers in templates
- expect(res).toContain('useScriptGoogleAnalytics([{"id":"GA_MEASUREMENT_ID"},{"trigger":{"idleTimeout":5000}}])')
+ // Registry scripts merge array input and options into a single argument
+ // When trigger resolvers are available, triggers are replaced with function calls
+ expect(res).toContain('useScriptGoogleAnalytics({"id":"GA_MEASUREMENT_ID","trigger":useScriptTriggerIdleTimeout({ timeout: 5000 })})')
+ expect(res).toContain('import { useScriptTriggerIdleTimeout }')
})
// Test both triggers together (should import both)
Analysis
Registry scripts with array format pass broken options to composables
What fails: When a registry script configuration uses array format [input, scriptOptions], the generated template plugin passes the entire array as a single argument instead of merging input and options into a single object.
How to reproduce:
templatePlugin({
registry: {
googleAnalytics: [
{ id: 'G-XXXXX' },
{ partytown: true },
],
},
}, [/* registry */])Result: Generates broken code that passes array as argument:
useScriptGoogleAnalytics([{"id":"G-XXXXX"},{"partytown":true}])The useScriptGoogleAnalytics() function receives an array with numeric keys ('0', '1') instead of a merged options object with properties (id, partytown).
Expected: Should merge input and options into a single argument:
useScriptGoogleAnalytics({"id":"G-XXXXX","partytown":true})Root cause: The template plugin code for registry scripts only handled arrays when c[1]?.trigger existed. Arrays without explicit triggers, or with other properties like partytown, were passed as-is to JSON.stringify(). This differs from the globals handler which properly unpacks arrays via spread syntax.
Fix: Updated src/templates.ts to properly unpack array registry entries [input, scriptOptions] by merging both elements into a single options object, matching the behavior of globals. Also updated corresponding tests in test/unit/templates.test.ts to validate the correct merged output.
| // Process partytown shorthand - add partytown: true to specified registry scripts | ||
| // and auto-configure @nuxtjs/partytown forward array | ||
| if (config.partytown?.length) { | ||
| config.registry = config.registry || {} | ||
| const requiredForwards: string[] = [] | ||
|
|
||
| for (const scriptKey of config.partytown) { | ||
| // Collect required forwards for this script | ||
| const forwards = PARTYTOWN_FORWARDS[scriptKey] | ||
| if (forwards) { | ||
| requiredForwards.push(...forwards) | ||
| } | ||
| else if (import.meta.dev) { | ||
| logger.warn(`[partytown] "${scriptKey}" has no known Partytown forwards configured. It may not work correctly or may require manual forward configuration.`) | ||
| } | ||
|
|
||
| const existing = config.registry[scriptKey] | ||
| if (Array.isArray(existing)) { | ||
| // [input, options] format - merge partytown into options | ||
| existing[1] = { ...existing[1], partytown: true } | ||
| } | ||
| else if (existing && typeof existing === 'object' && existing !== true && existing !== 'mock') { | ||
| // input object format - wrap with partytown option | ||
| config.registry[scriptKey] = [existing, { partytown: true }] as any | ||
| } | ||
| else if (existing === true || existing === 'mock') { | ||
| // simple enable - convert to array with partytown | ||
| config.registry[scriptKey] = [{}, { partytown: true }] as any | ||
| } | ||
| else { | ||
| // not configured - add with partytown enabled | ||
| config.registry[scriptKey] = [{}, { partytown: true }] as any | ||
| } | ||
| } | ||
|
|
||
| // Auto-configure @nuxtjs/partytown forward array | ||
| if (requiredForwards.length && hasNuxtModule('@nuxtjs/partytown')) { | ||
| const partytownConfig = (nuxt.options as any).partytown || {} | ||
| const existingForwards = partytownConfig.forward || [] | ||
| const newForwards = [...new Set([...existingForwards, ...requiredForwards])] | ||
| ;(nuxt.options as any).partytown = { ...partytownConfig, forward: newForwards } | ||
| logger.info(`[partytown] Auto-configured forwards: ${requiredForwards.join(', ')}`) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The partytown feature wraps registry script entries in array format [input, {partytown: true}], but the template plugin doesn't properly handle this array format when generating initialization code, so the partytown option is never actually applied to registry scripts.
View Details
π Patch Details
diff --git a/src/templates.ts b/src/templates.ts
index f1b2cd4..7aa6de6 100644
--- a/src/templates.ts
+++ b/src/templates.ts
@@ -110,20 +110,26 @@ export function templatePlugin(config: Partial<ModuleOptions>, registry: Require
if (importDefinition) {
// title case
imports.unshift(`import { ${importDefinition.import.name} } from '${importDefinition.import.from}'`)
- const args = (typeof c !== 'object' ? {} : c) || {}
+ let args: any = (typeof c !== 'object' ? {} : c) || {}
if (c === 'mock') {
+ args = {}
args.scriptOptions = { trigger: 'manual', skipValidation: true }
}
- else if (Array.isArray(c) && c.length === 2 && c[1]?.trigger) {
- const triggerResolved = resolveTriggerForTemplate(c[1].trigger)
- if (triggerResolved) {
- args.scriptOptions = { ...c[1] } as any
- // Store the resolved trigger as a string that will be replaced later
- if (args.scriptOptions) {
+ else if (Array.isArray(c) && c.length === 2) {
+ // Handle array format [input, options]
+ args = typeof c[0] === 'object' ? c[0] : {}
+ const scriptOptions = c[1]
+ if (scriptOptions) {
+ const triggerResolved = scriptOptions.trigger ? resolveTriggerForTemplate(scriptOptions.trigger) : null
+ if (triggerResolved) {
+ args.scriptOptions = { ...scriptOptions } as any
args.scriptOptions.trigger = `__TRIGGER_${triggerResolved}__` as any
+ if (triggerResolved.includes('useScriptTriggerIdleTimeout')) needsIdleTimeoutImport = true
+ if (triggerResolved.includes('useScriptTriggerInteraction')) needsInteractionImport = true
+ }
+ else {
+ args.scriptOptions = scriptOptions
}
- if (triggerResolved.includes('useScriptTriggerIdleTimeout')) needsIdleTimeoutImport = true
- if (triggerResolved.includes('useScriptTriggerInteraction')) needsInteractionImport = true
}
}
inits.push(`const ${k} = ${importDefinition.import.name}(${JSON.stringify(args).replace(/"__TRIGGER_(.*?)__"/g, '$1')})`)
diff --git a/test/unit/templates.test.ts b/test/unit/templates.test.ts
index fe030bb..be5bf37 100644
--- a/test/unit/templates.test.ts
+++ b/test/unit/templates.test.ts
@@ -139,7 +139,7 @@ describe('template plugin file', () => {
},
},
])
- expect(res).toContain('useScriptStripe([{"id":"test"},{"trigger":"onNuxtReady"}])')
+ expect(res).toContain('useScriptStripe({"id":"test","scriptOptions":{"trigger":"onNuxtReady"}})')
})
it('registry with partytown option', async () => {
@@ -158,7 +158,7 @@ describe('template plugin file', () => {
},
},
])
- expect(res).toContain('useScriptGoogleAnalytics([{"id":"G-XXXXX"},{"partytown":true}])')
+ expect(res).toContain('useScriptGoogleAnalytics({"id":"G-XXXXX","scriptOptions":{"partytown":true}})')
})
// Test idleTimeout trigger in globals
@@ -203,8 +203,9 @@ describe('template plugin file', () => {
},
},
])
- // Registry scripts pass trigger objects directly, they don't resolve triggers in templates
- expect(res).toContain('useScriptGoogleAnalytics([{"id":"GA_MEASUREMENT_ID"},{"trigger":{"idleTimeout":5000}}])')
+ // Registry scripts now properly handle triggers in scriptOptions, including resolving idleTimeout/interaction triggers
+ expect(res).toContain('useScriptTriggerIdleTimeout({ timeout: 5000 })')
+ expect(res).toContain('useScriptGoogleAnalytics({"id":"GA_MEASUREMENT_ID","scriptOptions":{"trigger":useScriptTriggerIdleTimeout({ timeout: 5000 })}}')
})
// Test both triggers together (should import both)
Analysis
Registry scripts with partytown option not properly handled in template generation
What fails: When using scripts.partytown: ['googleAnalytics'] to enable partytown for registry scripts, the generated template code doesn't properly unpack the array format [input, {partytown: true}], causing the partytown option to never reach useScript. Scripts tagged with partytown receive normal script tags instead of type="text/partytown" and run on the main thread instead of in a web worker.
How to reproduce:
- Set config in
nuxt.config.ts:
export default defineNuxtConfig({
scripts: {
partytown: ['googleAnalytics'],
registry: {
googleAnalytics: { id: 'G-XXXXX' }
}
}
})- Build/generate the project
- Inspect the generated template in
.nuxt/plugins/scripts:init.ts
Result: The generated code contains:
const googleAnalytics = useScriptGoogleAnalytics([{id:"G-XXXXX"},{partytown:true}])This passes the entire array as a single options parameter, with partytown buried at array index [1].partytown, making it inaccessible to the registry function.
Expected: The partytown option should be unpacked into scriptOptions:
)Root cause: The template plugin in src/templates.ts (lines 108-131) only checked for c[1]?.trigger when handling array format for registry scripts. When partytown is the only option in the second element, the condition failed and the entire array was treated as a single parameter, unlike how globals handle the same array format correctly (lines 136-147).
Fix: Updated registry script handling in src/templates.ts to properly unpack array format [input, options] regardless of whether options contain a trigger, similar to the existing globals handling. The second element is now correctly placed in scriptOptions for all array-formatted registry entries.
- Disable warmupStrategy for partytown scripts (conflicts with partytown loading) - Update test fixture to use useHead for SSR script rendering - Simplify e2e tests to verify console log output and script type - Partytown changes type to "text/partytown-x" after processing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When partytown: true, bypass normal script loading and use useHead directly to ensure script is rendered in SSR HTML with type="text/partytown". Returns minimal stub since partytown handles execution in worker. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Warning Rate limit exceeded
β How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. π¦ How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. π WalkthroughWalkthroughAdds Partytown support to Nuxt Scripts. Introduces a new Estimated code review effortπ― 4 (Complex) | β±οΈ ~45 minutes π₯ Pre-merge checks | β 4 | β 1β Failed checks (1 warning)
β Passed checks (4 passed)
βοΈ Tip: You can configure your own custom pre-merge checks in the settings. β¨ Finishing touchesπ§ͺ Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and canβt be posted inline due to platform limitations.
β οΈ Outside diff range comments (1)
src/module.ts (1)
203-258: Keep runtimeConfig in sync after Partytown shorthand mutates the registry.The runtimeConfig merge runs before
config.partytownmutates/createsconfig.registry. If a user only setsscripts.partytown(or if existing entries get wrapped),runtimeConfig.public.scriptscan be stale, which may desync downstream consumers (e.g., bundle transformer). Re-merge after Partytown changes or move the merge block below.β Suggested fix (re-sync after Partytown processing)
@@ if (config.partytown?.length) { config.registry = config.registry || {} const requiredForwards: string[] = [] @@ if (requiredForwards.length && hasNuxtModule('@nuxtjs/partytown')) { const partytownConfig = (nuxt.options as any).partytown || {} const existingForwards = partytownConfig.forward || [] const newForwards = [...new Set([...existingForwards, ...requiredForwards])] ;(nuxt.options as any).partytown = { ...partytownConfig, forward: newForwards } logger.info(`[partytown] Auto-configured forwards: ${requiredForwards.join(', ')}`) } } + + // Re-sync runtimeConfig after Partytown shorthand updates registry + if (config.partytown?.length && config.registry) { + nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {} + nuxt.options.runtimeConfig.public.scripts = defu( + nuxt.options.runtimeConfig.public.scripts || {}, + config.registry, + ) + }
π€ Fix all issues with AI agents
In `@src/runtime/composables/useScript.ts`:
- Around line 25-43: The partytown early-return stub in useScript currently
returns status as a plain string; change it to a reactive Ref (e.g., use Vue's
ref('loaded')) so the returned
UseScriptContext<UseFunctionType<NuxtUseScriptOptions<T>, T>> exposes
status.value correctly, and also register the stub entry with nuxtApp.$scripts
(push an appropriate record or call the same registration helper used for
non-partytown scripts) so DevTools sees the script; ensure you import/ref from
Vue if not already and update the returned object to use the ref instance for
the status field and add the registration step referencing useScript,
UseScriptContext, and nuxtApp.$scripts.
In `@test/e2e/partytown.test.ts`:
- Around line 44-48: The page.evaluate snippet that builds partytownLib can
throw when s.src is null for inline scripts; update the predicate used inside
Array.from(document.querySelectorAll('script')) (the arrow function that checks
s.id === 'partytown' || s.src.includes('partytown')) to guard s.src before
calling includes (e.g., check s.src exists or use optional chaining) so inline
scripts with null src won't cause an exception when evaluating partytownLib.
β»οΈ Duplicate comments (1)
test/unit/templates.test.ts (1)
145-162: Align registry array expectation with merged options (if thatβs the intended API).Line 161 currently asserts an array argument; this locks in the same behavior previously flagged for registry arrays. If the registry shorthand is meant to merge input + options into one argument (as globals do), adjust the expectation.
Potential adjustment
- expect(res).toContain('useScriptGoogleAnalytics([{"id":"G-XXXXX"},{"partytown":true}])') + expect(res).toContain('useScriptGoogleAnalytics({"id":"G-XXXXX","partytown":true})')
π§Ή Nitpick comments (4)
test/e2e/basic.test.ts (1)
183-191: Prefer waiting for the script element instead of fixed sleeps.Line 185 uses a fixed timeout; this can be flaky under slower CI. Waiting for the script tag makes the assertion deterministic.
Proposed change
- await page.waitForTimeout(500) - // verify the script tag has type="text/partytown" - const scriptType = await page.evaluate(() => { - const script = document.querySelector('script[src="/myScript.js"]') - return script?.getAttribute('type') - }) + await page.waitForSelector('script[src="/myScript.js"]') + // verify the script tag has type="text/partytown" + const scriptType = await page.$eval( + 'script[src="/myScript.js"]', + el => el.getAttribute('type'), + )src/runtime/composables/useScript.ts (1)
32-34: Consider passing through additional script attributes.Only
srcandtypeare passed touseHead. If users specify attributes likecrossorigin,referrerpolicy, or customattributesin the input, they're silently ignored for Partytown scripts.Optional enhancement
useHead({ - script: [{ src, type: 'text/partytown' }], + script: [{ + ...input, + type: 'text/partytown', + }], })docs/content/docs/3.api/1.use-script.md (1)
74-81: Documentation looks good; consider notingsrcrequirement.The documentation clearly explains the experimental nature and limitations. One minor addition: since the implementation throws an error when
partytown: trueis used without asrc, it would be helpful to explicitly document this requirement.Suggested addition
* [Experimental] Load the script in a web worker using Partytown. * When enabled, adds `type="text/partytown"` to the script tag. * Requires `@nuxtjs/partytown` to be installed and configured separately. + * Requires a `src` attribute - inline scripts are not supported. * Note: Scripts requiring DOM access (GTM, Hotjar, chat widgets) are not compatible.test/e2e/partytown.test.ts (1)
40-41: Replace hardcoded timeout with condition polling to reduce flakiness.
waitForTimeout(1000)is fragileβslower CI environments may need longer, faster ones waste time. Polling for the expected condition is more reliable.Proposed fix
- // Wait for partytown to execute scripts - await page.waitForTimeout(1000) + // Wait for partytown to execute scripts (poll for console output) + await page.waitForFunction( + () => (window as any).__partytownWorkerRan === true, + { timeout: 5000 } + )This assumes the worker script sets a flag. Alternatively, poll until the console log appears or use
page.waitForEvent('console', ...).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
π€ Fix all issues with AI agents
In `@src/templates.ts`:
- Around line 116-127: The code embeds triggerResolved into
scriptOptions.trigger as a templated value which then gets JSON.stringify'd,
producing escaped quotes when triggerResolved contains JSON (e.g.,
trigger.interaction), so change the approach: set scriptOptions.trigger to a
fixed placeholder token (e.g., "__TRIGGER_PLACEHOLDER__") instead of
`__TRIGGER_${triggerResolved}__`, keep the import flags
(needsIdleTimeoutImport/needsInteractionImport) based on triggerResolved as you
already do, then after building the JSON string for args in the inits.push call,
replace the quoted placeholder string with the raw triggerResolved expression
(i.e., JSON.stringify(args).replace(/"__TRIGGER_PLACEHOLDER__"/g,
triggerResolved)) so the final emitted JS injects the unescaped trigger object;
update the line that sets scriptOptions.trigger and the replacement in the
inits.push accordingly (refer to resolveTriggerForTemplate,
needsIdleTimeoutImport, needsInteractionImport, and the inits.push constructing
const ${k} = ${importDefinition.import.name}(...)).
| else if (Array.isArray(c) && c.length === 2) { | ||
| // [input, options] format - unpack properly | ||
| const input = c[0] || {} | ||
| const scriptOptions = { ...c[1] } | ||
| const triggerResolved = resolveTriggerForTemplate(scriptOptions?.trigger) | ||
| if (triggerResolved) { | ||
| args.scriptOptions = { ...c[1] } as any | ||
| // Store the resolved trigger as a string that will be replaced later | ||
| if (args.scriptOptions) { | ||
| args.scriptOptions.trigger = `__TRIGGER_${triggerResolved}__` as any | ||
| } | ||
| scriptOptions.trigger = `__TRIGGER_${triggerResolved}__` as any | ||
| if (triggerResolved.includes('useScriptTriggerIdleTimeout')) needsIdleTimeoutImport = true | ||
| if (triggerResolved.includes('useScriptTriggerInteraction')) needsInteractionImport = true | ||
| } | ||
| const args = { ...input, scriptOptions } | ||
| inits.push(`const ${k} = ${importDefinition.import.name}(${JSON.stringify(args).replace(/"__TRIGGER_(.*?)__"/g, '$1')})`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid escaped quotes in trigger placeholder serialization.
When trigger.interaction is used, resolveTriggerForTemplate returns a string containing JSON with double quotes. Embedding that in the placeholder and then JSON.stringify causes escaped quotes (\") to remain after replacement, producing invalid JS (e.g., events: [\"click\"]). Use a fixed placeholder and inject the raw expression after stringifying.
π οΈ Suggested fix
- const scriptOptions = { ...c[1] }
- const triggerResolved = resolveTriggerForTemplate(scriptOptions?.trigger)
+ const scriptOptions = { ...c[1] }
+ let triggerResolved: string | null = null
+ let triggerPlaceholder: string | null = null
+ triggerResolved = resolveTriggerForTemplate(scriptOptions?.trigger)
if (triggerResolved) {
- scriptOptions.trigger = `__TRIGGER_${triggerResolved}__` as any
+ triggerPlaceholder = '__TRIGGER__'
+ scriptOptions.trigger = triggerPlaceholder as any
if (triggerResolved.includes('useScriptTriggerIdleTimeout')) needsIdleTimeoutImport = true
if (triggerResolved.includes('useScriptTriggerInteraction')) needsInteractionImport = true
}
const args = { ...input, scriptOptions }
- inits.push(`const ${k} = ${importDefinition.import.name}(${JSON.stringify(args).replace(/"__TRIGGER_(.*?)__"/g, '$1')})`)
+ let argsString = JSON.stringify(args)
+ if (triggerResolved && triggerPlaceholder) {
+ argsString = argsString.replace(`"${triggerPlaceholder}"`, triggerResolved)
+ }
+ inits.push(`const ${k} = ${importDefinition.import.name}(${argsString})`)π€ Prompt for AI Agents
In `@src/templates.ts` around lines 116 - 127, The code embeds triggerResolved
into scriptOptions.trigger as a templated value which then gets
JSON.stringify'd, producing escaped quotes when triggerResolved contains JSON
(e.g., trigger.interaction), so change the approach: set scriptOptions.trigger
to a fixed placeholder token (e.g., "__TRIGGER_PLACEHOLDER__") instead of
`__TRIGGER_${triggerResolved}__`, keep the import flags
(needsIdleTimeoutImport/needsInteractionImport) based on triggerResolved as you
already do, then after building the JSON string for args in the inits.push call,
replace the quoted placeholder string with the raw triggerResolved expression
(i.e., JSON.stringify(args).replace(/"__TRIGGER_PLACEHOLDER__"/g,
triggerResolved)) so the final emitted JS injects the unescaped trigger object;
update the line that sets scriptOptions.trigger and the replacement in the
inits.push accordingly (refer to resolveTriggerForTemplate,
needsIdleTimeoutImport, needsInteractionImport, and the inits.push constructing
const ${k} = ${importDefinition.import.name}(...)).
π Linked issue
Resolves #182
β Type of change
π Description
Add Partytown web worker support for loading third-party scripts off the main thread.
Features:
partytown: trueoption onuseScript- setstype="text/partytown"scripts.partytown: ['googleAnalytics', 'plausible']shorthand in nuxt.config@nuxtjs/partytownforward array for supported scriptsSupported scripts with auto-forwarding:
googleAnalytics,plausible,fathom,umami,matomo,segment,metaPixel,xPixel,tiktokPixel,snapchatPixel,redditPixel,cloudflareWebAnalyticsUsage:
Warning
Google Analytics 4 - GA4 has known issues with Partytown. The
navigator.sendBeaconandfetchAPIs used for collect requests don't work reliably from web workers. Consider using Plausible, Fathom, or Umami instead.Caution
Google Tag Manager - GTM is not compatible with Partytown. GTM dynamically injects scripts and requires full DOM access. Load GTM on main thread instead.
Incompatible scripts (require DOM access):
Recommended for Partytown: Plausible, Fathom, Umami, Matomo, Cloudflare Web Analytics