11# srcset-kit
22
3- Tools for working with the HTML ` srcset ` attribute.
3+ Small, dependency-free tools for parsing, validating, and serializing HTML
4+ ` srcset ` values.
5+
6+ ` srcset-kit ` is built for code that needs to understand responsive image
7+ candidates without taking shortcuts. It handles ` data: ` URLs with commas,
8+ normalizes whitespace, reports stable validation codes, and keeps URL strings
9+ exactly as you provided them.
10+
11+ ## Why
12+
13+ ` srcset ` looks simple until URLs contain commas:
14+
15+ ``` html
16+ <img srcset =" data:image/png;base64,AAAA 1x, /image@2x.png 2x" />
17+ ```
18+
19+ A plain ` split(",") ` breaks this value. ` srcset-kit ` tokenizes candidates with
20+ the descriptor boundary in mind, so inline image data, query strings, fragments,
21+ ports, relative URLs, and absolute URLs stay intact.
422
523## Installation
624
725``` sh
826pnpm add srcset-kit
927```
1028
11- ## Usage
29+ ``` sh
30+ npm install srcset-kit
31+ ```
32+
33+ ``` sh
34+ yarn add srcset-kit
35+ ```
36+
37+ ## Quick Start
38+
39+ ``` ts
40+ import {parse , stringify , validate } from " srcset-kit" ;
41+
42+ const candidates = parse (" image.png 1x, image@2x.png 2x" );
1243
13- ` srcset-kit ` parses, validates, and serializes HTML ` srcset ` attribute values
14- without splitting blindly on commas.
44+ const result = validate (candidates , {
45+ descriptor: " density" ,
46+ });
47+
48+ if (result .valid ) {
49+ stringify (result .candidates );
50+ // "image.png 1x, image@2x.png 2x"
51+ }
52+ ```
53+
54+ ## Parse
55+
56+ Parse a ` srcset ` string into clean candidate objects.
1557
1658``` ts
17- import { parse , stringify , validate } from " srcset-kit" ;
59+ import {parse } from " srcset-kit" ;
60+
61+ parse (" image.png" );
62+ // [{url: "image.png"}]
1863
1964parse (" image.png 1x, image@2x.png 2x" );
2065// [
21- // { url: "image.png", density: 1 },
22- // { url: "image@2x.png", density: 2 },
66+ // {url: "image.png", density: 1},
67+ // {url: "image@2x.png", density: 2},
2368// ]
2469
2570parse (" small.png 640w, large.png 1280w" );
2671// [
27- // { url: "small.png", width: 640 },
28- // { url: "large.png", width: 1280 },
72+ // {url: "small.png", width: 640},
73+ // {url: "large.png", width: 1280},
2974// ]
3075```
3176
32- ### Validate
77+ Parsing is tolerant by default. It keeps the usable URL candidate shape and
78+ leaves validation decisions to ` validate() ` .
3379
3480``` ts
35- const result = validate (" image.png 1x, image@2x.png 2x" );
81+ parse (" image.png 1x 640w, image@2x.png 2x" );
82+ // [
83+ // {url: "image.png"},
84+ // {url: "image@2x.png", density: 2},
85+ // ]
86+ ```
3687
37- if (result .valid ) {
38- result .descriptorType ;
39- // "density"
88+ Use strict parsing when invalid ` srcset ` values should throw.
89+
90+ ``` ts
91+ import {SrcsetValidationError , parse } from " srcset-kit" ;
92+
93+ try {
94+ parse (" image.png 1x 640w" , {strict: true });
95+ } catch (error ) {
96+ error instanceof SrcsetValidationError ;
97+ // true
4098}
4199```
42100
43- Validation returns structured issue codes instead of throwing for normal invalid
44- input.
101+ ## Data URLs
102+
103+ Commas inside URLs are preserved.
104+
105+ ``` ts
106+ parse (" data:image/png;base64,AAAA 1x, /image@2x.png 2x" );
107+ // [
108+ // {url: "data:image/png;base64,AAAA", density: 1},
109+ // {url: "/image@2x.png", density: 2},
110+ // ]
111+
112+ parse (" data:image/svg+xml,%3Csvg%3E%3C/svg%3E 1x, /image.svg 2x" );
113+ // [
114+ // {url: "data:image/svg+xml,%3Csvg%3E%3C/svg%3E", density: 1},
115+ // {url: "/image.svg", density: 2},
116+ // ]
117+ ```
118+
119+ ## Validate
120+
121+ Validate a string or already parsed candidates. Validation never throws for
122+ normal invalid input.
123+
124+ ``` ts
125+ import {validate } from " srcset-kit" ;
126+
127+ validate (" image.png 1x, image@2x.png 2x" );
128+ // {
129+ // valid: true,
130+ // descriptor: "density",
131+ // candidates: [
132+ // {url: "image.png", density: 1},
133+ // {url: "image@2x.png", density: 2},
134+ // ],
135+ // errors: [],
136+ // }
137+ ```
138+
139+ Invalid input returns stable issue codes.
45140
46141``` ts
47142validate (" image.png 1x, image@1x.png 1x" );
48143// {
49144// valid: false,
50- // descriptorType : "density",
145+ // descriptor : "density",
51146// candidates: [
52- // { url: "image.png", density: 1 },
53- // { url: "image@1x.png", density: 1 },
147+ // {url: "image.png", density: 1},
148+ // {url: "image@1x.png", density: 1},
54149// ],
55150// errors: [
56151// {
@@ -63,73 +158,180 @@ validate("image.png 1x, image@1x.png 1x");
63158// }
64159```
65160
66- Use ` descriptor ` to require a specific descriptor type for all candidates.
161+ Validate candidate arrays directly.
162+
163+ ``` ts
164+ validate ([
165+ {url: " image.png" , density: 1 },
166+ {url: " image@2x.png" , density: 2 },
167+ ]);
168+ // valid: true
169+ ```
170+
171+ ### Descriptor Policy
172+
173+ Use ` descriptor ` when a caller expects a specific candidate style.
67174
68175``` ts
69- validate (" small.png 640w, large.png 1280w" , { descriptor: " width" });
176+ validate (" small.png 640w, large.png 1280w" , {
177+ descriptor: " width" ,
178+ });
70179// valid: true
71180
72- validate (" image.png 1x, image@2x.png 2x" , { descriptor: " width" });
181+ validate (" image.png 1x, image@2x.png 2x" , {
182+ descriptor: " width" ,
183+ });
73184// valid: false
74185
75- validate (" image.png 1x, image@2x.png 2x" , { descriptor: " density" });
186+ validate (" image.png, image@2x.png 2x" , {
187+ descriptor: " density" ,
188+ });
76189// valid: true
77190```
78191
192+ A fallback candidate without a descriptor is treated as implicit ` 1x ` , so it is
193+ accepted by the density policy.
194+
195+ ### URL Context
196+
79197Use ` baseUrl ` when relative URLs should be checked in the context of a page URL.
80198Candidate URLs are not rewritten.
81199
82200``` ts
83- validate (" /image.png 1x" , { baseUrl: " https://example.com" });
201+ validate (" /images/photo.jpg 1x" , {
202+ baseUrl: " https://example.com/gallery/" ,
203+ });
84204// valid: true
205+
206+ parse (" /images/photo.jpg 1x" );
207+ // [{url: "/images/photo.jpg", density: 1}]
85208```
86209
87- ### Stringify
210+ ## Stringify
211+
212+ Serialize candidates into a normalized ` srcset ` string.
88213
89214``` ts
215+ import {stringify } from " srcset-kit" ;
216+
217+ stringify ([{url: " image.png" }]);
218+ // "image.png"
219+
220+ stringify ([{url: " image.png" , density: 1.5 }]);
221+ // "image.png 1.5x"
222+
223+ stringify ([{url: " image.png" , width: 640 }]);
224+ // "image.png 640w"
225+
90226stringify ([
91- { url: " image.png" , density: 1 },
92- { url: " image@2x.png" , density: 2 },
227+ {url: " image.png" , density: 1 },
228+ {url: " image@2x.png" , density: 2 },
93229]);
94230// "image.png 1x, image@2x.png 2x"
95231```
96232
97- ### Strict Parse
98-
99- ` parse() ` is tolerant by default. Use ` strict: true ` when invalid descriptor sets
100- should throw a package-specific ` SrcsetValidationError ` .
233+ Strict stringifying validates before serializing.
101234
102235``` ts
103- parse (" image.png 1x 640w" );
104- // [{ url: "image.png" }]
105-
106- parse (" image.png 1x 640w" , { strict: true });
236+ stringify (
237+ [
238+ {url: " image.png" , density: 1 },
239+ {url: " image-copy.png" , density: 1 },
240+ ],
241+ {strict: true },
242+ );
107243// throws SrcsetValidationError
108244```
109245
110- ### Data URLs
246+ ## Round Trips
111247
112- The parser keeps commas inside URLs, including ` data: ` URLs.
248+ Use ` parse() ` and ` stringify() ` together to normalize whitespace while preserving
249+ URLs.
113250
114251``` ts
115- parse (" data:image/png;base64,AAAA 1x, /image@2x.png 2x" );
252+ stringify (parse (" image.png 1x,\n image@2x.png 2x " ));
253+ // "image.png 1x, image@2x.png 2x"
254+
255+ parse (
256+ stringify ([
257+ {url: " data:image/png;base64,AAAA+BB==" , density: 1 },
258+ {url: " /image.png?w=100,h=200&fmt=webp" , density: 2 },
259+ ]),
260+ );
116261// [
117- // { url: "data:image/png;base64,AAAA", density: 1 },
118- // { url: "/image@2x .png", density: 2 },
262+ // {url: "data:image/png;base64,AAAA+BB== ", density: 1},
263+ // {url: "/image.png?w=100,h=200&fmt=webp ", density: 2},
119264// ]
120265```
121266
122- ## Development
267+ ## Validation Rules
123268
124- ``` sh
125- pnpm install
126- pnpm run verify
269+ ` srcset-kit ` checks the parts that matter when accepting or transforming
270+ ` srcset ` values:
271+
272+ - Empty ` srcset ` values are invalid.
273+ - Candidate URLs must be non-empty.
274+ - Relative URLs can be validated with ` baseUrl ` .
275+ - A candidate may be fallback, density-based, or width-based.
276+ - A candidate must not contain both density and width.
277+ - Width descriptors must be positive integers.
278+ - Density descriptors must be positive finite numbers.
279+ - Width and density descriptors must not be mixed in one candidate set.
280+ - Duplicate descriptors are invalid.
281+ - A fallback candidate is equivalent to ` 1x ` .
282+ - Descriptor policy mismatches are reported with stable issue codes.
283+
284+ ## API
285+
286+ ``` ts
287+ import {
288+ parse ,
289+ stringify ,
290+ validate ,
291+ SrcsetError ,
292+ SrcsetValidationError ,
293+ type DescriptorType ,
294+ type ParseOptions ,
295+ type SrcsetCandidate ,
296+ type SrcsetValidationIssue ,
297+ type StringifyOptions ,
298+ type ValidateOptions ,
299+ type ValidationResult ,
300+ } from " srcset-kit" ;
301+ ```
302+
303+ ``` ts
304+ type SrcsetCandidate =
305+ | {url: string ; density: number }
306+ | {url: string ; width: number }
307+ | {url: string };
308+
309+ type ParseOptions = {
310+ strict? : boolean ;
311+ };
312+
313+ type ValidateOptions = {
314+ baseUrl? : string | URL ;
315+ descriptor? : " width" | " density" ;
316+ };
317+
318+ type StringifyOptions = {
319+ strict? : boolean ;
320+ };
127321```
128322
129323## Package
130324
131325- Runtime dependencies: none.
326+ - Works in Node and browser environments.
132327- Source language: TypeScript.
133- - Build tool: Rslib.
134- - Test runner: Rstest.
135328- Published formats: ESM, CommonJS, and TypeScript declarations.
329+ - Public API: ` parse ` , ` validate ` , ` stringify ` , types, and package-specific
330+ errors.
331+
332+ ## Development
333+
334+ ``` sh
335+ pnpm install
336+ pnpm run verify
337+ ```
0 commit comments