Skip to content

Commit

Permalink
fix: should transform the img tag's srcset arrtibute and css' image-s…
Browse files Browse the repository at this point in the history
…et property (#2188)

fix #2177
  • Loading branch information
HcySunYang authored Feb 24, 2021
1 parent 45e4595 commit 0f17a74
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 17 deletions.
28 changes: 28 additions & 0 deletions packages/playground/assets/__tests__/assets.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@ describe('css url() references', () => {
expect(await getBg('.css-url-relative')).toMatch(assetMatch)
})

test('image-set relative', async () => {
let imageSet = await getBg('.css-image-set-relative')
imageSet.split(', ').forEach((s) => {
expect(s).toMatch(assetMatch)
})
})

test('image-set without the url() call', async () => {
let imageSet = await getBg('.css-image-set-without-url-call')
imageSet.split(', ').forEach((s) => {
expect(s).toMatch(assetMatch)
})
})

test('relative in @import', async () => {
expect(await getBg('.css-url-relative-at-imported')).toMatch(assetMatch)
})
Expand Down Expand Up @@ -117,6 +131,20 @@ describe('css url() references', () => {
}
})

describe('image', () => {
test('srcset', async () => {
const img = await page.$('.img-src-set')
const srcset = await img.getAttribute('srcset')
srcset.split(', ').forEach((s) => {
expect(s).toMatch(
isBuild
? /\/foo\/assets\/asset\.\w{8}\.png \d{1}x/
: /\.\/nested\/asset\.png \d{1}x/
)
})
})
})

