diff --git a/packages/script/src/module.ts b/packages/script/src/module.ts index 99131a99..cdc99f7c 100644 --- a/packages/script/src/module.ts +++ b/packages/script/src/module.ts @@ -1023,6 +1023,7 @@ export default defineNuxtModule({ dev: true, }) addBuildPlugin(NuxtScriptBundleTransformer({ + nuxt, scripts: registryScriptsWithImport, registryConfig: nuxt.options.runtimeConfig.public.scripts as Record | undefined, proxyConfigs, diff --git a/packages/script/src/plugins/transform.ts b/packages/script/src/plugins/transform.ts index 8e056902..7258ad6a 100644 --- a/packages/script/src/plugins/transform.ts +++ b/packages/script/src/plugins/transform.ts @@ -1,3 +1,4 @@ +import type { Nuxt } from '@nuxt/schema' import type { FetchOptions } from 'ofetch' import type { SourceMapInput } from 'rollup' import type { InferInput } from 'valibot' @@ -84,6 +85,12 @@ export interface AssetBundlerTransformerOptions { * @default false */ integrity?: boolean | IntegrityAlgorithm + /** + * The active Nuxt instance. Used to resolve the build directory and register the + * `build:done` copy hook. Defaults to `useNuxt()`; pass it explicitly to run the + * transformer without an active Nuxt context (e.g. unit tests). + */ + nuxt?: Nuxt renderedScript?: Map /** * Set of registry script keys that use Partytown. @@ -214,7 +221,7 @@ async function downloadScript(opts: { export function NuxtScriptBundleTransformer(options: AssetBundlerTransformerOptions = { renderedScript: new Map(), }) { - const nuxt = useNuxt() + const nuxt = options.nuxt ?? useNuxt() const { renderedScript = new Map() } = options const cacheDir = join(nuxt.options.buildDir, 'cache', 'scripts') @@ -239,7 +246,7 @@ export function NuxtScriptBundleTransformer(options: AssetBundlerTransformerOpti await Promise.all(scripts.map(async ([url, content]) => { if (content instanceof Error || !content.filename) return - await fsp.writeFile(join(nuxt.options.buildDir, 'cache', 'scripts', content.filename), content.content) + await fsp.writeFile(join(cacheDir, content.filename), content.content) logger.debug(colors.gray(` ├─ ${url} → ${joinURL(content.src)} (${content.size.toFixed(2)} kB ${content.encoding})`)) })) }) diff --git a/test/unit/bundle-sdk-patches.test.ts b/test/unit/bundle-sdk-patches.test.ts index 1cd7c7ee..522eb9b4 100644 --- a/test/unit/bundle-sdk-patches.test.ts +++ b/test/unit/bundle-sdk-patches.test.ts @@ -34,18 +34,14 @@ vi.mock('../../packages/script/src/assets', () => ({ const fetchMock = vi.fn() vi.stubGlobal('fetch', fetchMock) -vi.mock('@nuxt/kit', async (og) => { - const mod = await og() - const nuxt = { - options: { buildDir: '.nuxt', app: { baseURL: '/' }, runtimeConfig: { app: {} } }, - hooks: { hook: vi.fn() }, - } - return { ...mod, useNuxt: () => nuxt, tryUseNuxt: () => nuxt } -}) - vi.mocked(hasProtocol).mockImplementation(() => true) vi.mocked(hash).mockImplementation(() => 'fathom-script') +const mockNuxt = { + options: { buildDir: '.nuxt', app: { baseURL: '/' }, runtimeConfig: { app: {} } }, + hooks: { hook: vi.fn() }, +} as any + let _proxyConfigs: ReturnType | undefined async function getProxyConfigs() { if (!_proxyConfigs) @@ -63,7 +59,7 @@ function mockUpstream(bytes: Buffer) { } async function runTransform(code: string, options: AssetBundlerTransformerOptions) { - const plugin = NuxtScriptBundleTransformer(options).vite() as any + const plugin = NuxtScriptBundleTransformer({ ...options, nuxt: mockNuxt }).vite() as any await plugin.transform.handler.call({}, code, 'file.js') } diff --git a/test/unit/bundle-two-deploy-repro.test.ts b/test/unit/bundle-two-deploy-repro.test.ts index 8c244881..50400e4a 100644 --- a/test/unit/bundle-two-deploy-repro.test.ts +++ b/test/unit/bundle-two-deploy-repro.test.ts @@ -31,23 +31,19 @@ vi.mock('../../packages/script/src/assets', () => ({ const fetchMock = vi.fn() vi.stubGlobal('fetch', fetchMock) -vi.mock('@nuxt/kit', async (og) => { - const mod = await og() - const nuxt = { - options: { buildDir: '.nuxt', app: { baseURL: '/' }, runtimeConfig: { app: {} } }, - hooks: { hook: vi.fn() }, - } - return { ...mod, useNuxt: () => nuxt, tryUseNuxt: () => nuxt } -}) - vi.mocked(hasProtocol).mockImplementation(() => true) // Source URL hash is stable across both "deploys" — the URL doesn't change between deployments, // only the upstream bytes do. This is exactly the real-world scenario in #724. vi.mocked(hash).mockImplementation(() => 'adsbygoogle') +const mockNuxt = { + options: { buildDir: '.nuxt', app: { baseURL: '/' }, runtimeConfig: { app: {} } }, + hooks: { hook: vi.fn() }, +} as any + async function runTransform(code: string, options?: AssetBundlerTransformerOptions) { mockBundleStorage.hasItem.mockResolvedValue(false) - const plugin = NuxtScriptBundleTransformer({ renderedScript: new Map(), ...options }).vite() as any + const plugin = NuxtScriptBundleTransformer({ renderedScript: new Map(), ...options, nuxt: mockNuxt }).vite() as any const out = await plugin.transform.handler.call({}, code, 'file.js') return out?.code as string } diff --git a/test/unit/transform.test.ts b/test/unit/transform.test.ts index 7f4ed929..45044828 100644 --- a/test/unit/transform.test.ts +++ b/test/unit/transform.test.ts @@ -51,45 +51,10 @@ vi.stubGlobal('fetch', vi.fn(() => { return Promise.resolve({ arrayBuffer: vi.fn(() => Buffer.from('')), ok: true, headers: { get: vi.fn() } }) })) -vi.mock('@nuxt/kit', async (og) => { - const mod = await og() - - return { - ...mod, - useNuxt() { - return { - options: { - buildDir: '.nuxt', - app: { - baseURL: '/', - }, - runtimeConfig: { - app: {}, - }, - }, - hooks: { - hook: vi.fn(), - }, - } - }, - tryUseNuxt() { - return { - options: { - buildDir: '.nuxt', - app: { - baseURL: '/', - }, - runtimeConfig: { - app: {}, - }, - }, - hooks: { - hook: vi.fn(), - }, - } - }, - } -}) +const mockNuxt = { + options: { buildDir: '.nuxt', app: { baseURL: '/' }, runtimeConfig: { app: {} } }, + hooks: { hook: vi.fn() }, +} as any // we want to control normalizeScriptData() output vi.mocked(hasProtocol).mockImplementation(() => true) @@ -97,7 +62,7 @@ vi.mocked(hasProtocol).mockImplementation(() => true) vi.mocked(hash).mockImplementation(src => src.pathname) async function transform(code: string | string[], options?: AssetBundlerTransformerOptions) { - const plugin = NuxtScriptBundleTransformer(options).vite() as any + const plugin = NuxtScriptBundleTransformer({ ...options, nuxt: mockNuxt }).vite() as any const res = await plugin.transform.handler.call( {}, Array.isArray(code) ? code.join('\n') : code,