Skip to content

fix: support cdnURL for bundled scripts #472

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

Merged
merged 1 commit into from
Jul 6, 2025
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
5 changes: 4 additions & 1 deletion src/plugins/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ function normalizeScriptData(src: string, assetsBaseURL: string = '/_scripts'):
`${ohash(url)}.js`, // force an extension
].filter(Boolean).join('-')
const nuxt = tryUseNuxt()
return { url: joinURL(joinURL(nuxt?.options.app.baseURL || '', assetsBaseURL), file), filename: file }
// Use cdnURL if available, otherwise fall back to baseURL
const cdnURL = nuxt?.options.runtimeConfig?.app?.cdnURL || nuxt?.options.app?.cdnURL || ''
const baseURL = cdnURL || nuxt?.options.app.baseURL || ''
return { url: joinURL(joinURL(baseURL, assetsBaseURL), file), filename: file }
}
return { url: src }
}
Expand Down
40 changes: 40 additions & 0 deletions test/e2e/cdn.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
// import { createResolver } from '@nuxt/kit'
import { $fetch, setup } from '@nuxt/test-utils/e2e'

// const { resolve } = createResolver(import.meta.url)

describe('cdnURL', async () => {
await setup({
rootDir: fileURLToPath(new URL('../fixtures/cdn', import.meta.url)),
nuxtConfig: {
nitro: {
prerender: {
routes: ['/'],
},
},
},
})

it('should use cdnURL for bundled scripts', async () => {
const html = await $fetch('/')

// Check that the page loads
expect(html).toContain('CDN URL Test')

// Check that script tags use the CDN URL
const scriptTags = html.match(/<script[^>]*src="([^"]+)"[^>]*>/g) || []
const bundledScripts = scriptTags.filter(tag => tag.includes('/_scripts/'))

bundledScripts.forEach((scriptTag) => {
const srcMatch = scriptTag.match(/src="([^"]+)"/)
if (srcMatch) {
expect(srcMatch[1]).toMatch(/^https:\/\/cdn\.example\.com\/_scripts\//)
}
})
})

// Runtime test would require a real CDN to be set up
// The static test above verifies the CDN URL is used in the generated HTML
})
35 changes: 35 additions & 0 deletions test/fixtures/cdn/app.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<div>
<h1>CDN URL Test</h1>
<button @click="loadScript">
Load Script
</button>
<div id="script-status">
{{ scriptStatus }}
</div>
</div>
</template>

<script setup>
import { ref } from 'vue'

const scriptStatus = ref('ready')

function loadScript() {
const { $script } = useScript('https://cdn.jsdelivr.net/npm/[email protected]/dist/index.min.js', {
bundle: true,
use() {
return {
ConfettiGenerator: window.ConfettiGenerator,
}
},
})

$script.then((script) => {
scriptStatus.value = 'loaded'
// Script loaded successfully
}).catch(() => {
scriptStatus.value = 'error'
})
}
</script>
11 changes: 11 additions & 0 deletions test/fixtures/cdn/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default defineNuxtConfig({
modules: ['@nuxt/scripts'],
app: {
cdnURL: 'https://cdn.example.com',
},
scripts: {
defaultScriptOptions: {
bundle: true,
},
},
})
38 changes: 38 additions & 0 deletions test/unit/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,28 @@ vi.mock('@nuxt/kit', async (og) => {
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(),
Expand Down Expand Up @@ -312,6 +334,22 @@ const _sfc_main = /* @__PURE__ */ _defineComponent({
expect(code.includes('useScript(\'/_scripts/vFJ41_fzYQOTRPr3v6G1PkI0hc5tMy0HGrgFjhaJhOI.js\', {')).toBeTruthy()
})

it('uses baseURL without cdnURL', async () => {
vi.mocked(hash).mockImplementationOnce(() => 'beacon.min')

const code = await transform(
`const instance = useScript('https://static.cloudflareinsights.com/beacon.min.js', {
bundle: true,
})`,
{
assetsBaseURL: '/_scripts',
},
)

// Without cdnURL configured, it should use baseURL
expect(code).toMatchInlineSnapshot(`"const instance = useScript('/_scripts/beacon.min.js', )"`)
})

describe.todo('fallbackOnSrcOnBundleFail', () => {
beforeEach(() => {
vi.mocked($fetch).mockImplementationOnce(() => Promise.reject(new Error('fetch error')))
Expand Down