Skip to content

Commit

Permalink
Merge pull request #40 from chromaui/todd/srcsets
Browse files Browse the repository at this point in the history
Archive assets from img srcsets
  • Loading branch information
tevanoff authored Nov 22, 2023
2 parents ecd8082 + 1febd8a commit d35dbc3
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 0 deletions.
11 changes: 11 additions & 0 deletions __playwright-tests__/fixtures/asset-paths.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,16 @@
<div class="with-rewritten-background-img-inline"></div>
<div style="background: url('/img?url=fixtures/pink.png'); no-repeat center;"></div>
</div>

<h2>srcset</h2>

<img
class="border-2 border-black ml-1 w-48"
srcset="/img?url=fixtures/blue.png&cachebust=srcset 384w, /img?url=fixtures/pink.png&cachebust=srcset 1920w"
sizes="(min-width: 768px) 768px, 192px"
src="/img?url=fixtures/purple.png&cachebust=srcset"
/>
<p class="text-sm">blue < 768px <= pink</p>

</body>
</html>
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"fs-extra": "^11.1.1",
"mime": "^3.0.0",
"rrweb-snapshot": "^2.0.0-alpha.4",
"srcset": "^4.0.0",
"ts-dedent": "^2.2.0"
},
"peerDependencies": {
Expand Down
23 changes: 23 additions & 0 deletions src/write-archive/dom-snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ function createSnapshot(url1: string, url2: string, url3: string) {
return `{"type":0,"childNodes":[{"type":1,"name":"html","publicId":"","systemId":"","id":2},{"type":2,"tagName":"html","attributes":{},"childNodes":[{"type":2,"tagName":"head","attributes":{},"childNodes":[{"type":3,"textContent":" ","id":5},{"type":2,"tagName":"link","attributes":{"rel":"stylesheet","href":"/styles-test.css"},"childNodes":[],"id":6},{"type":3,"textContent":" ","id":7},{"type":2,"tagName":"style","attributes":{},"childNodes":[{"type":3,"textContent":".test1 { background-image: url(\\"${url1}\\"); }.test2 { background-image: url(\\"${url2}\\"); }.test2 { background-image: url(\\"${url3}\\"); }","isStyle":true,"id":9}],"id":8},{"type":3,"textContent":" ","id":10}],"id":4},{"type":3,"textContent":" ","id":11},{"type":2,"tagName":"body","attributes":{},"childNodes":[{"type":3,"textContent":" ","id":13},{"type":2,"tagName":"div","attributes":{"class":"image-container flex flex-wrap"},"childNodes":[{"type":3,"textContent":" ","id":15},{"type":2,"tagName":"img","attributes":{"src":"${url1}"},"childNodes":[],"id":16},{"type":3,"textContent":" ","id":17},{"type":2,"tagName":"img","attributes":{"src":"${url2}"},"childNodes":[],"id":18},{"type":3,"textContent":" ","id":19},{"type":2,"tagName":"img","attributes":{"src":"${url3}"},"childNodes":[],"id":20},{"type":3,"textContent":" ","id":21},{"type":2,"tagName":"div","attributes":{"style":"background: url('${url1}'); no-repeat center;"},"childNodes":[],"id":22},{"type":3,"textContent":" ","id":23},{"type":2,"tagName":"div","attributes":{"style":"background: url('${url2}'); no-repeat center;"},"childNodes":[],"id":24},{"type":3,"textContent":" ","id":25},{"type":2,"tagName":"div","attributes":{"style":"background: url('${url3}'); no-repeat center;"},"childNodes":[],"id":26},{"type":3,"textContent":" ","id":27}],"id":14},{"type":3,"textContent":" ","id":28}],"id":12}],"id":3}],"id":1}`;
}

function createSrcsetSnapshot(url1: string, url2: string, url3: string) {
return `{"type":2,"tagName":"img","attributes":{"srcset":"${url2} 384w, ${url3} 1920w","sizes":"(min-width: 768px) 768px, 192px","src":"${url1}"},"childNodes":[],"id":61}`;
}

const snapshot = createSnapshot(relativeUrl, externalUrl, queryUrl);
const expectedMappedSnapshot = createSnapshot(relativeUrl, externalUrl, queryUrlTransformed);

Expand All @@ -32,5 +36,24 @@ describe('DOMSnapshot', () => {

expect(mappedSnapshot).toEqual(snapshot);
});

it('maps img srcsets', async () => {
const domSnapshot = new DOMSnapshot(createSrcsetSnapshot(relativeUrl, externalUrl, queryUrl));

const mappedSnapshot = await domSnapshot.mapAssetPaths(sourceMap);

expect(mappedSnapshot).toEqual(
`{"type":2,"tagName":"img","attributes":{"src":"${queryUrlTransformed}"},"childNodes":[],"id":61}`
);
});

it('does not change img srcsets when no mapped asset found in source map', async () => {
const origSnapshot = createSrcsetSnapshot(relativeUrl, externalUrl, queryUrl);
const domSnapshot = new DOMSnapshot(origSnapshot);

const mappedSnapshot = await domSnapshot.mapAssetPaths(new Map<string, string>());

expect(mappedSnapshot).toEqual(origSnapshot);
});
});
});
34 changes: 34 additions & 0 deletions src/write-archive/dom-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* eslint-disable no-param-reassign */
import type { serializedNodeWithId } from 'rrweb-snapshot';
import { NodeType } from 'rrweb-snapshot';
import srcset from 'srcset';

