Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/script/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,7 @@ export default defineNuxtModule<ModuleOptions>({
dev: true,
})
addBuildPlugin(NuxtScriptBundleTransformer({
nuxt,
scripts: registryScriptsWithImport,
registryConfig: nuxt.options.runtimeConfig.public.scripts as Record<string, any> | undefined,
proxyConfigs,
Expand Down
11 changes: 9 additions & 2 deletions packages/script/src/plugins/transform.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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<string, RenderedScriptMeta | Error>
/**
* Set of registry script keys that use Partytown.
Expand Down Expand Up @@ -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')

Expand All @@ -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})`))
}))
})
Expand Down
16 changes: 6 additions & 10 deletions test/unit/bundle-sdk-patches.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof import('@nuxt/kit')>()
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<typeof buildProxyConfigsFromRegistry> | undefined
async function getProxyConfigs() {
if (!_proxyConfigs)
Expand All @@ -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')
}

Expand Down
16 changes: 6 additions & 10 deletions test/unit/bundle-two-deploy-repro.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof import('@nuxt/kit')>()
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
}
Expand Down
45 changes: 5 additions & 40 deletions test/unit/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,53 +51,18 @@ 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<typeof import('@nuxt/kit')>()

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)
// hash receive a URL object, we want to mock it to return the pathname by default
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,
Expand Down
Loading