@lonik/oh-image 1.0.4 → 1.2.0

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 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: any;
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 ImageSrc, type ImageSrcSet, type PluginConfig, ohImage };
40
+ export { type ImageOptions, type PluginConfig, ohImage };
package/dist/plugin.js CHANGED
@@ -60,6 +60,9 @@ async function processImage(path, options) {
60
60
 
61
61
  //#endregion
62
62
  //#region src/plugin/plugin.ts
63
+ const DEFAULT_IMAGE_FORMAT = "webp";
64
+ const PLACEHOLDER_IMG_SIZE = 8;
65
+ const PLACEHOLDER_BLUR_QUALITY = 70;
63
66
  const DEFAULT_CONFIGS = {
64
67
  distDir: "oh-images",
65
68
  bps: [
@@ -76,18 +79,10 @@ const DEFAULT_CONFIGS = {
76
79
  1920
77
80
  ],
78
81
  format: "webp",
79
- blur: false,
80
- width: null,
81
- height: null,
82
- placeholder: false,
83
- placeholderH: 100,
84
- placeholderW: 100,
85
- placeholderB: true,
86
- placeholderF: "webp",
87
- srcSetsF: "webp"
82
+ placeholder: false
88
83
  };
89
84
  const PROCESS_KEY = "oh";
90
- const SUPPORTED_IMAGE_FORMATS = /\.(jpe?g|png|webp|avif|gif|tiff?|svg)(\?.*)?$/i;
85
+ const SUPPORTED_IMAGE_FORMATS = /\.(jpe?g|png|webp|avif|gif|svg)(\?.*)?$/i;
91
86
  const DEV_DIR = "/@oh-images/";
92
87
  function ohImage(options) {
93
88
  let isBuild = false;
@@ -163,7 +158,6 @@ function ohImage(options) {
163
158
  const mainEntry = {
164
159
  width: mergedOptions.width,
165
160
  height: mergedOptions.height,
166
- blur: mergedOptions.blur,
167
161
  format: mergedOptions.format,
168
162
  origin
169
163
  };
@@ -172,32 +166,42 @@ function ohImage(options) {
172
166
  width: metadata.width,
173
167
  height: metadata.height,
174
168
  src: mainIdentifier,
175
- srcSets: []
169
+ srcSets: ""
176
170
  };
177
171
  if (parsed.options?.placeholder) {
178
- const placeholderIdentifier = genIdentifier(name, mergedOptions.placeholderF, "placeholder");
172
+ let placeholderHeight = 0;
173
+ let placeholderWidth = 0;
174
+ if (metadata.width >= metadata.height) {
175
+ placeholderWidth = PLACEHOLDER_IMG_SIZE;
176
+ placeholderHeight = Math.max(Math.round(metadata.height / metadata.width * PLACEHOLDER_IMG_SIZE), 1);
177
+ } else {
178
+ placeholderWidth = Math.max(Math.round(metadata.width / metadata.height * PLACEHOLDER_IMG_SIZE), 1);
179
+ placeholderHeight = PLACEHOLDER_IMG_SIZE;
180
+ }
181
+ const placeholderIdentifier = genIdentifier(name, DEFAULT_IMAGE_FORMAT, "placeholder");
179
182
  const placeholderEntry = {
180
- width: mergedOptions.placeholderW,
181
- height: mergedOptions.placeholderH,
182
- format: mergedOptions.placeholderF,
183
- blur: mergedOptions.placeholderB,
183
+ width: placeholderWidth,
184
+ height: placeholderHeight,
185
+ format: DEFAULT_IMAGE_FORMAT,
186
+ blur: PLACEHOLDER_BLUR_QUALITY,
184
187
  origin
185
188
  };
186
189
  imageEntries.set(placeholderIdentifier, placeholderEntry);
187
190
  src.placeholderUrl = placeholderIdentifier;
188
191
  }
189
- if (mergedOptions.bps) for (const breakpoint of mergedOptions.bps) {
190
- const srcSetIdentifier = genIdentifier(name, mergedOptions.srcSetsF, `breakpoint-${breakpoint}`);
191
- const srcSetEntry = {
192
- width: breakpoint,
193
- format: mergedOptions.srcSetsF,
194
- origin
195
- };
196
- imageEntries.set(srcSetIdentifier, srcSetEntry);
197
- src.srcSets.push({
198
- src: srcSetIdentifier,
199
- width: `${breakpoint}w`
200
- });
192
+ if (mergedOptions.bps) {
193
+ const srcSets = [];
194
+ for (const breakpoint of mergedOptions.bps) {
195
+ const srcSetIdentifier = genIdentifier(name, DEFAULT_IMAGE_FORMAT, `breakpoint-${breakpoint}`);
196
+ const srcSetEntry = {
197
+ width: breakpoint,
198
+ format: DEFAULT_IMAGE_FORMAT,
199
+ origin
200
+ };
201
+ imageEntries.set(srcSetIdentifier, srcSetEntry);
202
+ srcSets.push(`${srcSetIdentifier} ${breakpoint}w`);
203
+ }
204
+ src.srcSets = srcSets.join(", ");
201
205
  }
202
206
  return `export default ${JSON.stringify(src)};`;
203
207
  } 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 { CSSProperties } from "react";
2
+ import { ImgHTMLAttributes } from "react";
3
3
 
4
- //#region src/react/types.d.ts
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 width in pixels */
6
+ /** Original width of the source image in pixels */
14
7
  width: number;
15
- /** Original image height in pixels */
8
+ /** Original height of the source image in pixels */
16
9
  height: number;
17
- /** Optional low-quality placeholder image URL for blur-up effect */
10
+ /** URL to the placeholder image (if placeholder was enabled) */
18
11
  placeholderUrl?: string;
19
- /** Array of responsive image variants for different screen sizes */
20
- srcSets: ImageSrcSet[];
21
- /** Primary image source URL */
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
- * Single responsive image variant in a srcset
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
- * Props for the optimized Image component
35
- * Extends standard HTML image attributes with optimization features
36
- */
37
- interface ImageProps extends Partial<Pick<HTMLImageElement, "alt" | "fetchPriority" | "decoding" | "loading" | "height" | "width" | "srcset" | "className" | "sizes">> {
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
- /** Image source - either a URL string or ImageSrc object with responsive variants */
27
+ /** */
41
28
  src: ImageSrcType;
42
- /** Override placeholder URL (takes precedence over ImageSrc.placeholderUrl) */
29
+ /** The URL of the placeholder image to display while loading. */
43
30
  placeholderUrl?: string | undefined;
44
- /** Enable blur-up placeholder effect during image loading */
45
- placeholder?: boolean;
46
- /** Inline CSS styles */
47
- style?: CSSProperties;
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, type ImageSrc, type ImageSrcSet };
41
+ export { Image, type ImageProps };
package/dist/react.js CHANGED
@@ -1,7 +1,8 @@
1
- import { preload } from "react-dom";
1
+ import * as ReactDOM from "react-dom";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
 
4
4
  //#region src/react/image.tsx
5
+ const preload = "preload" in ReactDOM && typeof ReactDOM.preload === "function" ? ReactDOM.preload : null;
5
6
  function resolveOptions(props) {
6
7
  const { src, ...rest } = props;
7
8
  const resolved = { ...rest };
@@ -9,14 +10,14 @@ function resolveOptions(props) {
9
10
  resolved.src = src.src;
10
11
  resolved.width ??= src.width;
11
12
  resolved.height ??= src.height;
12
- resolved.srcset ??= src.srcSets.map((set) => `${set.src} ${set.width}`).join(", ");
13
+ resolved.srcSet ??= src.srcSets;
13
14
  resolved.placeholderUrl ??= src.placeholderUrl;
14
15
  } else resolved.src = src;
15
16
  if (props.asap) {
16
17
  resolved.decoding = "async";
17
18
  resolved.loading = "eager";
18
19
  resolved.fetchPriority = "high";
19
- preload(resolved.src, {
20
+ if (preload) preload(resolved.src, {
20
21
  as: "image",
21
22
  fetchPriority: "high"
22
23
  });
@@ -25,11 +26,7 @@ function resolveOptions(props) {
25
26
  return resolved;
26
27
  }
27
28
  function getPlaceholderStyles(props) {
28
- if (!props.placeholder) return {};
29
- if (!props.placeholderUrl) {
30
- console.warn("Blur URL is required for placeholder");
31
- return {};
32
- }
29
+ if (!props.placeholderUrl) return {};
33
30
  return {
34
31
  backgroundPosition: "50% 50%",
35
32
  backgroundRepeat: "no-repeat",
@@ -60,7 +57,7 @@ function Image(props) {
60
57
  src: options.src,
61
58
  width: options.width,
62
59
  height: options.height,
63
- srcSet: options.srcset,
60
+ srcSet: options.srcSet,
64
61
  alt: options.alt,
65
62
  loading: options.loading,
66
63
  decoding: options.decoding,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lonik/oh-image",
3
3
  "type": "module",
4
- "version": "1.0.4",
4
+ "version": "1.2.0",
5
5
  "description": "A React component library for optimized image handling.",
6
6
  "author": "Luka Onikadze <lukonik@gmail.com>",
7
7
  "license": "MIT",
@@ -45,16 +45,16 @@
45
45
  "prepublishOnly": "pnpm run build"
46
46
  },
47
47
  "peerDependencies": {
48
- "react": "^19.2.0",
49
- "react-dom": "^19.2.0",
50
- "sharp": "^0.34.5"
48
+ "react": ">=18",
49
+ "react-dom": ">=18",
50
+ "sharp": ">=0.34.5"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@eslint/js": "^9.39.2",
54
54
  "@tsconfig/strictest": "^2.0.8",
55
55
  "@types/node": "^25.0.3",
56
- "@types/react": "^19.2.7",
57
- "@types/react-dom": "^19.2.3",
56
+ "@types/react": "^18",
57
+ "@types/react-dom": "^18",
58
58
  "@vitejs/plugin-react": "^5.1.2",
59
59
  "@vitest/browser-playwright": "^4.0.16",
60
60
  "bumpp": "^10.3.2",