From 842f32f7cab3c57edbb93e5eaa35ac622786bc4a Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Fri, 16 Jan 2026 02:30:09 +1100 Subject: [PATCH 1/7] feat: add partytown web worker support - 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 --- docs/content/docs/3.api/1.use-script.md | 7 ++ docs/content/docs/3.api/5.nuxt-config.md | 34 +++++++++ package.json | 3 +- pnpm-lock.yaml | 26 +++++++ src/module.ts | 71 +++++++++++++++++++ src/runtime/composables/useScript.ts | 5 ++ src/runtime/types.ts | 7 ++ test/e2e/basic.test.ts | 10 +++ test/e2e/partytown.test.ts | 42 +++++++++++ test/fixtures/basic/pages/partytown.vue | 9 +++ test/fixtures/partytown/app.vue | 3 + test/fixtures/partytown/nuxt.config.ts | 14 ++++ test/fixtures/partytown/package.json | 5 ++ test/fixtures/partytown/pages/index.vue | 10 +++ .../partytown/public/worker-script.js | 4 ++ test/fixtures/partytown/tsconfig.json | 3 + test/unit/templates.test.ts | 19 +++++ 17 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 test/e2e/partytown.test.ts create mode 100644 test/fixtures/basic/pages/partytown.vue create mode 100644 test/fixtures/partytown/app.vue create mode 100644 test/fixtures/partytown/nuxt.config.ts create mode 100644 test/fixtures/partytown/package.json create mode 100644 test/fixtures/partytown/pages/index.vue create mode 100644 test/fixtures/partytown/public/worker-script.js create mode 100644 test/fixtures/partytown/tsconfig.json diff --git a/docs/content/docs/3.api/1.use-script.md b/docs/content/docs/3.api/1.use-script.md index 3eb29008..b69d5c30 100644 --- a/docs/content/docs/3.api/1.use-script.md +++ b/docs/content/docs/3.api/1.use-script.md @@ -67,6 +67,13 @@ export type NuxtUseScriptOptions = Omit, 'trigger'> * - `false` - Do not bundle the script. (default) */ bundle?: boolean + /** + * 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. + * @see https://partytown.qwik.dev/ + */ + partytown?: boolean /** * Skip any schema validation for the script input. This is useful for loading the script stubs for development without * loading the actual script and not getting warnings. diff --git a/docs/content/docs/3.api/5.nuxt-config.md b/docs/content/docs/3.api/5.nuxt-config.md index 0ee5beca..483de8c3 100644 --- a/docs/content/docs/3.api/5.nuxt-config.md +++ b/docs/content/docs/3.api/5.nuxt-config.md @@ -11,6 +11,40 @@ Global registry scripts that should be loaded. See the [Script Registry](/scripts) for more details. +## `partytown` + +- Type: `(keyof ScriptRegistry)[]` +- Default: `[]` + +Registry scripts to load via [Partytown](https://partytown.qwik.dev/) (web worker). + +This is a shorthand for setting `partytown: true` on individual registry scripts. When a script is listed here, it will be loaded with `type="text/partytown"` so Partytown can execute it in a web worker. + +::code-group +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + partytown: ['googleAnalytics', 'plausible', 'fathom'], + registry: { + googleAnalytics: { id: 'G-XXXXX' }, + plausible: { domain: 'example.com' }, + fathom: { site: 'XXXXX' } + } + } +}) +``` +:: + +::callout{icon="i-heroicons-exclamation-triangle" color="amber"} +Requires [`@nuxtjs/partytown`](https://github.com/nuxt-modules/partytown) to be installed. The `forward` array is **automatically configured** for supported registry scripts. +:: + +::callout{icon="i-heroicons-information-circle" color="blue"} +**Supported scripts with auto-forwarding**: `googleAnalytics`, `plausible`, `fathom`, `umami`, `matomo`, `segment`, `metaPixel`, `xPixel`, `tiktokPixel`, `snapchatPixel`, `redditPixel`, `cloudflareWebAnalytics` + +**Unsupported scripts**: Scripts that require DOM access (GTM, Hotjar, Clarity, chat widgets, video players) are not supported. A warning will be shown if you try to use Partytown with an unsupported script. +:: + ## `defaultScriptOptions` - Type: `NuxtUseScriptOptions` diff --git a/package.json b/package.json index b3e5c291..557e6ac1 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "prepack": "pnpm dev:prepare && nuxt-module-build build && npm run client:build", "dev": "nuxi dev playground", "dev:ssl": "nuxi dev playground --https", - "prepare:fixtures": "nuxi prepare test/fixtures/basic && nuxi prepare test/fixtures/cdn && nuxi prepare test/fixtures/extend-registry", + "prepare:fixtures": "nuxi prepare test/fixtures/basic && nuxi prepare test/fixtures/cdn && nuxi prepare test/fixtures/extend-registry && nuxi prepare test/fixtures/partytown", "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground && pnpm run prepare:fixtures", "typecheck": "vue-tsc --noEmit", "bump": "bumpp package.json --commit --push --tag", @@ -124,6 +124,7 @@ "valibot": "^1.2.0" }, "devDependencies": { + "@nuxtjs/partytown": "^2.0.0", "@nuxt/devtools-kit": "^3.1.1", "@nuxt/devtools-ui-kit": "^3.1.1", "@nuxt/eslint-config": "^1.12.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20189f6f..cea3ed4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,6 +96,9 @@ importers: '@nuxt/test-utils': specifier: 3.19.2 version: 3.19.2(@vue/test-utils@2.4.6)(happy-dom@20.1.0)(magicast@0.5.1)(playwright-core@1.57.0)(typescript@5.9.3)(vitest@4.0.17(@opentelemetry/api@1.9.0)(@types/node@25.0.8)(happy-dom@20.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(yaml@2.8.2)) + '@nuxtjs/partytown': + specifier: ^2.0.0 + version: 2.0.0(magicast@0.5.1) '@paypal/paypal-js': specifier: ^9.1.0 version: 9.2.0 @@ -1373,6 +1376,9 @@ packages: '@nuxtjs/mdc@0.19.2': resolution: {integrity: sha512-mtwBb9D5U7H1R3kpqEmqwML1RudN6qOJqJwebrqLxk+EWhtGUXAdUBXC2L/kPWiCNA4Yz/EO+tSfSQV8Idh5nw==} + '@nuxtjs/partytown@2.0.0': + resolution: {integrity: sha512-G/TTHTpj60LCXX9gbp3RpMRsSBL9BquPZl12zBoCAzT/XQdwKu74k5L+DFD83nkbrIjUBygW0UnGDwVlD17fmA==} + '@nuxtjs/robots@5.6.7': resolution: {integrity: sha512-0Ex0Y38z9s5I5w3AkdlrpLPACE+cd38KH9dYjZE+flP5RSGMvKzbTc8/jyNeY7numIuFt9BQGXEzkEAmxe04Cg==} peerDependencies: @@ -1884,6 +1890,11 @@ packages: '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + '@qwik.dev/partytown@0.11.2': + resolution: {integrity: sha512-795y49CqBiKiwKAD+QBZlzlqEK275hVcazZ7wBPSfgC23L+vWuA7PJmMpgxojOucZHzYi5rAAQ+IP1I3BKVZxw==} + engines: {node: '>=18.0.0'} + hasBin: true + '@remirror/core-constants@3.0.0': resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} @@ -9450,6 +9461,17 @@ snapshots: - magicast - supports-color + '@nuxtjs/partytown@2.0.0(magicast@0.5.1)': + dependencies: + '@nuxt/kit': 4.2.2(magicast@0.5.1) + '@qwik.dev/partytown': 0.11.2 + knitwork: 1.3.0 + serve-static: 2.2.1 + ufo: 1.6.2 + transitivePeerDependencies: + - magicast + - supports-color + '@nuxtjs/robots@5.6.7(magicast@0.5.1)(vue@3.5.26(typescript@5.9.3))(zod@3.25.76)': dependencies: '@fingerprintjs/botd': 2.0.0 @@ -9875,6 +9897,10 @@ snapshots: dependencies: quansync: 1.0.0 + '@qwik.dev/partytown@0.11.2': + dependencies: + dotenv: 16.6.1 + '@remirror/core-constants@3.0.0': {} '@resvg/resvg-js-android-arm-eabi@2.6.2': diff --git a/src/module.ts b/src/module.ts index dd8acff1..49e01f3f 100644 --- a/src/module.ts +++ b/src/module.ts @@ -27,11 +27,37 @@ import type { import { NuxtScriptsCheckScripts } from './plugins/check-scripts' import { registerTypeTemplates, templatePlugin, templateTriggerResolver } from './templates' +/** + * Partytown forward config for registry scripts. + * Scripts not listed here are likely incompatible due to DOM access requirements. + * @see https://partytown.qwik.dev/forwarding-events + */ +const PARTYTOWN_FORWARDS: Record = { + googleAnalytics: ['dataLayer.push', 'gtag'], + plausible: ['plausible'], + fathom: ['fathom', 'fathom.trackEvent', 'fathom.trackPageview'], + umami: ['umami', 'umami.track'], + matomo: ['_paq.push'], + segment: ['analytics', 'analytics.track', 'analytics.page', 'analytics.identify'], + metaPixel: ['fbq'], + xPixel: ['twq'], + tiktokPixel: ['ttq.track', 'ttq.page', 'ttq.identify'], + snapchatPixel: ['snaptr'], + redditPixel: ['rdt'], + cloudflareWebAnalytics: ['__cfBeacon'], +} + export interface ModuleOptions { /** * The registry of supported third-party scripts. Loads the scripts in globally using the default script options. */ registry?: NuxtConfigScriptRegistry + /** + * Registry scripts to load via Partytown (web worker). + * Shorthand for setting `partytown: true` on individual registry scripts. + * @example ['googleAnalytics', 'plausible', 'fathom'] + */ + partytown?: (keyof NuxtConfigScriptRegistry)[] /** * Default options for scripts. */ @@ -147,6 +173,51 @@ export default defineNuxtModule({ ) } + // 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(', ')}`) + } + } + const composables = [ 'useScript', 'useScriptEventPage', diff --git a/src/runtime/composables/useScript.ts b/src/runtime/composables/useScript.ts index c64e71ee..a54f6f99 100644 --- a/src/runtime/composables/useScript.ts +++ b/src/runtime/composables/useScript.ts @@ -22,6 +22,11 @@ export function useScript = Record + // Partytown support: add type="text/partytown" for web worker execution + if (options.partytown) { + input = { ...input, type: 'text/partytown' } + } + // Warn about unsupported bundling for dynamic sources (internal value set by transform) if (import.meta.dev && (options.bundle as any) === 'unsupported') { console.warn('[Nuxt Scripts] Bundling is not supported for dynamic script sources. Static URLs are required for bundling.') diff --git a/src/runtime/types.ts b/src/runtime/types.ts index ba941353..c7bf0b4e 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -59,6 +59,13 @@ export type NuxtUseScriptOptions = {}> = * Note: Using 'force' may significantly increase build time as scripts will be re-downloaded on every build. */ bundle?: boolean | 'force' + /** + * 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. + * @see https://partytown.qwik.dev/ + */ + partytown?: boolean /** * Skip any schema validation for the script input. This is useful for loading the script stubs for development without * loading the actual script and not getting warnings. diff --git a/test/e2e/basic.test.ts b/test/e2e/basic.test.ts index 50437349..99e11f74 100644 --- a/test/e2e/basic.test.ts +++ b/test/e2e/basic.test.ts @@ -151,6 +151,16 @@ describe('basic', () => { const text = await page.$eval('#script-src', el => el.textContent) expect(text).toMatchInlineSnapshot(`"/_scripts/6bEy8slcRmYcRT4E2QbQZ1CMyWw9PpHA7L87BtvSs2U.js"`) }) + it('partytown adds type attribute', async () => { + const { page } = await createPage('/partytown') + 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') + }) + expect(scriptType).toBe('text/partytown') + }) }) describe('third-party-capital', () => { diff --git a/test/e2e/partytown.test.ts b/test/e2e/partytown.test.ts new file mode 100644 index 00000000..49b362a8 --- /dev/null +++ b/test/e2e/partytown.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from 'vitest' +import { createResolver } from '@nuxt/kit' +import { getBrowser, url, waitForHydration, setup } from '@nuxt/test-utils/e2e' + +const { resolve } = createResolver(import.meta.url) + +await setup({ + rootDir: resolve('../fixtures/partytown'), + browser: true, +}) + +describe('partytown integration', () => { + it('script tag has type="text/partytown" when partytown option is enabled', async () => { + const browser = await getBrowser() + const page = await browser.newPage() + + await page.goto(url('/'), { waitUntil: 'networkidle' }) + await waitForHydration(page, '/') + + // Verify our module correctly sets the type attribute for partytown + const scriptType = await page.evaluate(() => { + const script = document.querySelector('script[src="/worker-script.js"]') + return script?.getAttribute('type') + }) + expect(scriptType).toBe('text/partytown') + }) + + it('partytown config is initialized with forward array', async () => { + const browser = await getBrowser() + const page = await browser.newPage() + + await page.goto(url('/'), { waitUntil: 'networkidle' }) + await waitForHydration(page, '/') + + // Verify partytown module sets up config with our forwarded function + const partytownConfig = await page.evaluate(() => { + return (window as any).partytown + }) + expect(partytownConfig).toBeDefined() + expect(partytownConfig.forward).toContain('testFn') + }) +}) diff --git a/test/fixtures/basic/pages/partytown.vue b/test/fixtures/basic/pages/partytown.vue new file mode 100644 index 00000000..b61885ca --- /dev/null +++ b/test/fixtures/basic/pages/partytown.vue @@ -0,0 +1,9 @@ + + + diff --git a/test/fixtures/partytown/app.vue b/test/fixtures/partytown/app.vue new file mode 100644 index 00000000..8f62b8bf --- /dev/null +++ b/test/fixtures/partytown/app.vue @@ -0,0 +1,3 @@ + diff --git a/test/fixtures/partytown/nuxt.config.ts b/test/fixtures/partytown/nuxt.config.ts new file mode 100644 index 00000000..75e20979 --- /dev/null +++ b/test/fixtures/partytown/nuxt.config.ts @@ -0,0 +1,14 @@ +import { defineNuxtConfig } from 'nuxt/config' + +export default defineNuxtConfig({ + modules: [ + '@nuxtjs/partytown', + '@nuxt/scripts', + ], + + compatibilityDate: '2024-07-05', + + partytown: { + forward: ['testFn'], + }, +}) diff --git a/test/fixtures/partytown/package.json b/test/fixtures/partytown/package.json new file mode 100644 index 00000000..24ecd416 --- /dev/null +++ b/test/fixtures/partytown/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "@nuxtjs/partytown": "^2.0.0" + } +} diff --git a/test/fixtures/partytown/pages/index.vue b/test/fixtures/partytown/pages/index.vue new file mode 100644 index 00000000..2d603e53 --- /dev/null +++ b/test/fixtures/partytown/pages/index.vue @@ -0,0 +1,10 @@ + + + diff --git a/test/fixtures/partytown/public/worker-script.js b/test/fixtures/partytown/public/worker-script.js new file mode 100644 index 00000000..33eedc00 --- /dev/null +++ b/test/fixtures/partytown/public/worker-script.js @@ -0,0 +1,4 @@ +// This script runs in a web worker via Partytown +// It calls a forwarded function to communicate back to main thread +console.log('Partytown script executing in worker') +window.testFn('partytown-executed') diff --git a/test/fixtures/partytown/tsconfig.json b/test/fixtures/partytown/tsconfig.json new file mode 100644 index 00000000..4b34df15 --- /dev/null +++ b/test/fixtures/partytown/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./.nuxt/tsconfig.json" +} diff --git a/test/unit/templates.test.ts b/test/unit/templates.test.ts index 5ea90c36..fe030bbf 100644 --- a/test/unit/templates.test.ts +++ b/test/unit/templates.test.ts @@ -142,6 +142,25 @@ describe('template plugin file', () => { expect(res).toContain('useScriptStripe([{"id":"test"},{"trigger":"onNuxtReady"}])') }) + 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}])') + }) + // Test idleTimeout trigger in globals it('global with idleTimeout trigger', async () => { const res = templatePlugin({ From d3c0c972865707d4d33fb6db237fa4ddd7f9c877 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 20 Jan 2026 18:25:39 +1100 Subject: [PATCH 2/7] doc: mark partytown as experimental with limitations - 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 --- docs/content/docs/3.api/1.use-script.md | 3 ++- docs/content/docs/3.api/5.nuxt-config.md | 29 ++++++++++++++++++++---- test/e2e/partytown.test.ts | 23 +++++++++++++++++++ test/fixtures/partytown/pages/index.vue | 10 ++++++++ 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/docs/content/docs/3.api/1.use-script.md b/docs/content/docs/3.api/1.use-script.md index b69d5c30..b9766d46 100644 --- a/docs/content/docs/3.api/1.use-script.md +++ b/docs/content/docs/3.api/1.use-script.md @@ -68,9 +68,10 @@ export type NuxtUseScriptOptions = Omit, 'trigger'> */ bundle?: boolean /** - * Load the script in a web worker using Partytown. + * [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. + * Note: Scripts requiring DOM access (GTM, Hotjar, chat widgets) are not compatible. * @see https://partytown.qwik.dev/ */ partytown?: boolean diff --git a/docs/content/docs/3.api/5.nuxt-config.md b/docs/content/docs/3.api/5.nuxt-config.md index 483de8c3..60b7e50b 100644 --- a/docs/content/docs/3.api/5.nuxt-config.md +++ b/docs/content/docs/3.api/5.nuxt-config.md @@ -11,7 +11,7 @@ Global registry scripts that should be loaded. See the [Script Registry](/scripts) for more details. -## `partytown` +## `partytown` :badge[Experimental]{color="amber"} - Type: `(keyof ScriptRegistry)[]` - Default: `[]` @@ -39,10 +39,31 @@ export default defineNuxtConfig({ Requires [`@nuxtjs/partytown`](https://github.com/nuxt-modules/partytown) to be installed. The `forward` array is **automatically configured** for supported registry scripts. :: -::callout{icon="i-heroicons-information-circle" color="blue"} -**Supported scripts with auto-forwarding**: `googleAnalytics`, `plausible`, `fathom`, `umami`, `matomo`, `segment`, `metaPixel`, `xPixel`, `tiktokPixel`, `snapchatPixel`, `redditPixel`, `cloudflareWebAnalytics` +### Supported Scripts -**Unsupported scripts**: Scripts that require DOM access (GTM, Hotjar, Clarity, chat widgets, video players) are not supported. A warning will be shown if you try to use Partytown with an unsupported script. +Scripts with auto-forwarding configured: +- `googleAnalytics`, `plausible`, `fathom`, `umami`, `matomo`, `segment` +- `metaPixel`, `xPixel`, `tiktokPixel`, `snapchatPixel`, `redditPixel` +- `cloudflareWebAnalytics` + +### Limitations + +::callout{icon="i-heroicons-x-circle" color="red"} +**Incompatible scripts** - The following cannot work with Partytown due to DOM access requirements: +- **Tag managers**: Google Tag Manager, Adobe Launch +- **Session replay**: Hotjar, Clarity, FullStory, LogRocket +- **Chat widgets**: Intercom, Crisp, Drift, HubSpot Chat +- **Video players**: YouTube, Vimeo embeds +- **A/B testing**: Optimizely, VWO, Google Optimize +:: + +::callout{icon="i-heroicons-exclamation-triangle" color="amber"} +**General limitations**: +- Scripts cannot directly access or modify the DOM +- `document.cookie` access requires additional Partytown configuration +- Debugging is more complex (code runs in a web worker) +- Some scripts may have timing differences compared to main thread execution +- localStorage/sessionStorage access may require forwarding configuration :: ## `defaultScriptOptions` diff --git a/test/e2e/partytown.test.ts b/test/e2e/partytown.test.ts index 49b362a8..16e174ee 100644 --- a/test/e2e/partytown.test.ts +++ b/test/e2e/partytown.test.ts @@ -39,4 +39,27 @@ describe('partytown integration', () => { expect(partytownConfig).toBeDefined() expect(partytownConfig.forward).toContain('testFn') }) + + it('partytown library is loaded and configured for worker execution', async () => { + const browser = await getBrowser() + const page = await browser.newPage() + + await page.goto(url('/'), { waitUntil: 'networkidle' }) + await waitForHydration(page, '/') + + // Verify partytown library script is injected + const partytownLib = await page.evaluate(() => { + // Partytown injects its library script + const scripts = Array.from(document.querySelectorAll('script')) + return scripts.some(s => s.textContent?.includes('partytown') || s.src.includes('partytown')) + }) + expect(partytownLib).toBe(true) + + // Verify our script has the partytown type (already tested but good to confirm in integration) + const scriptType = await page.evaluate(() => { + const script = document.querySelector('script[src="/worker-script.js"]') + return script?.getAttribute('type') + }) + expect(scriptType).toBe('text/partytown') + }) }) diff --git a/test/fixtures/partytown/pages/index.vue b/test/fixtures/partytown/pages/index.vue index 2d603e53..34c92da4 100644 --- a/test/fixtures/partytown/pages/index.vue +++ b/test/fixtures/partytown/pages/index.vue @@ -1,4 +1,11 @@ @@ -6,5 +13,8 @@ useScript('/worker-script.js', { partytown: true }) From 6259505c31ded2630c99f6762521a9d16bdd7b7e Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 20 Jan 2026 19:38:28 +1100 Subject: [PATCH 3/7] fix: partytown integration - use useHead for SSR, disable warmup - 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 --- src/runtime/composables/useScript.ts | 2 + test/e2e/partytown.test.ts | 38 +++++++------------ test/fixtures/partytown/nuxt.config.ts | 2 +- test/fixtures/partytown/pages/index.vue | 15 +++----- .../partytown/public/worker-script.js | 4 +- 5 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/runtime/composables/useScript.ts b/src/runtime/composables/useScript.ts index a54f6f99..056fbec2 100644 --- a/src/runtime/composables/useScript.ts +++ b/src/runtime/composables/useScript.ts @@ -25,6 +25,8 @@ export function useScript = Record { await waitForHydration(page, '/') // Verify our module correctly sets the type attribute for partytown + // Note: Partytown changes type to "text/partytown-x" after processing const scriptType = await page.evaluate(() => { const script = document.querySelector('script[src="/worker-script.js"]') return script?.getAttribute('type') }) - expect(scriptType).toBe('text/partytown') + expect(scriptType?.startsWith('text/partytown')).toBe(true) }) - it('partytown config is initialized with forward array', async () => { + it('partytown library is loaded and script executes in worker', async () => { const browser = await getBrowser() const page = await browser.newPage() - await page.goto(url('/'), { waitUntil: 'networkidle' }) - await waitForHydration(page, '/') - - // Verify partytown module sets up config with our forwarded function - const partytownConfig = await page.evaluate(() => { - return (window as any).partytown - }) - expect(partytownConfig).toBeDefined() - expect(partytownConfig.forward).toContain('testFn') - }) - - it('partytown library is loaded and configured for worker execution', async () => { - const browser = await getBrowser() - const page = await browser.newPage() + // Capture console messages to verify worker execution + const consoleLogs: string[] = [] + page.on('console', msg => consoleLogs.push(msg.text())) await page.goto(url('/'), { waitUntil: 'networkidle' }) await waitForHydration(page, '/') - // Verify partytown library script is injected + // Wait for partytown to execute scripts + await page.waitForTimeout(1000) + + // Verify partytown library is loaded const partytownLib = await page.evaluate(() => { - // Partytown injects its library script const scripts = Array.from(document.querySelectorAll('script')) - return scripts.some(s => s.textContent?.includes('partytown') || s.src.includes('partytown')) + return scripts.some(s => s.id === 'partytown' || s.src.includes('partytown')) }) expect(partytownLib).toBe(true) - // Verify our script has the partytown type (already tested but good to confirm in integration) - const scriptType = await page.evaluate(() => { - const script = document.querySelector('script[src="/worker-script.js"]') - return script?.getAttribute('type') - }) - expect(scriptType).toBe('text/partytown') + // Verify our script executed in the worker (check console log) + expect(consoleLogs.some(log => log.includes('Partytown script executing in worker'))).toBe(true) }) }) diff --git a/test/fixtures/partytown/nuxt.config.ts b/test/fixtures/partytown/nuxt.config.ts index 75e20979..26984abb 100644 --- a/test/fixtures/partytown/nuxt.config.ts +++ b/test/fixtures/partytown/nuxt.config.ts @@ -9,6 +9,6 @@ export default defineNuxtConfig({ compatibilityDate: '2024-07-05', partytown: { - forward: ['testFn'], + debug: true, }, }) diff --git a/test/fixtures/partytown/pages/index.vue b/test/fixtures/partytown/pages/index.vue index 34c92da4..c1d51e4c 100644 --- a/test/fixtures/partytown/pages/index.vue +++ b/test/fixtures/partytown/pages/index.vue @@ -1,13 +1,10 @@