fix(vite): respect baseURL for public assets in dev (#28482)

This commit is contained in:
Daniel Roe 2024-08-09 13:46:38 +01:00 committed by GitHub
parent 68e153c71a
commit 4d36810334
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 85 additions and 46 deletions

View File

@ -1,77 +1,112 @@
import { existsSync } from 'node:fs' import { existsSync } from 'node:fs'
import { useNitro } from '@nuxt/kit' import { useNitro } from '@nuxt/kit'
import { createUnplugin } from 'unplugin' import { createUnplugin } from 'unplugin'
import type { UnpluginOptions } from 'unplugin'
import { withLeadingSlash, withTrailingSlash } from 'ufo' import { withLeadingSlash, withTrailingSlash } from 'ufo'
import { dirname, relative } from 'pathe' import { dirname, relative } from 'pathe'
import MagicString from 'magic-string' import MagicString from 'magic-string'
import { isCSSRequest } from 'vite'
const PREFIX = 'virtual:public?' const PREFIX = 'virtual:public?'
const CSS_URL_RE = /url\((\/[^)]+)\)/g const CSS_URL_RE = /url\((\/[^)]+)\)/g
const CSS_URL_SINGLE_RE = /url\(\/[^)]+\)/
export const VitePublicDirsPlugin = createUnplugin((options: { sourcemap?: boolean }) => { interface VitePublicDirsPluginOptions {
dev?: boolean
sourcemap?: boolean
baseURL?: string
}
export const VitePublicDirsPlugin = createUnplugin((options: VitePublicDirsPluginOptions) => {
const { resolveFromPublicAssets } = useResolveFromPublicAssets() const { resolveFromPublicAssets } = useResolveFromPublicAssets()
return { const devTransformPlugin: UnpluginOptions = {
name: 'nuxt:vite-public-dir-resolution', name: 'nuxt:vite-public-dir-resolution-dev',
vite: { vite: {
load: { transform (code, id) {
enforce: 'pre', if (!isCSSRequest(id) || !CSS_URL_SINGLE_RE.test(code)) { return }
handler (id) {
if (id.startsWith(PREFIX)) {
return `import { publicAssetsURL } from '#internal/nuxt/paths';export default publicAssetsURL(${JSON.stringify(decodeURIComponent(id.slice(PREFIX.length)))})`
}
},
},
resolveId: {
enforce: 'post',
handler (id) {
if (id === '/__skip_vite' || id[0] !== '/' || id.startsWith('/@fs')) { return }
if (resolveFromPublicAssets(id)) {
return PREFIX + encodeURIComponent(id)
}
},
},
renderChunk (code, chunk) {
if (!chunk.facadeModuleId?.includes('?inline&used')) { return }
const s = new MagicString(code) const s = new MagicString(code)
const q = code.match(/(?<= = )['"`]/)?.[0] || '"'
for (const [full, url] of code.matchAll(CSS_URL_RE)) { for (const [full, url] of code.matchAll(CSS_URL_RE)) {
if (url && resolveFromPublicAssets(url)) { if (url && resolveFromPublicAssets(url)) {
s.replace(full, `url(${q} + publicAssetsURL(${q}${url}${q}) + ${q})`) s.replace(full, `url(${options.baseURL}${url})`)
} }
} }
if (s.hasChanged()) { if (s.hasChanged()) {
s.prepend(`import { publicAssetsURL } from '#internal/nuxt/paths';`)
return { return {
code: s.toString(), code: s.toString(),
map: options.sourcemap ? s.generateMap({ hires: true }) : undefined, map: options.sourcemap ? s.generateMap({ hires: true }) : undefined,
} }
} }
}, },
generateBundle (_outputOptions, bundle) {
for (const file in bundle) {
const chunk = bundle[file]!
if (!file.endsWith('.css') || chunk.type !== 'asset') { continue }
let css = chunk.source.toString()
let wasReplaced = false
for (const [full, url] of css.matchAll(CSS_URL_RE)) {
if (url && resolveFromPublicAssets(url)) {
const relativeURL = relative(withLeadingSlash(dirname(file)), url)
css = css.replace(full, `url(${relativeURL})`)
wasReplaced = true
}
}
if (wasReplaced) {
chunk.source = css
}
}
},
}, },
} }
return [
...(options.dev && options.baseURL && options.baseURL !== '/' ? [devTransformPlugin] : []),
{
name: 'nuxt:vite-public-dir-resolution',
vite: {
load: {
enforce: 'pre',
handler (id) {
if (id.startsWith(PREFIX)) {
return `import { publicAssetsURL } from '#internal/nuxt/paths';export default publicAssetsURL(${JSON.stringify(decodeURIComponent(id.slice(PREFIX.length)))})`
}
},
},
resolveId: {
enforce: 'post',
handler (id) {
if (id === '/__skip_vite' || id[0] !== '/' || id.startsWith('/@fs')) { return }
if (resolveFromPublicAssets(id)) {
return PREFIX + encodeURIComponent(id)
}
},
},
renderChunk (code, chunk) {
if (!chunk.facadeModuleId?.includes('?inline&used')) { return }
const s = new MagicString(code)
const q = code.match(/(?<= = )['"`]/)?.[0] || '"'
for (const [full, url] of code.matchAll(CSS_URL_RE)) {
if (url && resolveFromPublicAssets(url)) {
s.replace(full, `url(${q} + publicAssetsURL(${q}${url}${q}) + ${q})`)
}
}
if (s.hasChanged()) {
s.prepend(`import { publicAssetsURL } from '#internal/nuxt/paths';`)
return {
code: s.toString(),
map: options.sourcemap ? s.generateMap({ hires: true }) : undefined,
}
}
},
generateBundle (_outputOptions, bundle) {
for (const file in bundle) {
const chunk = bundle[file]!
if (!file.endsWith('.css') || chunk.type !== 'asset') { continue }
let css = chunk.source.toString()
let wasReplaced = false
for (const [full, url] of css.matchAll(CSS_URL_RE)) {
if (url && resolveFromPublicAssets(url)) {
const relativeURL = relative(withLeadingSlash(dirname(file)), url)
css = css.replace(full, `url(${relativeURL})`)
wasReplaced = true
}
}
if (wasReplaced) {
chunk.source = css
}
}
},
},
},
]
}) })
export function useResolveFromPublicAssets () { export function useResolveFromPublicAssets () {

View File

@ -99,7 +99,11 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
}, },
plugins: [ plugins: [
// add resolver for files in public assets directories // add resolver for files in public assets directories
VitePublicDirsPlugin.vite({ sourcemap: !!nuxt.options.sourcemap.server }), VitePublicDirsPlugin.vite({
dev: nuxt.options.dev,
sourcemap: !!nuxt.options.sourcemap.server,
baseURL: nuxt.options.app.baseURL,
}),
composableKeysPlugin.vite({ composableKeysPlugin.vite({
sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client, sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client,
rootDir: nuxt.options.rootDir, rootDir: nuxt.options.rootDir,