@lonik/oh-image 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +14 -1
- package/dist/plugin.d.ts +2 -46
- package/dist/plugin.js +41 -36
- package/dist/react.d.ts +23 -37
- package/dist/react.js +3 -7
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
export interface ImageSrc {
|
|
2
|
+
/** Original width of the source image in pixels */
|
|
3
|
+
width: number;
|
|
4
|
+
/** Original height of the source image in pixels */
|
|
5
|
+
height: number;
|
|
6
|
+
/** URL to the placeholder image (if placeholder was enabled) */
|
|
7
|
+
placeholderUrl?: string;
|
|
8
|
+
/** Array of responsive image sources at different breakpoints */
|
|
9
|
+
srcSets: string;
|
|
10
|
+
/** URL to the main processed image */
|
|
11
|
+
src: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
1
14
|
declare module "*?oh" {
|
|
2
|
-
const imageSrc:
|
|
15
|
+
const imageSrc: ImageSrc;
|
|
3
16
|
export default imageSrc;
|
|
4
17
|
}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -3,18 +3,10 @@ import * as vite0 from "vite";
|
|
|
3
3
|
import * as rollup0 from "rollup";
|
|
4
4
|
|
|
5
5
|
//#region src/plugin/types.d.ts
|
|
6
|
-
|
|
7
|
-
* Configuration options for the oh-image Vite plugin.
|
|
8
|
-
* Extends ImageOptions with all properties required, plus plugin-specific settings.
|
|
9
|
-
*/
|
|
10
|
-
interface PluginConfig extends Required<ImageOptions> {
|
|
6
|
+
interface PluginConfig extends Required<Pick<ImageOptions, "placeholder" | "bps" | "format">> {
|
|
11
7
|
/** Directory name where processed images will be output during build */
|
|
12
8
|
distDir: string;
|
|
13
9
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Options for image processing and transformation.
|
|
16
|
-
* Can be passed via query parameters or plugin configuration.
|
|
17
|
-
*/
|
|
18
10
|
interface ImageOptions {
|
|
19
11
|
/** Target width for the processed image in pixels */
|
|
20
12
|
width?: number | null;
|
|
@@ -26,44 +18,8 @@ interface ImageOptions {
|
|
|
26
18
|
blur?: number | boolean;
|
|
27
19
|
/** Whether to generate a placeholder image for lazy loading */
|
|
28
20
|
placeholder?: boolean;
|
|
29
|
-
/** Width of the placeholder image in pixels */
|
|
30
|
-
placeholderW?: number;
|
|
31
|
-
/** Height of the placeholder image in pixels */
|
|
32
|
-
placeholderH?: number;
|
|
33
|
-
/** Output format for the placeholder image */
|
|
34
|
-
placeholderF?: keyof FormatEnum;
|
|
35
|
-
/** Blur setting for the placeholder (true for default, or sigma value) */
|
|
36
|
-
placeholderB: boolean | number;
|
|
37
21
|
/** Breakpoints array - widths in pixels for responsive srcSet generation */
|
|
38
22
|
bps?: number[];
|
|
39
|
-
/** Output format for srcSet images */
|
|
40
|
-
srcSetsF: keyof FormatEnum;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Represents a single entry in the srcSet array.
|
|
44
|
-
* Used for responsive image loading at different viewport widths.
|
|
45
|
-
*/
|
|
46
|
-
interface ImageSrcSet {
|
|
47
|
-
/** Width descriptor (e.g., '640w', '1080w') */
|
|
48
|
-
width: string;
|
|
49
|
-
/** URL or path to the image at this breakpoint */
|
|
50
|
-
src: string;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* The processed image source object returned by the plugin.
|
|
54
|
-
* Contains all URLs and metadata needed for responsive image rendering.
|
|
55
|
-
*/
|
|
56
|
-
interface ImageSrc {
|
|
57
|
-
/** Original width of the source image in pixels */
|
|
58
|
-
width: number;
|
|
59
|
-
/** Original height of the source image in pixels */
|
|
60
|
-
height: number;
|
|
61
|
-
/** URL to the placeholder image (if placeholder was enabled) */
|
|
62
|
-
placeholderUrl?: string;
|
|
63
|
-
/** Array of responsive image sources at different breakpoints */
|
|
64
|
-
srcSets: ImageSrcSet[];
|
|
65
|
-
/** URL to the main processed image */
|
|
66
|
-
src: string;
|
|
67
23
|
}
|
|
68
24
|
//#endregion
|
|
69
25
|
//#region src/plugin/plugin.d.ts
|
|
@@ -81,4 +37,4 @@ declare function ohImage(options?: Partial<PluginConfig>): {
|
|
|
81
37
|
writeBundle(this: rollup0.PluginContext): Promise<void>;
|
|
82
38
|
};
|
|
83
39
|
//#endregion
|
|
84
|
-
export { type ImageOptions, type
|
|
40
|
+
export { type ImageOptions, type PluginConfig, ohImage };
|
package/dist/plugin.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { basename, dirname, extname, join, parse } from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
3
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import queryString from "query-string";
|
|
5
5
|
import sharp from "sharp";
|
|
6
6
|
import pLimit from "p-limit";
|
|
7
7
|
|
|
8
8
|
//#region src/plugin/utils.ts
|
|
9
|
-
function
|
|
10
|
-
|
|
9
|
+
async function getFileHash(filePath) {
|
|
10
|
+
const content = await readFile(filePath);
|
|
11
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
11
12
|
}
|
|
12
13
|
async function readFileSafe(path) {
|
|
13
14
|
try {
|
|
@@ -60,6 +61,9 @@ async function processImage(path, options) {
|
|
|
60
61
|
|
|
61
62
|
//#endregion
|
|
62
63
|
//#region src/plugin/plugin.ts
|
|
64
|
+
const DEFAULT_IMAGE_FORMAT = "webp";
|
|
65
|
+
const PLACEHOLDER_IMG_SIZE = 8;
|
|
66
|
+
const PLACEHOLDER_BLUR_QUALITY = 70;
|
|
63
67
|
const DEFAULT_CONFIGS = {
|
|
64
68
|
distDir: "oh-images",
|
|
65
69
|
bps: [
|
|
@@ -76,18 +80,10 @@ const DEFAULT_CONFIGS = {
|
|
|
76
80
|
1920
|
|
77
81
|
],
|
|
78
82
|
format: "webp",
|
|
79
|
-
|
|
80
|
-
width: null,
|
|
81
|
-
height: null,
|
|
82
|
-
placeholder: false,
|
|
83
|
-
placeholderH: 100,
|
|
84
|
-
placeholderW: 100,
|
|
85
|
-
placeholderB: true,
|
|
86
|
-
placeholderF: "webp",
|
|
87
|
-
srcSetsF: "webp"
|
|
83
|
+
placeholder: false
|
|
88
84
|
};
|
|
89
85
|
const PROCESS_KEY = "oh";
|
|
90
|
-
const SUPPORTED_IMAGE_FORMATS = /\.(jpe?g|png|webp|avif|gif|
|
|
86
|
+
const SUPPORTED_IMAGE_FORMATS = /\.(jpe?g|png|webp|avif|gif|svg)(\?.*)?$/i;
|
|
91
87
|
const DEV_DIR = "/@oh-images/";
|
|
92
88
|
function ohImage(options) {
|
|
93
89
|
let isBuild = false;
|
|
@@ -107,9 +103,8 @@ function ohImage(options) {
|
|
|
107
103
|
const fileId = basename(url);
|
|
108
104
|
return join(cacheDir, fileId);
|
|
109
105
|
}
|
|
110
|
-
function genIdentifier(uri, format, prefix) {
|
|
111
|
-
const
|
|
112
|
-
const uniqueFileId = `${prefix}-${getRandomString()}-${fileId}.${format}`;
|
|
106
|
+
function genIdentifier(uri, format, prefix, hash) {
|
|
107
|
+
const uniqueFileId = `${prefix}-${hash}-${basename(uri)}.${format}`;
|
|
113
108
|
if (!isBuild) return join(DEV_DIR, uniqueFileId);
|
|
114
109
|
return join(assetsDir, config.distDir, uniqueFileId);
|
|
115
110
|
}
|
|
@@ -155,15 +150,15 @@ function ohImage(options) {
|
|
|
155
150
|
const origin = parsed.path;
|
|
156
151
|
const { name, ext } = parse(parsed.path);
|
|
157
152
|
const metadata = await sharp(parsed.path).metadata();
|
|
153
|
+
const hash = await getFileHash(origin);
|
|
158
154
|
const mergedOptions = {
|
|
159
155
|
...config,
|
|
160
156
|
...parsed.options
|
|
161
157
|
};
|
|
162
|
-
const mainIdentifier = genIdentifier(name, mergedOptions.format ?? ext.slice(1), "main");
|
|
158
|
+
const mainIdentifier = genIdentifier(name, mergedOptions.format ?? ext.slice(1), "main", hash);
|
|
163
159
|
const mainEntry = {
|
|
164
160
|
width: mergedOptions.width,
|
|
165
161
|
height: mergedOptions.height,
|
|
166
|
-
blur: mergedOptions.blur,
|
|
167
162
|
format: mergedOptions.format,
|
|
168
163
|
origin
|
|
169
164
|
};
|
|
@@ -172,32 +167,42 @@ function ohImage(options) {
|
|
|
172
167
|
width: metadata.width,
|
|
173
168
|
height: metadata.height,
|
|
174
169
|
src: mainIdentifier,
|
|
175
|
-
srcSets:
|
|
170
|
+
srcSets: ""
|
|
176
171
|
};
|
|
177
172
|
if (parsed.options?.placeholder) {
|
|
178
|
-
|
|
173
|
+
let placeholderHeight = 0;
|
|
174
|
+
let placeholderWidth = 0;
|
|
175
|
+
if (metadata.width >= metadata.height) {
|
|
176
|
+
placeholderWidth = PLACEHOLDER_IMG_SIZE;
|
|
177
|
+
placeholderHeight = Math.max(Math.round(metadata.height / metadata.width * PLACEHOLDER_IMG_SIZE), 1);
|
|
178
|
+
} else {
|
|
179
|
+
placeholderWidth = Math.max(Math.round(metadata.width / metadata.height * PLACEHOLDER_IMG_SIZE), 1);
|
|
180
|
+
placeholderHeight = PLACEHOLDER_IMG_SIZE;
|
|
181
|
+
}
|
|
182
|
+
const placeholderIdentifier = genIdentifier(name, DEFAULT_IMAGE_FORMAT, "placeholder", hash);
|
|
179
183
|
const placeholderEntry = {
|
|
180
|
-
width:
|
|
181
|
-
height:
|
|
182
|
-
format:
|
|
183
|
-
blur:
|
|
184
|
+
width: placeholderWidth,
|
|
185
|
+
height: placeholderHeight,
|
|
186
|
+
format: DEFAULT_IMAGE_FORMAT,
|
|
187
|
+
blur: PLACEHOLDER_BLUR_QUALITY,
|
|
184
188
|
origin
|
|
185
189
|
};
|
|
186
190
|
imageEntries.set(placeholderIdentifier, placeholderEntry);
|
|
187
191
|
src.placeholderUrl = placeholderIdentifier;
|
|
188
192
|
}
|
|
189
|
-
if (mergedOptions.bps)
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
193
|
+
if (mergedOptions.bps) {
|
|
194
|
+
const srcSets = [];
|
|
195
|
+
for (const breakpoint of mergedOptions.bps) {
|
|
196
|
+
const srcSetIdentifier = genIdentifier(name, DEFAULT_IMAGE_FORMAT, `breakpoint-${breakpoint}`, hash);
|
|
197
|
+
const srcSetEntry = {
|
|
198
|
+
width: breakpoint,
|
|
199
|
+
format: DEFAULT_IMAGE_FORMAT,
|
|
200
|
+
origin
|
|
201
|
+
};
|
|
202
|
+
imageEntries.set(srcSetIdentifier, srcSetEntry);
|
|
203
|
+
srcSets.push(`${srcSetIdentifier} ${breakpoint}w`);
|
|
204
|
+
}
|
|
205
|
+
src.srcSets = srcSets.join(", ");
|
|
201
206
|
}
|
|
202
207
|
return `export default ${JSON.stringify(src)};`;
|
|
203
208
|
} catch (err) {
|
package/dist/react.d.ts
CHANGED
|
@@ -1,55 +1,41 @@
|
|
|
1
1
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { ImgHTMLAttributes } from "react";
|
|
3
3
|
|
|
4
|
-
//#region src/
|
|
5
|
-
/**
|
|
6
|
-
* Image source type - can be either a simple URL string or a full ImageSrc object
|
|
7
|
-
*/
|
|
8
|
-
type ImageSrcType = string | ImageSrc;
|
|
9
|
-
/**
|
|
10
|
-
* Optimized image source with multiple responsive variants
|
|
11
|
-
*/
|
|
4
|
+
//#region src/client.d.ts
|
|
12
5
|
interface ImageSrc {
|
|
13
|
-
/** Original image
|
|
6
|
+
/** Original width of the source image in pixels */
|
|
14
7
|
width: number;
|
|
15
|
-
/** Original image
|
|
8
|
+
/** Original height of the source image in pixels */
|
|
16
9
|
height: number;
|
|
17
|
-
/**
|
|
10
|
+
/** URL to the placeholder image (if placeholder was enabled) */
|
|
18
11
|
placeholderUrl?: string;
|
|
19
|
-
/** Array of responsive image
|
|
20
|
-
srcSets:
|
|
21
|
-
/**
|
|
12
|
+
/** Array of responsive image sources at different breakpoints */
|
|
13
|
+
srcSets: string;
|
|
14
|
+
/** URL to the main processed image */
|
|
22
15
|
src: string;
|
|
23
16
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
interface ImageSrcSet {
|
|
28
|
-
/** Width descriptor (e.g., "1920w") */
|
|
29
|
-
width: string;
|
|
30
|
-
/** Image URL for this variant */
|
|
31
|
-
src: string;
|
|
17
|
+
declare module "*?oh" {
|
|
18
|
+
const imageSrc: ImageSrc;
|
|
19
|
+
export default imageSrc;
|
|
32
20
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/** Load the image immediately, bypassing lazy loading */
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/react/types.d.ts
|
|
23
|
+
type ImageSrcType = string | ImageSrc;
|
|
24
|
+
interface ImageProps extends Partial<Pick<ImgHTMLAttributes<HTMLImageElement>, "alt" | "fetchPriority" | "decoding" | "loading" | "height" | "width" | "srcSet" | "className" | "sizes" | "style">> {
|
|
25
|
+
/** Configures the Image component to load the image immediately. */
|
|
39
26
|
asap?: boolean;
|
|
40
|
-
/**
|
|
27
|
+
/** */
|
|
41
28
|
src: ImageSrcType;
|
|
42
|
-
/**
|
|
29
|
+
/** The URL of the placeholder image to display while loading. */
|
|
43
30
|
placeholderUrl?: string | undefined;
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
/** Make image fill its container (position: absolute) */
|
|
31
|
+
/**
|
|
32
|
+
* Sets the image to "fill mode", which eliminates the height/width requirement and adds
|
|
33
|
+
* styles such that the image fills its containing element.
|
|
34
|
+
*/
|
|
49
35
|
fill?: boolean;
|
|
50
36
|
}
|
|
51
37
|
//#endregion
|
|
52
38
|
//#region src/react/image.d.ts
|
|
53
39
|
declare function Image(props: ImageProps): react_jsx_runtime0.JSX.Element;
|
|
54
40
|
//#endregion
|
|
55
|
-
export { Image, type ImageProps
|
|
41
|
+
export { Image, type ImageProps };
|
package/dist/react.js
CHANGED
|
@@ -10,7 +10,7 @@ function resolveOptions(props) {
|
|
|
10
10
|
resolved.src = src.src;
|
|
11
11
|
resolved.width ??= src.width;
|
|
12
12
|
resolved.height ??= src.height;
|
|
13
|
-
resolved.
|
|
13
|
+
resolved.srcSet ??= src.srcSets;
|
|
14
14
|
resolved.placeholderUrl ??= src.placeholderUrl;
|
|
15
15
|
} else resolved.src = src;
|
|
16
16
|
if (props.asap) {
|
|
@@ -26,11 +26,7 @@ function resolveOptions(props) {
|
|
|
26
26
|
return resolved;
|
|
27
27
|
}
|
|
28
28
|
function getPlaceholderStyles(props) {
|
|
29
|
-
if (!props.
|
|
30
|
-
if (!props.placeholderUrl) {
|
|
31
|
-
console.warn("Blur URL is required for placeholder");
|
|
32
|
-
return {};
|
|
33
|
-
}
|
|
29
|
+
if (!props.placeholderUrl) return {};
|
|
34
30
|
return {
|
|
35
31
|
backgroundPosition: "50% 50%",
|
|
36
32
|
backgroundRepeat: "no-repeat",
|
|
@@ -61,7 +57,7 @@ function Image(props) {
|
|
|
61
57
|
src: options.src,
|
|
62
58
|
width: options.width,
|
|
63
59
|
height: options.height,
|
|
64
|
-
srcSet: options.
|
|
60
|
+
srcSet: options.srcSet,
|
|
65
61
|
alt: options.alt,
|
|
66
62
|
loading: options.loading,
|
|
67
63
|
decoding: options.decoding,
|
package/package.json
CHANGED