diff --git a/packages/vite/src/plugins/public-dirs.ts b/packages/vite/src/plugins/public-dirs.ts index 39e6b46b85..e2765510f5 100644 --- a/packages/vite/src/plugins/public-dirs.ts +++ b/packages/vite/src/plugins/public-dirs.ts @@ -1,77 +1,112 @@ import { existsSync } from 'node:fs' import { useNitro } from '@nuxt/kit' import { createUnplugin } from 'unplugin' +import type { UnpluginOptions } from 'unplugin' import { withLeadingSlash, withTrailingSlash } from 'ufo' import { dirname, relative } from 'pathe' import MagicString from 'magic-string' +import { isCSSRequest } from 'vite' const PREFIX = 'virtual:public?' 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() - return { - name: 'nuxt:vite-public-dir-resolution', + const devTransformPlugin: UnpluginOptions = { + name: 'nuxt:vite-public-dir-resolution-dev', 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 } + transform (code, id) { + if (!isCSSRequest(id) || !CSS_URL_SINGLE_RE.test(code)) { 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})`) + s.replace(full, `url(${options.baseURL}${url})`) } } 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 - } - } - }, }, } + + 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 () { diff --git a/packages/vite/src/vite.ts b/packages/vite/src/vite.ts index bace63f36f..5f9c2249db 100644 --- a/packages/vite/src/vite.ts +++ b/packages/vite/src/vite.ts @@ -100,7 +100,11 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => { }, plugins: [ // 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({ sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client, rootDir: nuxt.options.rootDir,