├── .gitignore ├── README.md ├── index.test.ts ├── index.ts ├── jsr.json ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | dist*/ 2 | node_modules 3 | yarn.lock 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # srcset-parse 2 | 3 | An extra small [`srcset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-srcset) 4 | attribute parser compliant with [the latest spec](https://html.spec.whatwg.org/multipage/images.html#srcset-attributes). 5 | It's is inspired by the [**srcset** package](https://github.com/sindresorhus/srcset) and has following features: 6 | 7 | - Extra tiny, only ~150B; 8 | - Supports URLs that contain commas; 9 | - Zero or one descriptors per image definitions are supported (like `2x` or `100w`, but not both!); 10 | 11 | Example usage: 12 | 13 | ```js 14 | import parse from "srcset-parse"; 15 | 16 | /** 17 | * [ 18 | * { url: "hifi-cat.jpeg", density: 3 }, 19 | * { url: "lowfi-cat.jpeg", width: 128 }, 20 | * ] 21 | */ 22 | parse("hifi-cat.jpeg 3x, lowfi-cat.jpeg 128w"); 23 | ``` 24 | 25 | ### Using with TypeScript 26 | 27 | The library is written in TypeScript, so you can import types if needed: 28 | 29 | ```js 30 | import parse, { ImageCandidate } from "srcset-parse"; 31 | 32 | const result: ImageCandidate[] = parse("icon@2x.png 2x, icon.png 1x"); 33 | ``` 34 | 35 | ### Platform support 36 | 37 | This library is written according to the _ES2015 standard_. Make sure your platform 38 | supports it, or your project is configured to transpile external modules. 39 | -------------------------------------------------------------------------------- /index.test.ts: -------------------------------------------------------------------------------- 1 | import parse from "./index"; 2 | 3 | import { test } from "uvu"; 4 | import * as assert from "uvu/assert"; 5 | 6 | test("parses srcset strings", () => { 7 | const srcset = "cat-@2x.jpeg 2x, dog.jpeg 100w"; 8 | 9 | assert.equal(parse(srcset), [ 10 | { url: "cat-@2x.jpeg", density: 2 }, 11 | { url: "dog.jpeg", width: 100 }, 12 | ]); 13 | }); 14 | 15 | test("ingores extra whitespaces", () => { 16 | const srcset = ` 17 | foo-bar.png 2x , 18 | bar-baz.png 100w`; 19 | 20 | assert.equal(parse(srcset), [ 21 | { url: "foo-bar.png", density: 2 }, 22 | { url: "bar-baz.png", width: 100 }, 23 | ]); 24 | }); 25 | 26 | test("properly parses float descriptors", () => { 27 | const srcset = "cat.jpeg 2.4x, dog.jpeg 1.5x"; 28 | 29 | assert.equal(parse(srcset), [ 30 | { url: "cat.jpeg", density: 2.4 }, 31 | { url: "dog.jpeg", density: 1.5 }, 32 | ]); 33 | }); 34 | 35 | test("supports URLs that contain comma", () => { 36 | const srcset = ` 37 | https://foo.bar/w=100,h=200/dog.png 100w, 38 | https://baz.bar/cat.png?meow=yes 1024w 39 | `; 40 | 41 | assert.equal(parse(srcset), [ 42 | { url: "https://foo.bar/w=100,h=200/dog.png", width: 100 }, 43 | { url: "https://baz.bar/cat.png?meow=yes", width: 1024 }, 44 | ]); 45 | }); 46 | 47 | test("supports single URLs", () => { 48 | const srcset = "/cat.jpg"; 49 | assert.equal(parse(srcset), [{ url: "/cat.jpg" }]); 50 | }); 51 | 52 | test("supports optional descriptors", () => { 53 | const srcset = "/cat.jpg, /dog.png 3x , /lol "; 54 | assert.equal(parse(srcset), [ 55 | { url: "/cat.jpg" }, 56 | { url: "/dog.png", density: 3 }, 57 | { url: "/lol" }, 58 | ]); 59 | }); 60 | 61 | test.run(); 62 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | // a regex for matching srcset segments 2 | const SRCSEG = /(\S*[^,\s])(\s+([\d.]+)(x|w))?/g; 3 | 4 | // defines how srcset descriptors translate into human-readable names 5 | const DescriptorNames = { w: "width", x: "density" } as const; 6 | 7 | type Descriptor = keyof typeof DescriptorNames; 8 | export type DescriptorName = (typeof DescriptorNames)[Descriptor]; 9 | 10 | /** 11 | * srcset definition consists of image candidates 12 | */ 13 | export type ImageCandidate = { url: string } & { 14 | [K in DescriptorName]?: number; 15 | }; 16 | 17 | /** 18 | * Parses an srcset string and returns an array of objects 19 | * 20 | * @param {string} an srcset string 21 | * @returns {ImageCandidate[]} 22 | */ 23 | const parse = (srcset: string): ImageCandidate[] => 24 | matchAll(srcset, SRCSEG).map(([, url, , value, modifier]): ImageCandidate => { 25 | let modKey = DescriptorNames[modifier]; 26 | 27 | // descriptor is optional 28 | return modKey ? { url, [modKey]: parseFloat(value) } : { url }; 29 | }); 30 | 31 | /** 32 | * Similar to String.prototype.matchAll, but returns an array 33 | * rather than the iterable. It also works everywhere, including IE11. 34 | * (`String.prototype.matchAll` doesn't). 35 | */ 36 | const matchAll = (str: string, regex: RegExp): RegExpExecArray[] => { 37 | let match = null, 38 | result = []; 39 | 40 | while ((match = regex.exec(str)) !== null) result.push(match); 41 | return result; 42 | }; 43 | 44 | export default parse; 45 | -------------------------------------------------------------------------------- /jsr.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@molefrog/srcset-parse", 3 | "version": "1.1.2", 4 | "exports": "./index.ts", 5 | "publish": { 6 | "include": ["./index.ts", "./README.md"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "srcset-parse", 3 | "description": "Extra tiny parser of srcset definitions", 4 | "license": "MIT", 5 | "author": "Alexey Taktarov ", 6 | "version": "1.1.0", 7 | "module": "dist/index.js", 8 | "types": "dist/index.d.ts", 9 | "main": "dist-cjs/index.js", 10 | "files": [ 11 | "dist", 12 | "dist-cjs" 13 | ], 14 | "size-limit": [ 15 | { 16 | "path": "dist/index.js", 17 | "limit": "160 B" 18 | } 19 | ], 20 | "scripts": { 21 | "test": "uvu -r ts-node/register", 22 | "build": "tsc --module es2015 && tsc --module commonjs --outDir dist-cjs", 23 | "prepublishOnly": "npm run build" 24 | }, 25 | "dependencies": {}, 26 | "devDependencies": { 27 | "@size-limit/preset-small-lib": "4.5.4", 28 | "size-limit": "4.5.4", 29 | "ts-node": "^8.10.2", 30 | "typescript": "^3.9.6", 31 | "uvu": "0.2.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2015", 5 | "declaration": true, 6 | "outDir": "./dist" 7 | }, 8 | "include": ["index.ts"] 9 | } 10 | --------------------------------------------------------------------------------