Skip to content

Commit 6f9579b

Browse files
authored
fix: support cdnURL for bundled scripts (#472)
1 parent 908f412 commit 6f9579b

File tree

5 files changed

+128
-1
lines changed

5 files changed

+128
-1
lines changed

src/plugins/transform.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ function normalizeScriptData(src: string, assetsBaseURL: string = '/_scripts'):
4545
`${ohash(url)}.js`, // force an extension
4646
].filter(Boolean).join('-')
4747
const nuxt = tryUseNuxt()
48-
return { url: joinURL(joinURL(nuxt?.options.app.baseURL || '', assetsBaseURL), file), filename: file }
48+
// Use cdnURL if available, otherwise fall back to baseURL
49+
const cdnURL = nuxt?.options.runtimeConfig?.app?.cdnURL || nuxt?.options.app?.cdnURL || ''
50+
const baseURL = cdnURL || nuxt?.options.app.baseURL || ''
51+
return { url: joinURL(joinURL(baseURL, assetsBaseURL), file), filename: file }
4952
}
5053
return { url: src }
5154
}

test/e2e/cdn.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { fileURLToPath } from 'node:url'
2+
import { describe, expect, it } from 'vitest'
3+
// import { createResolver } from '@nuxt/kit'
4+
import { $fetch, setup } from '@nuxt/test-utils/e2e'
5+
6+
// const { resolve } = createResolver(import.meta.url)
7+
8+
describe('cdnURL', async () => {
9+
await setup({
10+
rootDir: fileURLToPath(new URL('../fixtures/cdn', import.meta.url)),
11+
nuxtConfig: {
12+
nitro: {
13+
prerender: {
14+
routes: ['/'],
15+
},
16+
},
17+
},
18+
})
19+
20+
it('should use cdnURL for bundled scripts', async () => {
21+
const html = await $fetch('/')
22+
23+
// Check that the page loads
24+
expect(html).toContain('CDN URL Test')
25+
26+
// Check that script tags use the CDN URL
27+
const scriptTags = html.match(/<script[^>]*src="([^"]+)"[^>]*>/g) || []
28+
const bundledScripts = scriptTags.filter(tag => tag.includes('/_scripts/'))
29+
30+
bundledScripts.forEach((scriptTag) => {
31+
const srcMatch = scriptTag.match(/src="([^"]+)"/)
32+
if (srcMatch) {
33+
expect(srcMatch[1]).toMatch(/^https:\/\/cdn\.example\.com\/_scripts\//)
34+
}
35+
})
36+
})
37+
38+
// Runtime test would require a real CDN to be set up
39+
// The static test above verifies the CDN URL is used in the generated HTML
40+
})

test/fixtures/cdn/app.vue

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<template>
2+
<div>
3+
<h1>CDN URL Test</h1>
4+
<button @click="loadScript">
5+
Load Script
6+
</button>
7+
<div id="script-status">
8+
{{ scriptStatus }}
9+
</div>
10+
</div>
11+
</template>
12+
13+
<script setup>
14+
import { ref } from 'vue'
15+
16+
const scriptStatus = ref('ready')
17+
18+
function loadScript() {
19+
const { $script } = useScript('https://cdn.jsdelivr.net/npm/[email protected]/dist/index.min.js', {
20+
bundle: true,
21+
use() {
22+
return {
23+
ConfettiGenerator: window.ConfettiGenerator,
24+
}
25+
},
26+
})
27+
28+
$script.then((script) => {
29+
scriptStatus.value = 'loaded'
30+
// Script loaded successfully
31+
}).catch(() => {
32+
scriptStatus.value = 'error'
33+
})
34+
}
35+
</script>

test/fixtures/cdn/nuxt.config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default defineNuxtConfig({
2+
modules: ['@nuxt/scripts'],
3+
app: {
4+
cdnURL: 'https://cdn.example.com',
5+
},
6+
scripts: {
7+
defaultScriptOptions: {
8+
bundle: true,
9+
},
10+
},
11+
})

test/unit/transform.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,28 @@ vi.mock('@nuxt/kit', async (og) => {
4949
return {
5050
options: {
5151
buildDir: '.nuxt',
52+
app: {
53+
baseURL: '/',
54+
},
55+
runtimeConfig: {
56+
app: {},
57+
},
58+
},
59+
hooks: {
60+
hook: vi.fn(),
61+
},
62+
}
63+
},
64+
tryUseNuxt() {
65+
return {
66+
options: {
67+
buildDir: '.nuxt',
68+
app: {
69+
baseURL: '/',
70+
},
71+
runtimeConfig: {
72+
app: {},
73+
},
5274
},
5375
hooks: {
5476
hook: vi.fn(),
@@ -312,6 +334,22 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({
312334
expect(code.includes('useScript(\'/_scripts/vFJ41_fzYQOTRPr3v6G1PkI0hc5tMy0HGrgFjhaJhOI.js\', {')).toBeTruthy()
313335
})
314336

337+
it('uses baseURL without cdnURL', async () => {
338+
vi.mocked(hash).mockImplementationOnce(() => 'beacon.min')
339+
340+
const code = await transform(
341+
`const instance = useScript('https://static.cloudflareinsights.com/beacon.min.js', {
342+
bundle: true,
343+
})`,
344+
{
345+
assetsBaseURL: '/_scripts',
346+
},
347+
)
348+
349+
// Without cdnURL configured, it should use baseURL
350+
expect(code).toMatchInlineSnapshot(`"const instance = useScript('/_scripts/beacon.min.js', )"`)
351+
})
352+
315353
describe.todo('fallbackOnSrcOnBundleFail', () => {
316354
beforeEach(() => {
317355
vi.mocked($fetch).mockImplementationOnce(() => Promise.reject(new Error('fetch error')))

0 commit comments

Comments
 (0)