// Matches `url(...)` function in CSS text, excluding data URLs
const CSS_URL_REGEX = /url\((?!['"]?(?:data):)['"]?([^'")]*)['"]?\)/gi;
Expand Down Expand Up @@ -63,6 +64,27 @@ export class DOMSnapshot {
const mappedCssText = this.mapCssUrls(cssText, sourceMap);
node.attributes._cssText = mappedCssText;
}

// When an image tag has the `srcset` attributes, the browser will choose one of the images
// from the `srcset` list to load at render time based on the viewport size. To support this,
// we parse the URLs in the `srcset` attribute and try to find a match in the asset map.
// If a match is found, we'll overwrite the `src` attribute with the mapped asset path,
// and we'll remove the `srcset` and `sizes` attributes because we'll only have captured
// the one asset that the browser decided to load when this was rendered. We don't want
// the browser to try to load one of the others when this snapshot is rendered in Chromatic
// because we won't have archived them.
if (node.tagName === 'img' && node.attributes.srcset) {
const srcsetValue = node.attributes.srcset as string;
const currentSrc = this.mapSrcsetUrls(srcsetValue, sourceMap);
if (currentSrc) {
node.attributes.src = currentSrc;

// Remove srcset attributes since we'll only have the one that
// loaded on render archived
delete node.attributes.srcset;
delete node.attributes.sizes;
}
}
}

return node;
Expand All @@ -88,6 +110,18 @@ export class DOMSnapshot {
return cssUrl;
});
}

private mapSrcsetUrls(srcsetValue: string, sourceMap: Map<string, string>) {
const parsedSrcset = srcset.parse(srcsetValue);
let currentSrc;
parsedSrcset.forEach((set) => {
if (sourceMap.has(set.url)) {
currentSrc = sourceMap.get(set.url);
}
});

return currentSrc;
}
}
/* eslint-enable no-underscore-dangle */
/* eslint-enable no-param-reassign */
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11920,6 +11920,11 @@ sprintf-js@~1.0.2:
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==

srcset@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/srcset/-/srcset-4.0.0.tgz#336816b665b14cd013ba545b6fe62357f86e65f4"
integrity sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==

sshpk@^1.14.1:
version "1.18.0"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028"
Expand Down

0 comments on commit d35dbc3

Please sign in to comment.