Skip to content

Commit 79a0a82

Browse files
authored
feat(loader): add webpack and Rspack loader (#6)
1 parent fd072e6 commit 79a0a82

18 files changed

Lines changed: 2282 additions & 9 deletions

‎packages/loader/README.md‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# @srcset/loader
2+
3+
[![ESM-only package][package]][package-url]
4+
[![NPM version][npm]][npm-url]
5+
[![Node version][node]][node-url]
6+
[![Dependencies status][deps]][deps-url]
7+
[![Install size][size]][size-url]
8+
[![Build status][build]][build-url]
9+
[![Coverage status][coverage]][coverage-url]
10+
11+
[package]: https://img.shields.io/badge/package-ESM--only-ffe536.svg
12+
[package-url]: https://nodejs.org/api/esm.html
13+
14+
[npm]: https://img.shields.io/npm/v/%40srcset%2Floader.svg
15+
[npm-url]: https://npmjs.com/package/@srcset/loader
16+
17+
[node]: https://img.shields.io/node/v/%40srcset%2Floader.svg
18+
[node-url]: https://nodejs.org
19+
20+
[deps]: https://img.shields.io/librariesio/release/npm/%40srcset%2Floader
21+
[deps-url]: https://libraries.io/npm/%40srcset%2Floader
22+
23+
[size]: https://packagephobia.com/badge?p=%40srcset%2Floader
24+
[size-url]: https://packagephobia.com/result?p=%40srcset%2Floader
25+
26+
[build]: https://img.shields.io/github/actions/workflow/status/TrigenSoftware/srcset/tests.yml?branch=main
27+
[build-url]: https://github.com/TrigenSoftware/srcset/actions
28+
29+
[coverage]: https://img.shields.io/codecov/c/github/TrigenSoftware/srcset.svg
30+
[coverage-url]: https://app.codecov.io/gh/TrigenSoftware/srcset
31+
32+
[Webpack](https://webpack.js.org/) and [Rspack](https://rspack.rs/) loader for generating responsive images.
33+
34+
- 🧩 Image imports are processed by default - no query markers required
35+
- 🌳 Tree-shakable image modules: unused exports are dropped from the bundle
36+
- 🌫 Blur-up placeholders inlined as data-urls
37+
- 🌐 Pluggable backends: encode with [sharp](https://sharp.pixelplumbing.com/) or build [imgproxy](https://npmjs.com/package/@srcset/imgproxy) / [Cloudflare](https://npmjs.com/package/@srcset/cloudflare) urls
38+
39+
## Install
40+
41+
```bash
42+
# pnpm
43+
pnpm add -D @srcset/loader @srcset/runtime
44+
# yarn
45+
yarn add -D @srcset/loader @srcset/runtime
46+
# npm
47+
npm i -D @srcset/loader @srcset/runtime
48+
```
49+
50+
## Usage
51+
52+
```js
53+
// webpack.config.js / rspack.config.js
54+
export default {
55+
module: {
56+
rules: [
57+
{
58+
test: /\.jpe?g$/i,
59+
use: {
60+
loader: '@srcset/loader',
61+
options: {
62+
rules: [
63+
{
64+
width: [1, 0.5],
65+
format: ['avif', 'webp', 'jpg']
66+
}
67+
],
68+
placeholder: true
69+
}
70+
}
71+
}
72+
]
73+
}
74+
}
75+
```
76+
77+
```ts
78+
import url, { src, srcSet, srcMap, placeholder } from './photo.jpg'
79+
80+
// url - url of the selected variant, e.g. '/assets/photo.f37e2d3a.jpg'
81+
// src - selected variant: { id: 'jpg1200', format: 'jpg', type: 'image/jpeg', width: 1200, height: 800, url }
82+
// srcSet - array of all generated variants
83+
// srcMap - id-to-url map, e.g. srcMap.webp600
84+
// placeholder - blur-up data-url, when the `placeholder` option is enabled
85+
86+
const img = `<img src="${url}" srcset="${srcSet.map(({ url, width }) => `${url} ${width}w`).join(', ')}">`
87+
```
88+
89+
The module is tree-shakable: import only what you use - the rest is dropped from the bundle.
90+
91+
## Documentation
92+
93+
For more details, guides and API references, check out the [documentation website](https://srcset.js.org/integrations/loader/).

‎packages/loader/client.d.ts‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/* oxlint-disable trigen/import-order */
2+
/**
3+
* Ambient module declarations for image imports handled by @srcset/loader.
4+
*
5+
* Usage: add to tsconfig `types` or reference directly:
6+
* /// <reference types="@srcset/loader/client" />
7+
*/
8+
9+
declare module '*.jpg' {
10+
import type { SrcSetEntry } from '@srcset/runtime'
11+
12+
const url: string
13+
14+
export const src: SrcSetEntry
15+
export const srcSet: SrcSetEntry[]
16+
export const srcMap: Record<string, string>
17+
export const placeholder: string | undefined
18+
export default url
19+
}
20+
21+
declare module '*.jpeg' {
22+
import type { SrcSetEntry } from '@srcset/runtime'
23+
24+
const url: string
25+
26+
export const src: SrcSetEntry
27+
export const srcSet: SrcSetEntry[]
28+
export const srcMap: Record<string, string>
29+
export const placeholder: string | undefined
30+
export default url
31+
}
32+
33+
declare module '*.png' {
34+
import type { SrcSetEntry } from '@srcset/runtime'
35+
36+
const url: string
37+
38+
export const src: SrcSetEntry
39+
export const srcSet: SrcSetEntry[]
40+
export const srcMap: Record<string, string>
41+
export const placeholder: string | undefined
42+
export default url
43+
}
44+
45+
declare module '*.webp' {
46+
import type { SrcSetEntry } from '@srcset/runtime'
47+
48+
const url: string
49+
50+
export const src: SrcSetEntry
51+
export const srcSet: SrcSetEntry[]
52+
export const srcMap: Record<string, string>
53+
export const placeholder: string | undefined
54+
export default url
55+
}
56+
57+
declare module '*.avif' {
58+
import type { SrcSetEntry } from '@srcset/runtime'
59+
60+
const url: string
61+
62+
export const src: SrcSetEntry
63+
export const srcSet: SrcSetEntry[]
64+
export const srcMap: Record<string, string>
65+
export const placeholder: string | undefined
66+
export default url
67+
}
68+
69+
declare module '*.gif' {
70+
import type { SrcSetEntry } from '@srcset/runtime'
71+
72+
const url: string
73+
74+
export const src: SrcSetEntry
75+
export const srcSet: SrcSetEntry[]
76+
export const srcMap: Record<string, string>
77+
export const placeholder: string | undefined
78+
export default url
79+
}

‎packages/loader/oxlint.config.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { defineConfig } from '@trigen/oxlint'
2+
import testConfig from '@trigen/oxlint-config/test'
3+
import tsTypeCheckedConfig from '@trigen/oxlint-config/typescript-type-checked'
4+
import rootConfig from '../../oxlint.config.ts'
5+
6+
export default defineConfig({
7+
extends: [
8+
rootConfig,
9+
tsTypeCheckedConfig,
10+
testConfig
11+
]
12+
})

‎packages/loader/package.json‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
{
2+
"name": "@srcset/loader",
3+
"type": "module",
4+
"version": "1.0.0",
5+
"description": "Webpack and Rspack loader for generating responsive images.",
6+
"author": {
7+
"name": "Dan Onoshko",
8+
"email": "danon0404@gmail.com",
9+
"url": "https://github.com/dangreen"
10+
},
11+
"license": "MIT",
12+
"homepage": "https://srcset.js.org/integrations/loader/",
13+
"funding": "https://ko-fi.com/dangreen",
14+
"repository": {
15+
"type": "git",
16+
"url": "https://github.com/TrigenSoftware/srcset.git",
17+
"directory": "packages/loader"
18+
},
19+
"bugs": {
20+
"url": "https://github.com/TrigenSoftware/srcset/issues"
21+
},
22+
"keywords": [
23+
"srcset",
24+
"image",
25+
"picture",
26+
"responsive",
27+
"webpack",
28+
"rspack",
29+
"loader",
30+
"webpack-loader"
31+
],
32+
"engines": {
33+
"node": ">=22"
34+
},
35+
"sideEffects": false,
36+
"exports": {
37+
"./package.json": "./package.json",
38+
".": "./src/index.ts",
39+
"./client": {
40+
"types": "./client.d.ts"
41+
}
42+
},
43+
"publishConfig": {
44+
"exports": {
45+
"./package.json": "./package.json",
46+
".": {
47+
"types": "./dist/index.d.ts",
48+
"default": "./dist/index.js"
49+
},
50+
"./client": {
51+
"types": "./client.d.ts"
52+
}
53+
},
54+
"directory": "package",
55+
"linkDirectory": false
56+
},
57+
"files": [
58+
"dist",
59+
"client.d.ts"
60+
],
61+
"scripts": {
62+
"clear:package": "del ./package",
63+
"clear:dist": "del ./dist",
64+
"clear": "del ./package ./dist ./coverage",
65+
"prepublishOnly": "run build clear:package clean-publish",
66+
"postpublish": "pnpm clear:package",
67+
"build": "tsc -p ./tsconfig.build.json",
68+
"lint": "oxlint",
69+
"format": "oxlint --fix",
70+
"test:unit": "vitest run --coverage",
71+
"test:unit:watch": "vitest watch",
72+
"test:types": "tsc --noEmit",
73+
"test": "run -p lint test:unit test:types"
74+
},
75+
"peerDependencies": {
76+
"@rspack/core": ">=1",
77+
"@srcset/runtime": "workspace:^",
78+
"webpack": ">=5"
79+
},
80+
"peerDependenciesMeta": {
81+
"@rspack/core": {
82+
"optional": true
83+
},
84+
"webpack": {
85+
"optional": true
86+
}
87+
},
88+
"dependencies": {
89+
"@srcset/bundler-utils": "workspace:^",
90+
"@srcset/core": "workspace:^",
91+
"p-limit": "^7.3.0"
92+
},
93+
"devDependencies": {
94+
"@rspack/core": "^2.1.4",
95+
"@srcset/runtime": "workspace:^",
96+
"@types/node": "^22.0.0",
97+
"memfs": "^4.64.0",
98+
"sharp": "^0.35.3",
99+
"webpack": "^5.108.4"
100+
}
101+
}

‎packages/loader/src/index.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* oxlint-disable import/no-default-export */
2+
export {
3+
default,
4+
raw
5+
} from './loader.ts'
6+
export type * from './types.ts'
7+
export type {
8+
SrcSetRule,
9+
SrcSetEntrySelect,
10+
SrcSetModuleEntry,
11+
ResourceIdFormatter,
12+
PlaceholderOptions
13+
} from '@srcset/bundler-utils'

‎packages/loader/src/limit.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { availableParallelism } from 'node:os'
2+
import type { LimitFunction } from 'p-limit'
3+
import pLimit from 'p-limit'
4+
5+
const limits = new Map<number | undefined, LimitFunction>()
6+
7+
/**
8+
* Get concurrency limit shared between loader runs.
9+
* @param concurrency - Concurrency limit.
10+
* @returns p-limit's limit function.
11+
*/
12+
export function getSharedLimit(concurrency?: number) {
13+
let limit = limits.get(concurrency)
14+
15+
if (!limit) {
16+
limit = pLimit(concurrency ?? availableParallelism())
17+
limits.set(concurrency, limit)
18+
}
19+
20+
return limit
21+
}

0 commit comments

Comments
 (0)