describe('svg fragments', () => {
// 404 is checked already, so here we just ensure the urls end with #fragment
test('img url', async () => {
Expand Down
16 changes: 16 additions & 0 deletions packages/playground/assets/css/css-url.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@
background-size: 10px;
}

.css-image-set-relative {
background-image: -webkit-image-set(
url('../nested/asset.png') 1x,
url('../nested/asset.png') 2x
);
background-size: 10px;
}

.css-image-set-without-url-call {
background-image: -webkit-image-set(
'../nested/asset.png' 1x,
'../nested/asset.png' 2x
);
background-size: 10px;
}

.css-url-public {
background: url('/icon.png');
background-size: 10px;
Expand Down
20 changes: 20 additions & 0 deletions packages/playground/assets/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ <h2>CSS url references</h2>
<div class="css-url-relative">
<span style="background: #fff">CSS background (relative)</span>
</div>
<div class="css-image-set-relative">
<span style="background: #fff"
>CSS background with image-set() (relative)</span
>
</div>
<div class="css-image-set-without-url-call">
<span style="background: #fff"
>CSS background with image-set() (relative)</span
>
</div>
<div class="css-url-relative-at-imported">
<span style="background: #fff"
>CSS background (relative from @imported file in different dir)</span
Expand All @@ -56,6 +66,16 @@ <h2>CSS url references</h2>
<span style="background: #fff">CSS background (aliased)</span>
</div>

<h2>Image Src Set</h2>
<div>
<img
class="img-src-set"
src="./nested/asset.png"
srcset="./nested/asset.png 1x, ./nested/asset.png 2x"
alt=""
/>
</div>

<h2>SVG Fragments</h2>
<div>
<img
Expand Down
54 changes: 40 additions & 14 deletions packages/vite/src/node/plugins/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
generateCodeFrame,
isDataUrl,
isObject,
normalizePath
normalizePath,
processSrcSet
} from '../utils'
import { Plugin } from '../plugin'
import { ResolvedConfig } from '../config'
Expand Down Expand Up @@ -500,7 +501,7 @@ async function compileCSS(
// although at serve time it can work without processing, we do need to
// crawl them in order to register watch dependencies.
const needInlineImport = code.includes('@import')
const hasUrl = cssUrlRE.test(code)
const hasUrl = cssUrlRE.test(code) || cssImageSetRE.test(code)
const postcssConfig = await resolvePostcssConfig(config)
const lang = id.match(cssLangRE)?.[1]

Expand Down Expand Up @@ -698,6 +699,7 @@ type CssUrlReplacer = (
importer?: string
) => string | Promise<string>
const cssUrlRE = /url\(\s*('[^']+'|"[^"]+"|[^'")]+)\s*\)/
const cssImageSetRE = /image-set\(([^)]+)\)/

const UrlRewritePostcssPlugin: Postcss.PluginCreator<{
replacer: CssUrlReplacer
Expand All @@ -711,13 +713,16 @@ const UrlRewritePostcssPlugin: Postcss.PluginCreator<{
Once(root) {
const promises: Promise<void>[] = []
root.walkDecls((decl) => {
if (cssUrlRE.test(decl.value)) {
const isCssUrl = cssUrlRE.test(decl.value)
const isCssImageSet = cssImageSetRE.test(decl.value)
if (isCssUrl || isCssImageSet) {
const replacerForDecl = (rawUrl: string) => {
const importer = decl.source?.input.file
return opts.replacer(rawUrl, importer)
}
const rewriterToUse = isCssUrl ? rewriteCssUrls : rewriteCssImageSet
promises.push(
rewriteCssUrls(decl.value, replacerForDecl).then((url) => {
rewriterToUse(decl.value, replacerForDecl).then((url) => {
decl.value = url
})
)
Expand All @@ -737,19 +742,40 @@ function rewriteCssUrls(
): Promise<string> {
return asyncReplace(css, cssUrlRE, async (match) => {
let [matched, rawUrl] = match
let wrap = ''
const first = rawUrl[0]
if (first === `"` || first === `'`) {
wrap = first
rawUrl = rawUrl.slice(1, -1)
}
if (isExternalUrl(rawUrl) || isDataUrl(rawUrl) || rawUrl.startsWith('#')) {
return matched
}
return `url(${wrap}${await replacer(rawUrl)}${wrap})`
return await doUrlReplace(rawUrl, matched, replacer)
})
}

function rewriteCssImageSet(
css: string,
replacer: CssUrlReplacer
): Promise<string> {
return asyncReplace(css, cssImageSetRE, async (match) => {
let [matched, rawUrl] = match
const url = await processSrcSet(rawUrl, ({ url }) =>
doUrlReplace(url, matched, replacer)
)
return `image-set(${url})`
})
}
async function doUrlReplace(
rawUrl: string,
matched: string,
replacer: CssUrlReplacer
) {
let wrap = ''
const first = rawUrl[0]
if (first === `"` || first === `'`) {
wrap = first
rawUrl = rawUrl.slice(1, -1)
}
if (isExternalUrl(rawUrl) || isDataUrl(rawUrl) || rawUrl.startsWith('#')) {
return matched
}

return `url(${wrap}${await replacer(rawUrl)}${wrap})`
}

let CleanCSS: any

async function minifyCSS(css: string, config: ResolvedConfig) {
Expand Down
13 changes: 10 additions & 3 deletions packages/vite/src/node/plugins/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
cleanUrl,
isExternalUrl,
isDataUrl,
generateCodeFrame
generateCodeFrame,
processSrcSet
} from '../utils'
import { ResolvedConfig } from '../config'
import MagicString from 'magic-string'
Expand Down Expand Up @@ -69,7 +70,7 @@ export const assetAttrsConfig: Record<string, string[]> = {
link: ['href'],
video: ['src', 'poster'],
source: ['src'],
img: ['src'],
img: ['src', 'srcset'],
image: ['xlink:href', 'href'],
use: ['xlink:href', 'href']
}
Expand Down Expand Up @@ -233,7 +234,13 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
for (const attr of assetUrls) {
const value = attr.value!
try {
const url = await urlToBuiltUrl(value.content, id, config, this)
const url =
attr.name === 'srcset'
? await processSrcSet(value.content, ({ url }) =>
urlToBuiltUrl(url, id, config, this)
)
: await urlToBuiltUrl(value.content, id, config, this)

s.overwrite(
value.loc.start.offset,
value.loc.end.offset,
Expand Down
35 changes: 35 additions & 0 deletions packages/vite/src/node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,38 @@ export function ensureWatchedFile(
watcher.add(path.resolve(file))
}
}

interface ImageCandidate {
url: string
descriptor: string
}
const escapedSpaceCharacters = /( |\\t|\\n|\\f|\\r)+/g
export async function processSrcSet(
srcs: string,
replacer: (arg: ImageCandidate) => Promise<string>
) {
const imageCandidates: ImageCandidate[] = srcs.split(',').map((s) => {
const [url, descriptor] = s
.replace(escapedSpaceCharacters, ' ')
.trim()
.split(' ', 2)
return { url, descriptor }
})

const ret = await Promise.all(
imageCandidates.map(async ({ url, descriptor }) => {
return {
url: await replacer({ url, descriptor }),
descriptor
}
})
)

const url = ret.reduce((prev, { url, descriptor }, index) => {
descriptor = descriptor || ''
return (prev +=
url + ` ${descriptor}${index === ret.length - 1 ? '' : ', '}`)
}, '')

return url
}

0 comments on commit 0f17a74

Please sign in to comment.