Skip to content

Commit e96b908

Browse files
authored
fix(css): var in image-set (#7921)
1 parent 1db7c49 commit e96b908

File tree

5 files changed

+103
-13
lines changed

5 files changed

+103
-13
lines changed

packages/playground/assets/__tests__/assets.spec.ts

+23
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,29 @@ describe('css url() references', () => {
104104
})
105105
})
106106

107+
test('image-set with var', async () => {
108+
const imageSet = await getBg('.css-image-set-with-var')
109+
imageSet.split(', ').forEach((s) => {
110+
expect(s).toMatch(assetMatch)
111+
})
112+
})
113+
114+
test('image-set with mix', async () => {
115+
const imageSet = await getBg('.css-image-set-mix-url-var')
116+
imageSet.split(', ').forEach((s) => {
117+
expect(s).toMatch(assetMatch)
118+
})
119+
})
120+
121+
// not supported in browser now
122+
// https://drafts.csswg.org/css-images-4/#image-set-notation
123+
// test('image-set with multiple descriptor', async () => {
124+
// const imageSet = await getBg('.css-image-set-multiple-descriptor')
125+
// imageSet.split(', ').forEach((s) => {
126+
// expect(s).toMatch(assetMatch)
127+
// })
128+
// })
129+
107130
test('relative in @import', async () => {
108131
expect(await getBg('.css-url-relative-at-imported')).toMatch(assetMatch)
109132
})

packages/playground/assets/css/css-url.css

+23
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,29 @@
2626
background-size: 10px;
2727
}
2828

29+
.css-image-set-with-var {
30+
--bg-img: url('../nested/asset.png');
31+
background-image: -webkit-image-set(var(--bg-img) 1x, var(--bg-img) 2x);
32+
background-size: 10px;
33+
}
34+
35+
.css-image-set-mix-url-var {
36+
--bg-img: url('../nested/asset.png');
37+
background-image: -webkit-image-set(
38+
var(--bg-img) 1x,
39+
url('../nested/asset.png') 2x
40+
);
41+
background-size: 10px;
42+
}
43+
44+
.css-image-set-multiple-descriptor {
45+
background-image: -webkit-image-set(
46+
'../nested/asset.png' type('image/png') 1x,
47+
'../nested/asset.png' type('image/png') 2x
48+
);
49+
background-size: 10px;
50+
}
51+
2952
.css-url-public {
3053
background: url('/icon.png');
3154
background-size: 10px;

packages/playground/assets/index.html

+28
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,34 @@ <h2>CSS url references</h2>
4646
>CSS background with image-set() (relative)</span
4747
>
4848
</div>
49+
<div class="css-image-set-with-var">
50+
<span style="background: #fff">
51+
CSS background image-set() (relative in var)
52+
</span>
53+
</div>
54+
<div class="css-image-set-mix-url-var">
55+
<span style="background: #fff">
56+
CSS background image-set() (mix var and url)
57+
</span>
58+
</div>
59+
<div class="css-image-set-multiple-descriptor">
60+
<span style="background: #fff">
61+
CSS background image-set() (with multiple descriptor)
62+
</span>
63+
</div>
64+
<div
65+
style="
66+
background-image: -webkit-image-set(
67+
'./nested/asset.png' type('image/png') 1x,
68+
'./nested/asset.png' type('image/png') 2x
69+
);
70+
background-size: 10px;
71+
"
72+
>
73+
<span style="background: #fff">
74+
CSS background image-set() (with multiple descriptor)
75+
</span>
76+
</div>
4977
<div class="css-url-relative-at-imported">
5078
<span style="background: #fff"
5179
>CSS background (relative from @imported file in different dir)</span

packages/vite/src/node/plugins/css.ts

+21-8
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ const commonjsProxyRE = /\?commonjs-proxy/
104104
const inlineRE = /(\?|&)inline\b/
105105
const inlineCSSRE = /(\?|&)inline-css\b/
106106
const usedRE = /(\?|&)used\b/
107+
const varRE = /^var\(/i
107108

108109
const enum PreprocessLang {
109110
less = 'less',
@@ -980,7 +981,7 @@ export const cssUrlRE =
980981
export const cssDataUriRE =
981982
/(?<=^|[^\w\-\u0080-\uffff])data-uri\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/
982983
export const importCssRE = /@import ('[^']+\.css'|"[^"]+\.css"|[^'")]+\.css)/
983-
const cssImageSetRE = /image-set\(([^)]+)\)/
984+
const cssImageSetRE = /(?<=image-set\()((?:[\w\-]+\([^\)]*\)|[^)])*)(?=\))/
984985

