diff --git a/plugins/postcss-image-set-function/src/index.ts b/plugins/postcss-image-set-function/src/index.ts index 08267d364..7dd71e394 100644 --- a/plugins/postcss-image-set-function/src/index.ts +++ b/plugins/postcss-image-set-function/src/index.ts @@ -37,6 +37,8 @@ const creator: PluginCreator<{ preserve: boolean, oninvalid: string }> = (opts?: } // process every image-set() function + const imageSetFunctions = []; + valueAST.walk((node) => { if (node.type !== 'function') { return; @@ -68,14 +70,19 @@ const creator: PluginCreator<{ preserve: boolean, oninvalid: string }> = (opts?: return x.type !== 'comment' && x.type !== 'space'; }); - processImageSet(relevantNodes, decl, { - decl, - oninvalid, - preserve, - result: result, - postcss: postcss, + imageSetFunctions.push({ + imageSetFunction: node, + imageSetOptionNodes: relevantNodes, }); }); + + processImageSet(imageSetFunctions, decl, { + decl, + oninvalid, + preserve, + result: result, + postcss: postcss, + }); }, }; }; diff --git a/plugins/postcss-image-set-function/src/lib/get-image.ts b/plugins/postcss-image-set-function/src/lib/get-image.ts index c82775962..b4b474241 100644 --- a/plugins/postcss-image-set-function/src/lib/get-image.ts +++ b/plugins/postcss-image-set-function/src/lib/get-image.ts @@ -10,7 +10,7 @@ export function getImage(node) { } if (node.type === 'string') { - return valueParser.stringify(node); + return 'url('+valueParser.stringify(node)+')'; } if ( diff --git a/plugins/postcss-image-set-function/src/lib/process-image-set.ts b/plugins/postcss-image-set-function/src/lib/process-image-set.ts index 2fc2c4415..a1d46d233 100644 --- a/plugins/postcss-image-set-function/src/lib/process-image-set.ts +++ b/plugins/postcss-image-set-function/src/lib/process-image-set.ts @@ -4,48 +4,78 @@ import { getImage } from './get-image'; import { getMedia, getMediaDPI } from './get-media'; import { handleInvalidation } from './handle-invalidation'; import type { AtRule, Container, Declaration, Result, Postcss } from 'postcss'; +import type { Node } from 'postcss-value-parser'; -export const processImageSet = (imageSetOptionNodes, decl: Declaration, opts: { decl: Declaration, oninvalid: string, preserve: boolean, result: Result, postcss: Postcss }) => { +type imageSetFunction = { + imageSetFunction: Node; + imageSetOptionNodes: Array; +} + +type mediaByDpr = { + atRule: AtRule; + value: string; +} + +export const processImageSet = (imageSetFunctions: Array, decl: Declaration, opts: { decl: Declaration, oninvalid: string, preserve: boolean, result: Result, postcss: Postcss }) => { const parent = decl.parent; - const mediasByDpr: Map = new Map(); - - const length = imageSetOptionNodes.length; - let index = -1; - - while (index < length) { - const comma = index < 0 ? true : getComma(imageSetOptionNodes[index]); - const value = getImage(imageSetOptionNodes[index + 1]); - const mediaDPI = getMediaDPI(imageSetOptionNodes[index + 2]); - - const media = getMedia(mediaDPI, opts.postcss); - - // handle invalidations - if (!comma) { - handleInvalidation(opts, 'expected a comma', valueParser.stringify(imageSetOptionNodes)); - return; - } else if (!value) { - handleInvalidation(opts, 'unexpected image', valueParser.stringify(imageSetOptionNodes)); - return; - } else if (!media || !mediaDPI || mediasByDpr.has(mediaDPI)) { - handleInvalidation(opts, 'unexpected resolution', valueParser.stringify(imageSetOptionNodes)); - return; + const mediasByDpr: Map = new Map(); + const declValue = decl.value; + + for (let i = 0; i < imageSetFunctions.length; i++) { + const { imageSetFunction, imageSetOptionNodes } = imageSetFunctions[i]; + const mediasByDprPerItem: Map = new Map(); + + const length = imageSetOptionNodes.length; + let index = -1; + + while (index < length) { + const comma = index < 0 ? true : getComma(imageSetOptionNodes[index]); + const value = getImage(imageSetOptionNodes[index + 1]); + const mediaDPI = getMediaDPI(imageSetOptionNodes[index + 2]); + + const media = getMedia(mediaDPI, opts.postcss); + + // handle invalidations + if (!comma) { + handleInvalidation(opts, 'expected a comma', valueParser.stringify(imageSetOptionNodes)); + return; + } else if (!value) { + handleInvalidation(opts, 'unexpected image', valueParser.stringify(imageSetOptionNodes)); + return; + } else if (!media || !mediaDPI || mediasByDprPerItem.has(mediaDPI)) { + handleInvalidation(opts, 'unexpected resolution', valueParser.stringify(imageSetOptionNodes)); + return; + } + + mediasByDprPerItem.set(mediaDPI, media); + + if (mediasByDpr.has(mediaDPI)) { + const m = mediasByDpr.get(mediaDPI); + m.value = m.value.replace(valueParser.stringify(imageSetFunction), value.trim()); + mediasByDpr.set(mediaDPI, m); + } else { + mediasByDpr.set(mediaDPI, { + atRule: media, + value: declValue.replace(valueParser.stringify(imageSetFunction), value.trim()), + }); + } + + index += 3; } + } - mediasByDpr.set(mediaDPI, media); - + for (const { atRule, value } of mediasByDpr.values()) { // prepare @media { decl: } const parentClone = parent.clone().removeAll(); - const declClone = decl.clone({ value: value.trim() }); + const declClone = decl.clone({ value: value }); parentClone.append(declClone); - media.append(parentClone); - - index += 3; + atRule.append(parentClone); } const medias = Array.from(mediasByDpr.keys()) .sort((a, b) => a - b) - .map(params => mediasByDpr.get(params)); + .map(params => mediasByDpr.get(params).atRule); if (!medias.length) { return; diff --git a/plugins/postcss-image-set-function/test/basic.css b/plugins/postcss-image-set-function/test/basic.css index 506ccc72c..438cfbd95 100644 --- a/plugins/postcss-image-set-function/test/basic.css +++ b/plugins/postcss-image-set-function/test/basic.css @@ -113,6 +113,14 @@ } } +.list-1 { + background-image: linear-gradient(#4444, #8888), image-set(url(img.png) 1x, url(img@2x.png) 2x); +} + +.list-2 { + background-image: image-set(url(img-a.png) 1x, url(img-a@2x.png) 2x), image-set(url(img-b.png) 1x, url(img-b@2x.png) 2x); +} + .test-valid-data-url { background-image: image-set(url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==) 1x, url(img/test-2x.png) 2x); } diff --git a/plugins/postcss-image-set-function/test/basic.expect.css b/plugins/postcss-image-set-function/test/basic.expect.css index 242ce0d1f..c878653d5 100644 --- a/plugins/postcss-image-set-function/test/basic.expect.css +++ b/plugins/postcss-image-set-function/test/basic.expect.css @@ -156,13 +156,13 @@ .test-no-url { order: 1; - background-image: "img/test.png"; + background-image: url("img/test.png"); background-image: image-set( "img/test.png" 1x, "img/test-2x.png" 2x ); order: 2; - background-image: "img/test.png"; + background-image: url("img/test.png"); background-image: image-set( "img/test.png" 1x, "img/test-2x.png" 2x, @@ -174,21 +174,21 @@ @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .test-no-url { - background-image: "img/test-2x.png"; + background-image: url("img/test-2x.png"); } } @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) { .test-no-url { - background-image: "my-img-print.png"; + background-image: url("my-img-print.png"); } } @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .test-no-url { - background-image: "img/test-2x.png"; + background-image: url("img/test-2x.png"); } } @@ -259,6 +259,30 @@ } } +.list-1 { + background-image: linear-gradient(#4444, #8888), url(img.png); + background-image: linear-gradient(#4444, #8888), image-set(url(img.png) 1x, url(img@2x.png) 2x); +} + +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + +.list-1 { + background-image: linear-gradient(#4444, #8888), url(img@2x.png); +} +} + +.list-2 { + background-image: url(img-a.png), url(img-b.png); + background-image: image-set(url(img-a.png) 1x, url(img-a@2x.png) 2x), image-set(url(img-b.png) 1x, url(img-b@2x.png) 2x); +} + +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + +.list-2 { + background-image: url(img-a@2x.png), url(img-b@2x.png); +} +} + .test-valid-data-url { background-image: url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==); background-image: image-set(url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==) 1x, url(img/test-2x.png) 2x); diff --git a/plugins/postcss-image-set-function/test/basic.no-preserve.expect.css b/plugins/postcss-image-set-function/test/basic.no-preserve.expect.css index b27f333b4..d62889ad1 100644 --- a/plugins/postcss-image-set-function/test/basic.no-preserve.expect.css +++ b/plugins/postcss-image-set-function/test/basic.no-preserve.expect.css @@ -118,30 +118,30 @@ .test-no-url { order: 1; - background-image: "img/test.png"; + background-image: url("img/test.png"); order: 2; - background-image: "img/test.png"; + background-image: url("img/test.png"); order: 3; } @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .test-no-url { - background-image: "img/test-2x.png"; + background-image: url("img/test-2x.png"); } } @media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) { .test-no-url { - background-image: "my-img-print.png"; + background-image: url("my-img-print.png"); } } @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .test-no-url { - background-image: "img/test-2x.png"; + background-image: url("img/test-2x.png"); } } @@ -197,6 +197,28 @@ } } +.list-1 { + background-image: linear-gradient(#4444, #8888), url(img.png); +} + +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + +.list-1 { + background-image: linear-gradient(#4444, #8888), url(img@2x.png); +} +} + +.list-2 { + background-image: url(img-a.png), url(img-b.png); +} + +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + +.list-2 { + background-image: url(img-a@2x.png), url(img-b@2x.png); +} +} + .test-valid-data-url { background-image: url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==); }