Runtime
@srcset/runtime is the only part of srcset that reaches the browser: it turns generated variants into DOM attributes. Zero dependencies, tree-shakable, and the whole public surface is tested against a 1 kB budget.
Everything else in the project speaks its language: the image modules of the bundler integrations, the baked modules of the cli and the proxy adapters all produce arrays of SrcSetEntry, and the framework components are built on the two functions below.
Install it as a regular dependency, not a dev one - it ships:
pnpm add @srcset/runtimeyarn add @srcset/runtimenpm i @srcset/runtimeSrcSetEntry
Section titled “SrcSetEntry”One generated image variant:
| Field | Type | Description |
|---|---|---|
id | string | Id of the variant: the format plus the width by default, e.g. webp600. The key under which the variant sits in srcMap; a build-time integration can change the scheme with resourceId. |
format | ImageFormat | avif, webp, jpg, png, gif or svg. |
type | string | Mime type of the variant, e.g. image/webp. What the <source> grouping runs on. |
width | number | Width in pixels. Becomes the w descriptor. |
height | number | undefined | Height in pixels. Optional: a build-time variant knows it, a runtime-built one - an imgproxy or Cloudflare url - does not. |
url | string | Url of the variant. |
import type { SrcSetEntry } from '@srcset/runtime'The srcSet export of an image module is a SrcSetEntry[], and its src export is the one entry the module selected for the <img> - by default the variant matching the source format and width, or the first generated one when there is no such variant.
getImageProps
Section titled “getImageProps”getImageProps( src: SrcSetEntry | undefined, srcSet: SrcSetEntry[] | string | undefined): ImageSrcPropsBuilds the two attributes of an <img> - the exported ImageSrcProps is { src?: string, srcSet?: string }, both optional:
import { getImageProps } from '@srcset/runtime'import { src, srcSet } from './photo.jpg'
getImageProps(src, srcSet)// {// src: '/photo.jpg',// srcSet: '/photo.jpg 1200w, /photo@600w.jpg 600w'// }The srcSet string holds only the variants of the src format. A single <img> gets one srcset attribute, and a browser picks a candidate from it by width alone - mixing formats in there would hand avif to a browser that cannot decode it. Formats belong in <source> elements, which is what getSourceProps is for.
The rest of the behaviour:
- A
srcSetthat would duplicatesrcis omitted. With one variant of thesrcformat there is nothing to choose from, so the result is just{ src }and the markup stays clean. - A ready string passes through.
getImageProps(src, '/photo.jpg 1x, /photo@2x.jpg 2x')returns it untouched as thesrcSet, next to thesrcurl, which is how a component accepts both an array and a hand-writtensrcset. - Without
srcthe whole set becomes thesrcSetstring, formats included - for a<source>-like case where the caller has already narrowed the set. - Without either the result is an empty object, so nothing is rendered rather than an
src=""that requests the current page.
getSourceProps
Section titled “getSourceProps”getSourceProps(srcSet: SrcSetEntry[]): { type: string, srcSet: string }[]Builds the <source> elements of a <picture>, one per mime type:
getSourceProps(srcSet)// [// { type: 'image/avif', srcSet: '/photo.avif 1200w, /photo@600w.avif 600w' },// { type: 'image/webp', srcSet: '/photo.webp 1200w, /photo@600w.webp 600w' },// { type: 'image/jpeg', srcSet: '/photo.jpg 1200w, /photo@600w.jpg 600w' }// ]Two things happen there, and both matter.
Grouping by type. A browser picks the first <source> whose type it can decode, then chooses a candidate from that source’s srcset by width. So one <source> per format, each carrying every width of that format.
Ordering by format efficiency. Avif first, then webp, then every other mime type in the order it appeared in the array. The browser takes the first source it can decode and never looks further, so the order in the markup is the preference: a browser without avif falls to webp, one without either falls through to the <img>. The format order of the generation rule does not carry over here - avif and webp are hoisted to the front wherever they sat in it.
Put together, by hand:
import { getImageProps, getSourceProps } from '@srcset/runtime'import { src, srcSet } from './photo.jpg'
const sizes = '(max-width: 48rem) 100vw, 48rem'
export function Photo() { return ( <picture> {getSourceProps(srcSet).map(source => ( <source key={source.type} {...source} sizes={sizes} /> ))} <img {...getImageProps(src, srcSet)} sizes={sizes} alt='A photo' /> </picture> )}String helpers
Section titled “String helpers”toSrcSetString(srcSet: SrcSetEntry[]): stringtoDensitySrcSetString(srcSet: SrcSetEntry[]): stringtoSrcSetString builds the srcset value with width descriptors - url 1200w, url 600w - which is what both functions above use. A set of one returns the bare url with no descriptor: a single candidate is not a choice, and a lone w descriptor would drag sizes along with it.
toDensitySrcSetString builds it with pixel density descriptors instead, computed from the variant widths relative to the smallest one and rounded to two decimals:
import { filterBy, toDensitySrcSetString } from '@srcset/runtime'
toDensitySrcSetString(filterBy(srcSet, 'format', 'jpg'))// '/photo.jpg 2x, /photo@600w.jpg 1x'Use it for images of a fixed display size - a logo, an avatar, an icon - where the layout width is known and width descriptors would need a sizes attribute to say what it is. Pass one format at a time, the same way getImageProps does.
Utilities
Section titled “Utilities”| Function | Description |
|---|---|
groupBy(srcSet, field) | Group entries by a field into [value, entries] pairs, keeping the original order inside every group and between the groups. |
filterBy(srcSet, field, value) | Entries whose field equals the value, e.g. one format out of a mixed set. |
Both are typed against SrcSetEntry, so field autocompletes and value is checked against that field’s type.
mimeTypes maps a format to its mime type - avif, webp, jpg, png, gif, svg to image/avif, image/webp, image/jpeg, image/png, image/gif, image/svg+xml - and ImageFormat is the union of those six names. They are the browser-side copies of the ones in @srcset/core, so nothing pulls sharp into a bundle.