mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
fix(vite): handle runtime paths in inlined styles (#27327)
This commit is contained in:
parent
6a271e8a56
commit
c054ca084c
@ -3,10 +3,11 @@ import { useNitro } from '@nuxt/kit'
|
|||||||
import { createUnplugin } from 'unplugin'
|
import { createUnplugin } 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'
|
||||||
|
|
||||||
const PREFIX = 'virtual:public?'
|
const PREFIX = 'virtual:public?'
|
||||||
|
|
||||||
export const VitePublicDirsPlugin = createUnplugin(() => {
|
export const VitePublicDirsPlugin = createUnplugin((options: { sourcemap?: boolean }) => {
|
||||||
const nitro = useNitro()
|
const nitro = useNitro()
|
||||||
|
|
||||||
function resolveFromPublicAssets (id: string) {
|
function resolveFromPublicAssets (id: string) {
|
||||||
@ -40,14 +41,33 @@ export const VitePublicDirsPlugin = createUnplugin(() => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
generateBundle (outputOptions, bundle) {
|
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 (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) {
|
for (const file in bundle) {
|
||||||
const chunk = bundle[file]
|
const chunk = bundle[file]
|
||||||
if (!file.endsWith('.css') || chunk.type !== 'asset') { continue }
|
if (!file.endsWith('.css') || chunk.type !== 'asset') { continue }
|
||||||
|
|
||||||
let css = chunk.source.toString()
|
let css = chunk.source.toString()
|
||||||
let wasReplaced = false
|
let wasReplaced = false
|
||||||
for (const [full, url] of css.matchAll(/url\((\/[^)]+)\)/g)) {
|
for (const [full, url] of css.matchAll(CSS_URL_RE)) {
|
||||||
if (resolveFromPublicAssets(url)) {
|
if (resolveFromPublicAssets(url)) {
|
||||||
const relativeURL = relative(withLeadingSlash(dirname(file)), url)
|
const relativeURL = relative(withLeadingSlash(dirname(file)), url)
|
||||||
css = css.replace(full, `url(${relativeURL})`)
|
css = css.replace(full, `url(${relativeURL})`)
|
||||||
@ -62,3 +82,5 @@ export const VitePublicDirsPlugin = createUnplugin(() => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const CSS_URL_RE = /url\((\/[^)]+)\)/g
|
||||||
|
@ -100,7 +100,7 @@ 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(),
|
VitePublicDirsPlugin.vite({ sourcemap: !!nuxt.options.sourcemap.server }),
|
||||||
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,
|
||||||
|
@ -1915,11 +1915,24 @@ describe('public directories', () => {
|
|||||||
|
|
||||||
// TODO: dynamic paths in dev
|
// TODO: dynamic paths in dev
|
||||||
describe.skipIf(isDev())('dynamic paths', () => {
|
describe.skipIf(isDev())('dynamic paths', () => {
|
||||||
|
const publicFiles = ['/public.svg', '/css-only-public-asset.svg']
|
||||||
|
const isPublicFile = (base = '/', file: string) => {
|
||||||
|
if (isWebpack) {
|
||||||
|
// TODO: webpack does not yet support dynamic static paths
|
||||||
|
expect(publicFiles).toContain(file)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(file).toMatch(new RegExp(`^${base.replace(/\//g, '\\/')}`))
|
||||||
|
expect(publicFiles).toContain(file.replace(base, '/'))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
it('should work with no overrides', async () => {
|
it('should work with no overrides', async () => {
|
||||||
const html: string = await $fetch<string>('/assets')
|
const html: string = await $fetch<string>('/assets')
|
||||||
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
||||||
const url = match[2] || match[3]
|
const url = match[2] || match[3]
|
||||||
expect(url.startsWith('/_nuxt/') || url === '/public.svg').toBeTruthy()
|
expect(url.startsWith('/_nuxt/') || isPublicFile('/', url)).toBeTruthy()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1929,16 +1942,14 @@ describe.skipIf(isDev())('dynamic paths', () => {
|
|||||||
const urls = Array.from(html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)).map(m => m[2] || m[3])
|
const urls = Array.from(html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)).map(m => m[2] || m[3])
|
||||||
const cssURL = urls.find(u => /_nuxt\/assets.*\.css$/.test(u))
|
const cssURL = urls.find(u => /_nuxt\/assets.*\.css$/.test(u))
|
||||||
expect(cssURL).toBeDefined()
|
expect(cssURL).toBeDefined()
|
||||||
const css: string = await $fetch<string>(cssURL!)
|
const css = await $fetch<string>(cssURL!)
|
||||||
const imageUrls = Array.from(css.matchAll(/url\(([^)]*)\)/g)).map(m => m[1].replace(/[-.]\w{8}\./g, '.'))
|
const imageUrls = new Set(Array.from(css.matchAll(/url\(([^)]*)\)/g)).map(m => m[1].replace(/[-.]\w{8}\./g, '.')))
|
||||||
expect(imageUrls).toMatchInlineSnapshot(`
|
expect([...imageUrls]).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
"./logo.svg",
|
"./logo.svg",
|
||||||
"../public.svg",
|
"../public.svg",
|
||||||
"../public.svg",
|
]
|
||||||
"../public.svg",
|
`)
|
||||||
]
|
|
||||||
`)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should allow setting base URL and build assets directory', async () => {
|
it('should allow setting base URL and build assets directory', async () => {
|
||||||
@ -1952,12 +1963,7 @@ describe.skipIf(isDev())('dynamic paths', () => {
|
|||||||
const html = await $fetch<string>('/foo/assets')
|
const html = await $fetch<string>('/foo/assets')
|
||||||
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
||||||
const url = match[2] || match[3]
|
const url = match[2] || match[3]
|
||||||
expect(
|
expect(url.startsWith('/foo/_other/') || isPublicFile('/foo/', url)).toBeTruthy()
|
||||||
url.startsWith('/foo/_other/') ||
|
|
||||||
url === '/foo/public.svg' ||
|
|
||||||
// TODO: webpack does not yet support dynamic static paths
|
|
||||||
(isWebpack && url === '/public.svg'),
|
|
||||||
).toBeTruthy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(await $fetch<string>('/foo/url')).toContain('path: /foo/url')
|
expect(await $fetch<string>('/foo/url')).toContain('path: /foo/url')
|
||||||
@ -1973,12 +1979,7 @@ describe.skipIf(isDev())('dynamic paths', () => {
|
|||||||
const html = await $fetch<string>('/assets')
|
const html = await $fetch<string>('/assets')
|
||||||
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
||||||
const url = match[2] || match[3]
|
const url = match[2] || match[3]
|
||||||
expect(
|
expect(url.startsWith('./_nuxt/') || isPublicFile('./', url)).toBeTruthy()
|
||||||
url.startsWith('./_nuxt/') ||
|
|
||||||
url === './public.svg' ||
|
|
||||||
// TODO: webpack does not yet support dynamic static paths
|
|
||||||
(isWebpack && url === '/public.svg'),
|
|
||||||
).toBeTruthy()
|
|
||||||
expect(url.startsWith('./_nuxt/_nuxt')).toBeFalsy()
|
expect(url.startsWith('./_nuxt/_nuxt')).toBeFalsy()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -2007,12 +2008,7 @@ describe.skipIf(isDev())('dynamic paths', () => {
|
|||||||
const html = await $fetch<string>('/foo/assets')
|
const html = await $fetch<string>('/foo/assets')
|
||||||
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
for (const match of html.matchAll(/(href|src)="(.*?)"|url\(([^)]*)\)/g)) {
|
||||||
const url = match[2] || match[3]
|
const url = match[2] || match[3]
|
||||||
expect(
|
expect(url.startsWith('https://example.com/_cdn/') || isPublicFile('https://example.com/', url)).toBeTruthy()
|
||||||
url.startsWith('https://example.com/_cdn/') ||
|
|
||||||
url === 'https://example.com/public.svg' ||
|
|
||||||
// TODO: webpack does not yet support dynamic static paths
|
|
||||||
(isWebpack && url === '/public.svg'),
|
|
||||||
).toBeTruthy()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
1
test/fixtures/basic/assets/global.css
vendored
1
test/fixtures/basic/assets/global.css
vendored
@ -1,4 +1,5 @@
|
|||||||
:root {
|
:root {
|
||||||
--global: 'global';
|
--global: 'global';
|
||||||
--asset: url('~/assets/css-only-asset.svg');
|
--asset: url('~/assets/css-only-asset.svg');
|
||||||
|
--public-asset: url('/css-only-public-asset.svg');
|
||||||
}
|
}
|
||||||
|
1
test/fixtures/basic/public/css-only-public-asset.svg
vendored
Normal file
1
test/fixtures/basic/public/css-only-public-asset.svg
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"></svg>
|
After Width: | Height: | Size: 67 B |
Loading…
Reference in New Issue
Block a user