vite-image-react 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 gotodev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # vite-image-react
2
+
3
+ **Content-aware, device-adaptive image optimizer for Vite + React.**
4
+
5
+ Surpasses `next/image` in perceptual quality at equal or smaller file sizes. Works with any Vite project, not locked to any framework.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/vite-image-react.svg)](https://www.npmjs.com/package/vite-image-react)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+ [![Node](https://img.shields.io/badge/node-%3E%3D22-brightgreen)](https://nodejs.org)
10
+ [![Vite](https://img.shields.io/badge/vite-%3E%3D7-blueviolet)](https://vitejs.dev)
11
+ [![React](https://img.shields.io/badge/react-%3E%3D19-blue)](https://react.dev)
12
+
13
+ ---
14
+
15
+ ## How it works
16
+
17
+ ### Build time (Vite plugin)
18
+
19
+ Every image is divided into 64×64 tiles and analyzed:
20
+
21
+ 1. **Analyze** — per-tile Shannon entropy, Sobel edge density, and skin-tone ratio produce an importance map
22
+ 2. **Weight** — top 30% most-important tiles drive 70% of the quality decision; center-bias and edge-bonuses refine it
23
+ 3. **Preprocess** — high-importance tiles are selectively sharpened (with overlapped boundaries to prevent seams)
24
+ 4. **Encode** — each tier/variant is encoded at the perceptually-weighted quality; SSIM auto-tune finds the Pareto-optimal quality/size point
25
+ 5. **Emit** — manifest with variants, tiers, LQIP placeholders, and SRI hashes is embedded in the module
26
+
27
+ ### Runtime (GImage component)
28
+
29
+ 1. **Fingerprint** — reads `effectiveType`, `deviceMemory`, `hardwareConcurrency`, `devicePixelRatio`, `saveData`
30
+ 2. **Tier** — scoring algorithm selects `ultra`/`high`/`medium`/`low` per device capability
31
+ 3. **Format** — `<picture>` with AVIF, WebP, and JPEG sources
32
+ 4. **Load** — IntersectionObserver with scroll-velocity-adaptive preload distance (600–3000px)
33
+ 5. **Placeholder** — blur-up CSS transition from 32×32 WebP base64 to full image
34
+
35
+ ---
36
+
37
+ ## Features
38
+
39
+ - **Perceptually-optimized quality** — 64×64 tile saliency drives quality per region. Faces, text, and detail get higher quality; backgrounds compress harder.
40
+ - **Saliency-driven preprocessing** — important tiles are sharpened before encoding, preserving detail where it matters.
41
+ - **Skin-tone face detection** — automatic quality boost around skin-colored regions. Zero extra dependencies.
42
+ - **SSIM auto-tune** — finds the lowest quality where SSIM >= 0.97, saving 20–40% file size with no visible loss.
43
+ - **Device-adaptive delivery** — runtime fingerprinting selects the optimal quality tier for each device.
44
+ - **Automatic format conversion** — AVIF, WebP, and JPEG sources in a `<picture>` element.
45
+ - **Predictive lazy loading** — scroll velocity sampling dynamically adjusts the preload distance.
46
+ - **Blur-up placeholders** — 32×32 WebP base64 with CSS fade-in.
47
+ - **CLS prevention** — fixed-aspect-ratio container from image metadata.
48
+ - **SVG optimization** — automatic SVGO compression with security sanitization.
49
+
50
+ ---
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ npm install vite-image-react
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Usage
61
+
62
+ ### Vite plugin
63
+
64
+ ```ts
65
+ // vite.config.ts
66
+ import { defineConfig } from 'vite'
67
+ import react from '@vitejs/plugin-react'
68
+ import viteImageReact from 'vite-image-react/vite-plugin'
69
+
70
+ export default defineConfig({
71
+ plugins: [
72
+ react(),
73
+ viteImageReact(),
74
+ ],
75
+ })
76
+ ```
77
+
78
+ ### React component
79
+
80
+ ```tsx
81
+ import GImage from 'vite-image-react'
82
+ import hero from './hero.jpg'
83
+
84
+ function Page() {
85
+ return (
86
+ <GImage
87
+ src={hero}
88
+ alt="Hero banner"
89
+ priority
90
+ sizes="(max-width: 768px) 100vw, 50vw"
91
+ />
92
+ )
93
+ }
94
+ ```
95
+
96
+ All standard `<img>` props work: `className`, `style`, `onLoad`, `onError`, `loading`, etc.
97
+
98
+ ---
99
+
100
+ ## Options
101
+
102
+ ### Plugin options
103
+
104
+ ```ts
105
+ viteImageReact({
106
+ tiers?: Partial<Record<QualityTier, TierConfig>>
107
+ adaptive?: boolean // default: true
108
+ autoTune?: boolean // default: true
109
+ preprocess?: boolean // default: true — sharpen important tiles before encoding
110
+ faceDetection?: boolean // default: true — boost quality around skin tones
111
+ formats?: OutputFormat[] // default: ['avif', 'webp', 'jpeg']
112
+ maxFileSize?: number // default: 52_428_800 (50MB)
113
+ verbose?: boolean // default: false
114
+ })
115
+ ```
116
+
117
+ ### GImage props
118
+
119
+ ```ts
120
+ interface GImageProps {
121
+ src: string | ImageMetadata // import result or metadata object
122
+ alt: string
123
+ priority?: boolean // eager load + fetchPriority='high'
124
+ sizes?: string // default: '100vw'
125
+ disableAdaptive?: boolean // always deliver highest quality
126
+ placeholder?: 'blur' | 'none' // default: 'blur'
127
+ onLoad?: () => void
128
+ onError?: () => void
129
+ // + all standard img props (className, style, loading, etc.)
130
+ }
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Comparison: next/image vs vite-image-react
136
+
137
+ | Aspect | next/image | vite-image-react |
138
+ |---|---|---|
139
+ | **Quality strategy** | Uniform (e.g. 75) | Perceptually-weighted — important regions drive quality |
140
+ | **Preprocessing** | None | Saliency-guided sharpen — detail preserved where it matters |
141
+ | **Face/subject detection** | None | Skin-tone heuristic — quality boost on faces |
142
+ | **SSIM auto-tune** | None | Smallest file at SSIM >= 0.97 |
143
+ | **Device adaptation** | Responsive srcSet only | Runtime tier switching — CPU/memory/connection-aware |
144
+ | **Predictive loading** | Fixed threshold | Velocity-adaptive — faster scroll = bigger preload zone |
145
+ | **Format pipeline** | AVIF/WebP/JPEG | Same + skin detection + selectable preprocessor |
146
+ | **SVG optimization** | None | SVGO integration with security sanitization |
147
+
148
+ ---
149
+
150
+ ## Requirements
151
+
152
+ - Node.js >= 22
153
+ - Vite >= 7
154
+ - React >= 19
155
+
156
+ ---
157
+
158
+ ## License
159
+
160
+ MIT
@@ -0,0 +1,57 @@
1
+ // src/core/formats.ts
2
+ var MAGIC_BYTES = {
3
+ jpeg: new Uint8Array([255, 216, 255]),
4
+ png: new Uint8Array([137, 80, 78, 71]),
5
+ webp: new Uint8Array([82, 73, 70, 70]),
6
+ gif: new Uint8Array([71, 73, 70, 56]),
7
+ bmp: new Uint8Array([66, 77]),
8
+ tiff: new Uint8Array([73, 73, 42, 0]),
9
+ ico: new Uint8Array([0, 0, 1, 0]),
10
+ svg: new Uint8Array([60])
11
+ // '<'
12
+ };
13
+ var EXTENSION_MAP = {
14
+ jpg: "jpeg",
15
+ jpeg: "jpeg",
16
+ png: "png",
17
+ webp: "webp",
18
+ avif: "avif",
19
+ gif: "gif",
20
+ svg: "svg",
21
+ bmp: "bmp",
22
+ tiff: "tiff",
23
+ tif: "tiff",
24
+ ico: "ico"
25
+ };
26
+ function detectFormat(buffer, extension) {
27
+ const extFormat = EXTENSION_MAP[extension.toLowerCase()];
28
+ if (extFormat === "svg") return "svg";
29
+ for (const [format, magic] of Object.entries(MAGIC_BYTES)) {
30
+ if (buffer.length >= magic.length) {
31
+ let match = true;
32
+ for (let i = 0; i < magic.length; i++) {
33
+ if (buffer[i] !== magic[i]) {
34
+ match = false;
35
+ break;
36
+ }
37
+ }
38
+ if (match) {
39
+ if (format === "webp") {
40
+ const riffType = new TextDecoder().decode(buffer.slice(8, 12));
41
+ if (riffType === "WEBP") return "webp";
42
+ continue;
43
+ }
44
+ return format;
45
+ }
46
+ }
47
+ }
48
+ return extFormat ?? "jpeg";
49
+ }
50
+ function isAnimatedFormat(format) {
51
+ return format === "gif";
52
+ }
53
+
54
+ export {
55
+ detectFormat,
56
+ isAnimatedFormat
57
+ };
@@ -0,0 +1,18 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ImgHTMLAttributes } from 'react';
3
+ import { M as ManifestEntry } from './types-GIeewM4u.js';
4
+ export { B as BuildManifest, D as DeviceFingerprint, I as ImageFormat, a as ImageMetadata, O as OutputFormat, P as PluginOptions, Q as QualityTier } from './types-GIeewM4u.js';
5
+
6
+ interface GImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'srcSet' | 'width' | 'height'> {
7
+ src: ManifestEntry | string;
8
+ alt: string;
9
+ priority?: boolean;
10
+ sizes?: string;
11
+ disableAdaptive?: boolean;
12
+ placeholder?: 'blur' | 'none';
13
+ onLoad?: () => void;
14
+ onError?: () => void;
15
+ }
16
+ declare function GImage({ src, alt, priority, sizes, disableAdaptive, placeholder: placeholderMode, className, style, loading: loadingProp, onLoad, onError, ...imgProps }: GImageProps): react_jsx_runtime.JSX.Element;
17
+
18
+ export { GImage, type GImageProps, ManifestEntry, GImage as default };
package/dist/index.js ADDED
@@ -0,0 +1,347 @@
1
+ import {
2
+ isAnimatedFormat
3
+ } from "./chunk-XVTCVM73.js";
4
+
5
+ // src/components/GImage.tsx
6
+ import { useCallback, useEffect, useRef, useState } from "react";
7
+
8
+ // src/adaptive/fingerprint.ts
9
+ function getConnectionType() {
10
+ try {
11
+ const conn = navigator.connection;
12
+ if (conn?.effectiveType) {
13
+ const type = conn.effectiveType;
14
+ if (["slow-2g", "2g", "3g", "4g"].includes(type)) {
15
+ return type;
16
+ }
17
+ }
18
+ } catch {
19
+ }
20
+ return "unknown";
21
+ }
22
+ function getDeviceMemory() {
23
+ try {
24
+ const mem = navigator.deviceMemory;
25
+ return mem ?? 4;
26
+ } catch {
27
+ return 4;
28
+ }
29
+ }
30
+ function getHardwareConcurrency() {
31
+ try {
32
+ return navigator.hardwareConcurrency ?? 4;
33
+ } catch {
34
+ return 4;
35
+ }
36
+ }
37
+ function getDevicePixelRatio() {
38
+ try {
39
+ return window.devicePixelRatio ?? 1;
40
+ } catch {
41
+ return 1;
42
+ }
43
+ }
44
+ function getSaveData() {
45
+ try {
46
+ const sd = navigator.connection;
47
+ return sd?.saveData ?? false;
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+ function computeTier(fp) {
53
+ if (fp.saveData) return "low";
54
+ if (fp.effectiveType === "slow-2g" || fp.effectiveType === "2g") {
55
+ return "low";
56
+ }
57
+ const score = (fp.effectiveType === "4g" ? 40 : fp.effectiveType === "3g" ? 20 : 0) + Math.min(fp.deviceMemory / 8, 1) * 25 + Math.min(fp.hardwareConcurrency / 8, 1) * 20 + Math.min(fp.devicePixelRatio / 3, 1) * 15;
58
+ if (score >= 80) return "ultra";
59
+ if (score >= 55) return "high";
60
+ if (score >= 30) return "medium";
61
+ return "low";
62
+ }
63
+ var cachedFingerprint = null;
64
+ function getDeviceFingerprint() {
65
+ if (cachedFingerprint) return cachedFingerprint;
66
+ const base = {
67
+ effectiveType: getConnectionType(),
68
+ deviceMemory: getDeviceMemory(),
69
+ hardwareConcurrency: getHardwareConcurrency(),
70
+ devicePixelRatio: getDevicePixelRatio(),
71
+ saveData: getSaveData()
72
+ };
73
+ cachedFingerprint = {
74
+ ...base,
75
+ tier: computeTier(base)
76
+ };
77
+ return cachedFingerprint;
78
+ }
79
+ function clearFingerprintCache() {
80
+ cachedFingerprint = null;
81
+ }
82
+ function listenForChanges(onChange) {
83
+ const handler = () => {
84
+ clearFingerprintCache();
85
+ onChange(getDeviceFingerprint());
86
+ };
87
+ try {
88
+ const conn = navigator.connection;
89
+ conn?.addEventListener?.("change", handler);
90
+ return () => {
91
+ try {
92
+ ;
93
+ conn?.removeEventListener?.("change", handler);
94
+ } catch {
95
+ }
96
+ };
97
+ } catch {
98
+ return () => {
99
+ };
100
+ }
101
+ }
102
+
103
+ // src/adaptive/predictive.ts
104
+ var MIN_PRELOAD = 600;
105
+ var MAX_PRELOAD = 3e3;
106
+ var VELOCITY_SAMPLES = 5;
107
+ var PredictiveLoader = class {
108
+ state = {
109
+ velocity: 0,
110
+ direction: "none",
111
+ lastScrollY: 0,
112
+ lastTimestamp: Date.now(),
113
+ preloadDistance: MIN_PRELOAD
114
+ };
115
+ velocities = [];
116
+ observer = null;
117
+ constructor() {
118
+ if (typeof window !== "undefined") {
119
+ this.state.lastScrollY = window.scrollY;
120
+ window.addEventListener("scroll", this.onScroll, { passive: true });
121
+ }
122
+ }
123
+ onScroll = () => {
124
+ const now = Date.now();
125
+ const dt = Math.max(1, now - this.state.lastTimestamp);
126
+ const dy = Math.abs(window.scrollY - this.state.lastScrollY);
127
+ const velocity = dy / dt;
128
+ this.velocities.push(velocity);
129
+ if (this.velocities.length > VELOCITY_SAMPLES) {
130
+ this.velocities.shift();
131
+ }
132
+ const avgVelocity = this.velocities.reduce((a, b) => a + b, 0) / this.velocities.length;
133
+ this.state.velocity = avgVelocity;
134
+ this.state.direction = window.scrollY > this.state.lastScrollY ? "down" : window.scrollY < this.state.lastScrollY ? "up" : "none";
135
+ this.state.lastScrollY = window.scrollY;
136
+ this.state.lastTimestamp = now;
137
+ this.state.preloadDistance = Math.min(MAX_PRELOAD, MIN_PRELOAD + avgVelocity * 5e3);
138
+ };
139
+ getPreloadMargin() {
140
+ return `${this.state.preloadDistance}px`;
141
+ }
142
+ observe(element, callback) {
143
+ this.observer?.disconnect();
144
+ this.observer = new IntersectionObserver(
145
+ (entries) => {
146
+ for (const entry of entries) {
147
+ if (entry.isIntersecting) {
148
+ callback(entry);
149
+ this.observer?.unobserve(entry.target);
150
+ }
151
+ }
152
+ },
153
+ {
154
+ rootMargin: this.getPreloadMargin(),
155
+ threshold: 0.01
156
+ }
157
+ );
158
+ this.observer.observe(element);
159
+ }
160
+ unobserve(element) {
161
+ this.observer?.unobserve(element);
162
+ }
163
+ destroy() {
164
+ this.observer?.disconnect();
165
+ if (typeof window !== "undefined") {
166
+ window.removeEventListener("scroll", this.onScroll);
167
+ }
168
+ this.observer = null;
169
+ }
170
+ };
171
+
172
+ // src/components/GImage.tsx
173
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
174
+ function GImage({
175
+ src,
176
+ alt,
177
+ priority = false,
178
+ sizes = "100vw",
179
+ disableAdaptive = false,
180
+ placeholder: placeholderMode = "blur",
181
+ className,
182
+ style,
183
+ loading: loadingProp,
184
+ onLoad,
185
+ onError,
186
+ ...imgProps
187
+ }) {
188
+ const imgRef = useRef(null);
189
+ const containerRef = useRef(null);
190
+ const predictiveRef = useRef(null);
191
+ const [loadState, setLoadState] = useState({
192
+ loaded: false,
193
+ error: false,
194
+ currentTier: "ultra"
195
+ });
196
+ const metadata = typeof src === "string" ? null : {
197
+ src: src.src,
198
+ width: src.width,
199
+ height: src.height,
200
+ format: src.format,
201
+ placeholder: src.placeholder,
202
+ variants: src.variants,
203
+ tiers: src.tiers
204
+ };
205
+ useEffect(() => {
206
+ if (disableAdaptive || !metadata) {
207
+ setLoadState((prev) => ({ ...prev, currentTier: "ultra" }));
208
+ return;
209
+ }
210
+ const fp = getDeviceFingerprint();
211
+ setLoadState((prev) => ({ ...prev, currentTier: fp.tier }));
212
+ const cleanup = listenForChanges((newFp) => {
213
+ setLoadState((prev) => ({ ...prev, currentTier: newFp.tier }));
214
+ });
215
+ return cleanup;
216
+ }, [disableAdaptive, metadata]);
217
+ useEffect(() => {
218
+ if (priority || loadingProp === "eager") {
219
+ setLoadState((prev) => ({ ...prev, loaded: true }));
220
+ return;
221
+ }
222
+ if (!containerRef.current) return;
223
+ predictiveRef.current = new PredictiveLoader();
224
+ predictiveRef.current.observe(containerRef.current, () => {
225
+ setLoadState((prev) => ({ ...prev, loaded: true }));
226
+ });
227
+ return () => {
228
+ predictiveRef.current?.destroy();
229
+ };
230
+ }, [priority, loadingProp]);
231
+ const handleLoad = useCallback(() => {
232
+ setLoadState((prev) => ({ ...prev, loaded: true }));
233
+ onLoad?.();
234
+ }, [onLoad]);
235
+ const handleError = useCallback(() => {
236
+ setLoadState((prev) => ({ ...prev, error: true }));
237
+ onError?.();
238
+ }, [onError]);
239
+ if (!metadata) {
240
+ return /* @__PURE__ */ jsx(
241
+ "img",
242
+ {
243
+ ref: imgRef,
244
+ ...imgProps,
245
+ src: typeof src === "string" ? src : "",
246
+ alt: alt ?? "",
247
+ className,
248
+ style,
249
+ loading: priority ? "eager" : loadingProp ?? "lazy",
250
+ fetchPriority: priority ? "high" : "low",
251
+ onLoad: handleLoad,
252
+ onError: handleError
253
+ }
254
+ );
255
+ }
256
+ const aspectRatio = metadata.width && metadata.height ? metadata.height / metadata.width * 100 : 0;
257
+ const tier = loadState.currentTier;
258
+ const tierVariants = metadata.variants.filter((v) => {
259
+ const tierWidths = {
260
+ ultra: 99999,
261
+ high: 1024,
262
+ medium: 768,
263
+ low: 480
264
+ };
265
+ return v.width <= tierWidths[tier];
266
+ });
267
+ const hasPlaceholder = placeholderMode === "blur" && !!metadata.placeholder && !loadState.loaded;
268
+ const isAnimated = isAnimatedFormat(metadata.format);
269
+ const pictureContent = /* @__PURE__ */ jsxs("picture", { children: [
270
+ !isAnimated && tierVariants.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
271
+ /* @__PURE__ */ jsx(
272
+ "source",
273
+ {
274
+ type: "image/avif",
275
+ srcSet: tierVariants.filter((v) => v.format === "avif").map((v) => `${v.src} ${v.width}w`).join(", "),
276
+ sizes
277
+ }
278
+ ),
279
+ /* @__PURE__ */ jsx(
280
+ "source",
281
+ {
282
+ type: "image/webp",
283
+ srcSet: tierVariants.filter((v) => v.format === "webp").map((v) => `${v.src} ${v.width}w`).join(", "),
284
+ sizes
285
+ }
286
+ )
287
+ ] }),
288
+ /* @__PURE__ */ jsx(
289
+ "img",
290
+ {
291
+ ref: imgRef,
292
+ ...imgProps,
293
+ src: tierVariants.find((v) => v.format === "jpeg")?.src ?? metadata.src,
294
+ srcSet: tierVariants.filter((v) => v.format === "jpeg" || v.format === "png").map((v) => `${v.src} ${v.width}w`).join(", ") || void 0,
295
+ sizes,
296
+ alt: alt ?? "",
297
+ width: metadata.width,
298
+ height: metadata.height,
299
+ loading: loadState.loaded && !priority ? "lazy" : "eager",
300
+ fetchPriority: priority ? "high" : "low",
301
+ decoding: priority ? "auto" : "async",
302
+ className,
303
+ style: {
304
+ width: "100%",
305
+ height: "auto",
306
+ display: "block",
307
+ background: hasPlaceholder ? `${metadata.placeholder} center/cover no-repeat` : void 0,
308
+ filter: hasPlaceholder ? "blur(20px)" : void 0,
309
+ transition: "filter 0.4s ease-out, opacity 0.4s ease-out",
310
+ opacity: loadState.loaded ? 1 : 0.99,
311
+ ...style
312
+ },
313
+ onLoad: handleLoad,
314
+ onError: handleError
315
+ }
316
+ )
317
+ ] });
318
+ if (aspectRatio > 0) {
319
+ return /* @__PURE__ */ jsx(
320
+ "div",
321
+ {
322
+ ref: containerRef,
323
+ style: {
324
+ position: "relative",
325
+ width: "100%",
326
+ paddingBottom: `${aspectRatio}%`,
327
+ overflow: "hidden"
328
+ },
329
+ children: /* @__PURE__ */ jsx(
330
+ "div",
331
+ {
332
+ style: {
333
+ position: "absolute",
334
+ inset: 0
335
+ },
336
+ children: pictureContent
337
+ }
338
+ )
339
+ }
340
+ );
341
+ }
342
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, children: pictureContent });
343
+ }
344
+ export {
345
+ GImage,
346
+ GImage as default
347
+ };
@@ -0,0 +1,59 @@
1
+ type ImageFormat = 'jpeg' | 'png' | 'webp' | 'avif' | 'gif' | 'svg' | 'bmp' | 'tiff' | 'ico';
2
+ type OutputFormat = 'avif' | 'webp' | 'jpeg' | 'png';
3
+ type QualityTier = 'ultra' | 'high' | 'medium' | 'low';
4
+ interface TierConfig {
5
+ quality: number;
6
+ widths: number[];
7
+ }
8
+ interface PluginOptions {
9
+ tiers?: Partial<Record<QualityTier, TierConfig>>;
10
+ adaptive?: boolean;
11
+ autoTune?: boolean;
12
+ formats?: OutputFormat[];
13
+ maxFileSize?: number;
14
+ widths?: number[];
15
+ verbose?: boolean;
16
+ preprocess?: boolean;
17
+ faceDetection?: boolean;
18
+ }
19
+ interface ImageVariant {
20
+ src: string;
21
+ width: number;
22
+ format: OutputFormat;
23
+ size: number;
24
+ integrity: string;
25
+ }
26
+ interface ImageMetadata {
27
+ src: string;
28
+ width: number;
29
+ height: number;
30
+ format: ImageFormat;
31
+ placeholder: string;
32
+ variants: ImageVariant[];
33
+ tiers: Record<QualityTier, string>;
34
+ blurHash?: string;
35
+ }
36
+ interface DeviceFingerprint {
37
+ effectiveType: 'slow-2g' | '2g' | '3g' | '4g' | 'unknown';
38
+ deviceMemory: number;
39
+ hardwareConcurrency: number;
40
+ devicePixelRatio: number;
41
+ saveData: boolean;
42
+ tier: QualityTier;
43
+ }
44
+ interface ManifestEntry {
45
+ src: string;
46
+ width: number;
47
+ height: number;
48
+ format: ImageFormat;
49
+ placeholder: string;
50
+ tiers: Record<QualityTier, string>;
51
+ variants: ImageVariant[];
52
+ }
53
+ interface BuildManifest {
54
+ version: string;
55
+ generatedAt: string;
56
+ entries: Record<string, ManifestEntry>;
57
+ }
58
+
59
+ export type { BuildManifest as B, DeviceFingerprint as D, ImageFormat as I, ManifestEntry as M, OutputFormat as O, PluginOptions as P, QualityTier as Q, ImageMetadata as a };
@@ -0,0 +1,6 @@
1
+ import { Plugin } from 'vite';
2
+ import { P as PluginOptions } from './types-GIeewM4u.js';
3
+
4
+ declare function viteImageReact(userOptions?: PluginOptions): Plugin;
5
+
6
+ export { viteImageReact as default };