Skip to content

Commit 9bf475e

Browse files
committed
enhanced-img: add config options
1 parent 2cf810f commit 9bf475e

File tree

3 files changed

+105
-29
lines changed

3 files changed

+105
-29
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/enhanced-img': minor
3+
---
4+
5+
feat: add configuration options

packages/enhanced-img/src/index.js

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,42 @@ import { imagetools } from 'vite-imagetools';
33
import { image_plugin } from './vite-plugin.js';
44

55
/**
6+
* @param {import('types/index.js').VitePluginOptions} [opts]
67
* @returns {import('vite').Plugin[]}
78
*/
8-
export function enhancedImages() {
9-
const imagetools_instance = imagetools_plugin();
9+
export function enhancedImages(opts) {
10+
const imagetools_instance = imagetools_plugin(opts);
1011
return !process.versions.webcontainer
1112
? [image_plugin(imagetools_instance), imagetools_instance]
1213
: [];
1314
}
1415

1516
/**
16-
* @param {import('sharp').Metadata} meta
17-
* @returns {string}
17+
* @param {import('types/index.js').VitePluginOptions} [opts]
18+
* @returns {import('vite').Plugin}
1819
*/
19-
function fallback_format(meta) {
20-
if (meta.pages && meta.pages > 1) {
21-
return meta.format === 'tiff' ? 'tiff' : 'gif';
22-
}
23-
if (meta.hasAlpha) {
24-
return 'png';
25-
}
26-
return 'jpg';
27-
}
20+
function imagetools_plugin(opts) {
21+
const get_formats = opts?.defaultFormats ?? default_formats;
22+
const get_widths = opts?.defaultWidths ?? default_widths;
2823

29-
function imagetools_plugin() {
3024
/** @type {Partial<import('vite-imagetools').VitePluginOptions>} */
3125
const imagetools_opts = {
32-
defaultDirectives: async ({ pathname, searchParams: qs }, metadata) => {
33-
if (!qs.has('enhanced')) return new URLSearchParams();
26+
...opts?.imagetools,
27+
28+
defaultDirectives: async (url, metadata) => {
29+
const { pathname, searchParams: qs } = url;
30+
31+
if (!qs.has('enhanced')) {
32+
if (typeof opts?.imagetools?.defaultDirectives === 'function') {
33+
return opts.imagetools.defaultDirectives(url, metadata);
34+
}
35+
36+
if (opts?.imagetools?.defaultDirectives) {
37+
return opts.imagetools.defaultDirectives;
38+
}
39+
40+
return new URLSearchParams();
41+
}
3442

3543
const meta = await metadata();
3644

@@ -42,12 +50,10 @@ function imagetools_plugin() {
4250
return new URLSearchParams();
4351
}
4452

45-
const { widths, kind } = get_widths(width, qs.get('imgSizes'));
4653
return new URLSearchParams({
4754
as: 'picture',
48-
format: `avif;webp;${fallback_format(meta)}`,
49-
w: widths.join(';'),
50-
...(kind === 'x' && !qs.has('w') && { basePixels: widths[0].toString() })
55+
format: get_formats(meta),
56+
...get_widths(width, qs.get('imgSizes'))
5157
});
5258
},
5359
namedExports: false
@@ -59,12 +65,28 @@ function imagetools_plugin() {
5965
return imagetools(imagetools_opts);
6066
}
6167

68+
/**
69+
* @param {import('sharp').Metadata} meta
70+
* @returns {string}
71+
*/
72+
function default_formats(meta) {
73+
let fallback = 'jpg';
74+
75+
if (meta.pages && meta.pages > 1) {
76+
fallback = meta.format === 'tiff' ? 'tiff' : 'gif';
77+
} else if (meta.hasAlpha) {
78+
fallback = 'png';
79+
}
80+
81+
return `avif;webp;${fallback}`;
82+
}
83+
6284
/**
6385
* @param {number} width
6486
* @param {string | null} sizes
65-
* @returns {{ widths: number[]; kind: 'w' | 'x' }}
87+
* @returns {{ w: string; basePixels?: string }}
6688
*/
67-
function get_widths(width, sizes) {
89+
function default_widths(width, sizes) {
6890
// We don't really know what the user wants here. But if they have an image that's really big
6991
// then we can probably assume they're always displaying it full viewport/breakpoint.
7092
// If the user is displaying a responsive image then the size usually doesn't change that much
@@ -79,9 +101,7 @@ function get_widths(width, sizes) {
79101
// https://screensiz.es/
80102
// https://gs.statcounter.com/screen-resolution-stats (note: logical. we want physical)
81103
// Include 1080 because lighthouse uses a moto g4 with 360 logical pixels and 3x pixel ratio.
82-
const widths = [540, 768, 1080, 1366, 1536, 1920, 2560, 3000, 4096, 5120];
83-
widths.push(width);
84-
return { widths, kind: 'w' };
104+
return { w: `540;768;1080;1366;1536;1920;2560;3000;4096;5120${width}` };
85105
}
86106

87107
// Don't need more than 2x resolution. Note that due to this optimization, pixel density
@@ -94,5 +114,6 @@ function get_widths(width, sizes) {
94114
// data. Even true 3x resolution screens are wasteful as the human eye cannot see that level of
95115
// detail without something like a magnifying glass.
96116
// https://blog.twitter.com/engineering/en_us/topics/infrastructure/2019/capping-image-fidelity-on-ultra-high-resolution-devices.html
97-
return { widths: [Math.round(width / 2), width], kind: 'x' };
117+
const small_width = Math.round(width / 2).toString();
118+
return { w: `${small_width};${width}`, basePixels: small_width };
98119
}
Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,59 @@
11
import type { HTMLImgAttributes } from 'svelte/elements';
22
import type { Plugin } from 'vite';
3-
import type { Picture } from 'vite-imagetools';
3+
import type { Picture, VitePluginOptions as ImagetoolsOptions } from 'vite-imagetools';
44
import './ambient.js';
5+
import { Metadata } from 'sharp';
56

6-
export { Picture };
7+
export type { Picture };
8+
9+
export type VitePluginOptions = {
10+
/**
11+
* Get the default formats for enhanced images.
12+
*
13+
* @param meta Metadata for the source image.
14+
* @returns A ';'-separated list of output formats like 'avif;webp;jpg'.
15+
*
16+
* The default value is the following function:
17+
*
18+
* ```
19+
* (meta) => {
20+
* let fallback = 'jpg';
21+
* if (meta.pages && meta.pages > 1) {
22+
* fallback = meta.format === 'tiff' ? 'tiff' : 'gif';
23+
* } else if (meta.hasAlpha) {
24+
* fallback = 'png';
25+
* }
26+
* return `avif;webp;${fallback}`;
27+
* }
28+
* ```
29+
*/
30+
defaultFormats?: (meta: Metadata) => string;
31+
/**
32+
* Get the default widths for enhanced images.
33+
*
34+
* @param width Original image width in physical pixels.
35+
* @param sizes The `sizes` attribute value from `<enhanced:img>`, or `null` when not provided.
36+
* @returns Width configuration for `vite-imagetools` directives:
37+
* - `w`: A ';'-separated list of target output widths.
38+
* - `basePixels`: Optional base width used to generate pixel-density (`x`) descriptors.
39+
*
40+
* The default value is the following function:
41+
*
42+
* ```
43+
* (width, sizes) => {
44+
* if (sizes) {
45+
* return { w: `540;768;1080;1366;1536;1920;2560;3000;4096;5120${width}` };
46+
* }
47+
*
48+
* const small_width = Math.round(width / 2).toString();
49+
* return { w: `${small_width};${width}`, basePixels: small_width };
50+
* }
51+
* ```
52+
*/
53+
defaultWidths?: (width: number, sizes: string | null) => { w: string; basePixels?: string };
54+
/** Options for the 'vite-imagetools' plugin */
55+
imagetools?: ImagetoolsOptions;
56+
};
757

858
type EnhancedImgAttributes = Omit<HTMLImgAttributes, 'src'> & { src: string | Picture };
959

@@ -14,4 +64,4 @@ declare module 'svelte/elements' {
1464
}
1565
}
1666

17-
export function enhancedImages(): Promise<Plugin[]>;
67+
export function enhancedImages(opts?: VitePluginOptions): Promise<Plugin[]>;

0 commit comments

Comments
 (0)