985986
const UrlRewritePostcssPlugin: PostCSS.PluginCreator<{
986987
replacer: CssUrlReplacer
@@ -1001,7 +1002,9 @@ const UrlRewritePostcssPlugin: PostCSS.PluginCreator<{
10011002
const importer = declaration.source?.input.file
10021003
return opts.replacer(rawUrl, importer)
10031004
}
1004-
const rewriterToUse = isCssUrl ? rewriteCssUrls : rewriteCssImageSet
1005+
const rewriterToUse = isCssImageSet
1006+
? rewriteCssImageSet
1007+
: rewriteCssUrls
10051008
promises.push(
10061009
rewriterToUse(declaration.value, replacerForDeclaration).then(
10071010
(url) => {
@@ -1054,11 +1057,15 @@ function rewriteCssImageSet(
10541057
replacer: CssUrlReplacer
10551058
): Promise<string> {
10561059
return asyncReplace(css, cssImageSetRE, async (match) => {
1057-
const [matched, rawUrl] = match
1058-
const url = await processSrcSet(rawUrl, ({ url }) =>
1059-
doUrlReplace(url, matched, replacer)
1060-
)
1061-
return `image-set(${url})`
1060+
const [, rawUrl] = match
1061+
const url = await processSrcSet(rawUrl, async ({ url }) => {
1062+
// the url maybe url(...)
1063+
if (cssUrlRE.test(url)) {
1064+
return await rewriteCssUrls(url, replacer)
1065+
}
1066+
return await doUrlReplace(url, url, replacer)
1067+
})
1068+
return url
10621069
})
10631070
}
10641071
async function doUrlReplace(
@@ -1073,7 +1080,13 @@ async function doUrlReplace(
10731080
wrap = first
10741081
rawUrl = rawUrl.slice(1, -1)
10751082
}
1076-
if (isExternalUrl(rawUrl) || isDataUrl(rawUrl) || rawUrl.startsWith('#')) {
1083+
1084+
if (
1085+
isExternalUrl(rawUrl) ||
1086+
isDataUrl(rawUrl) ||
1087+
rawUrl.startsWith('#') ||
1088+
varRE.test(rawUrl)
1089+
) {
10771090
return matched
10781091
}
10791092

packages/vite/src/node/utils.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -545,18 +545,21 @@ interface ImageCandidate {
545545
descriptor: string
546546
}
547547
const escapedSpaceCharacters = /( |\\t|\\n|\\f|\\r)+/g
548+
const imageSetUrlRE = /^(?:[\w\-]+\(.*?\)|'.*?'|".*?"|\S*)/
548549
export async function processSrcSet(
549550
srcs: string,
550551
replacer: (arg: ImageCandidate) => Promise<string>
551552
): Promise<string> {
552553
const imageCandidates: ImageCandidate[] = srcs
553554
.split(',')
554555
.map((s) => {
555-
const [url, descriptor] = s
556-
.replace(escapedSpaceCharacters, ' ')
557-
.trim()
558-
.split(' ', 2)
559-
return { url, descriptor }
556+
const src = s.replace(escapedSpaceCharacters, ' ').trim()
557+
const [url] = imageSetUrlRE.exec(src) || []
558+
559+
return {
560+
url,
561+
descriptor: src?.slice(url.length).trim()
562+
}
560563
})
561564
.filter(({ url }) => !!url)
562565

0 commit comments

Comments
 (0)