@son426/vite-image 0.1.3 โ†’ 0.1.5

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 CHANGED
@@ -20,3 +20,4 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
22
 
23
+
package/README.md CHANGED
@@ -1,19 +1,47 @@
1
1
  # @son426/vite-image
2
2
 
3
- **Next.js-style Image component for Vite.**
3
+ **The Next.js `<Image />` experience, now in Vite.**
4
4
 
5
- This library provides a Next.js-like Image component experience in Vite. The core philosophy is to bring static bundler capabilities similar to Next.js Image component to Vite projects. Simply add `viteImage()` to your `vite.config.ts` and start using the `<Image />` component.
5
+ - **Bring the power of Next.js's automatic image optimization to your Vite projects.**
6
+ - **Dedicated to the Vite + React ecosystem.**
6
7
 
7
- ## Features
8
+ Simply add the plugin to your config, and start using the `<Image />` component immediately. No complex setups, just performant images.
8
9
 
9
- - ๐Ÿ–ผ๏ธ **Optimized Images**: Automatic image optimization using vite-imagetools
10
- - ๐ŸŽจ **LQIP Support**: Low Quality Image Placeholder for smooth loading experience
11
- - ๐Ÿ“ฑ **Responsive Images**: Built-in support for srcSet and sizes attributes
12
- - ๐ŸŽฏ **TypeScript**: Full TypeScript support with type definitions
13
- - ๐Ÿš€ **Simple Setup**: Easy configuration with sensible defaults
10
+ ## โœจ Why use this?
11
+
12
+ - **โšก Next.js-like Experience**: Familiar Image API for those coming from Next.js.
13
+ - **๐Ÿ–ผ๏ธ Zero-Config Optimization**: Automatic format conversion, resizing, and compression via `vite-imagetools`.
14
+ - **๐ŸŽจ Built-in LQIP**: Automatic Low Quality Image Placeholders (blur effect) while loading.
15
+ - **๐Ÿ“ฑ Responsive Ready**: Auto-generated `srcSet` and `sizes` for all viewports.
16
+ - **๐ŸŽฏ Type-Safe**: Full TypeScript support with tight integration.
17
+
18
+ ---
19
+
20
+ ## ๐Ÿš€ Quick Look
21
+
22
+ Add it to `vite.config.ts`, and use it like this:
23
+
24
+ ```tsx
25
+ import Image from "@son426/vite-image/react";
26
+ // 1. Import with the required query
27
+ import myBg from "./assets/background.jpg?vite-image";
28
+
29
+ export default function Page() {
30
+ return (
31
+ // 2. Pass the object directly to src
32
+ <Image
33
+ src={myBg}
34
+ alt="Optimized Background"
35
+ fill // Optional: Fill parent container
36
+ />
37
+ );
38
+ }
39
+ ```
14
40
 
15
41
  ## Installation
16
42
 
43
+ Since `vite-imagetools` is handled internally, you only need to install this package.
44
+
17
45
  ```bash
18
46
  pnpm add @son426/vite-image
19
47
  # or
@@ -22,13 +50,13 @@ npm install @son426/vite-image
22
50
  yarn add @son426/vite-image
23
51
  ```
24
52
 
25
- ## Peer Dependencies
53
+ ## Requirements
26
54
 
27
- The following packages are required:
55
+ Just a standard Vite + React project.
28
56
 
29
- ```bash
30
- pnpm add react vite vite-imagetools
31
- ```
57
+ - vite (>= 4.0.0)
58
+ - react (>= 18.0.0)
59
+ - react-dom (>= 18.0.0)
32
60
 
33
61
  ## Usage
34
62
 
@@ -48,8 +76,6 @@ export default defineConfig({
48
76
  });
49
77
  ```
50
78
 
51
- **Important**: The `viteImage()` function returns an array of plugins, so you should spread it when adding to the plugins array.
52
-
53
79
  ### 2. Use the Component
54
80
 
55
81
  #### Using `?vite-image` query (Required)
@@ -61,14 +87,7 @@ import Image from "@son426/vite-image/react";
61
87
  import bgImage from "@/assets/image.webp?vite-image";
62
88
 
63
89
  function MyComponent() {
64
- return (
65
- <Image
66
- src={bgImage}
67
- fill={false}
68
- sizes="(max-width: 640px) 100vw, (max-width: 1024px) 100vw, 1920px"
69
- alt="Description"
70
- />
71
- );
90
+ return <Image src={bgImage} fill={false} alt="Description" />;
72
91
  }
73
92
  ```
74
93
 
@@ -78,16 +97,82 @@ The `?vite-image` query automatically generates:
78
97
 
79
98
  - `src`: Optimized image URL
80
99
  - `srcSet`: Responsive srcSet string
81
- - `lqipSrc`: Low Quality Image Placeholder (base64 inline)
100
+ - `blurDataURL`: Low Quality Image Placeholder (base64 inline)
82
101
  - `width` and `height`: Image dimensions
83
102
 
84
- ### Fill Mode
103
+ #### Usage Examples
85
104
 
86
- For images that fill their container (similar to Next.js Image):
105
+ **Basic usage:**
87
106
 
88
- ```typescript
107
+ ```tsx
108
+ import Image from "@son426/vite-image/react";
109
+ import heroImage from "@/assets/hero.jpg?vite-image";
110
+
111
+ <Image src={heroImage} alt="Hero" />;
112
+ ```
113
+
114
+ **Fill mode (container-filling images):**
115
+
116
+ ```tsx
89
117
  <div style={{ position: "relative", width: "100%", height: "400px" }}>
90
- <Image src={bgImage} fill={true} sizes="100vw" alt="Description" />
118
+ <Image src={bgImage} fill alt="Background" />
119
+ </div>
120
+ ```
121
+
122
+ **With priority (LCP images):**
123
+
124
+ ```tsx
125
+ <Image src={heroImage} alt="Hero" priority />
126
+ ```
127
+
128
+ **With blur placeholder:**
129
+
130
+ ```tsx
131
+ <Image src={heroImage} alt="Hero" placeholder="blur" />
132
+ ```
133
+
134
+ **Without placeholder:**
135
+
136
+ ```tsx
137
+ <Image src={heroImage} alt="Hero" placeholder="empty" />
138
+ ```
139
+
140
+ **Custom data URL placeholder:**
141
+
142
+ ```tsx
143
+ <Image src={heroImage} alt="Hero" placeholder="data:image/jpeg;base64,..." />
144
+ ```
145
+
146
+ **Custom sizes:**
147
+
148
+ ```tsx
149
+ <Image src={heroImage} alt="Hero" sizes="(max-width: 768px) 100vw, 50vw" />
150
+ ```
151
+
152
+ **With onLoad and onError callbacks:**
153
+
154
+ ```tsx
155
+ <Image
156
+ src={heroImage}
157
+ alt="Hero"
158
+ onLoad={(e) => console.log("Image loaded", e)}
159
+ onError={(e) => console.error("Image failed to load", e)}
160
+ />
161
+ ```
162
+
163
+ **Combined usage:**
164
+
165
+ ```tsx
166
+ <div style={{ position: "relative", width: "100%", height: "600px" }}>
167
+ <Image
168
+ src={heroImage}
169
+ alt="Hero"
170
+ fill
171
+ priority
172
+ placeholder="blur"
173
+ className="rounded-lg"
174
+ onLoad={(e) => console.log("Loaded")}
175
+ />
91
176
  </div>
92
177
  ```
93
178
 
@@ -95,16 +180,25 @@ For images that fill their container (similar to Next.js Image):
95
180
 
96
181
  ### Image Props
97
182
 
98
- | Prop | Type | Required | Description |
99
- | ----------- | --------------------- | -------- | ------------------------------------------ |
100
- | `src` | `ResponsiveImageData` | Yes | Image data object from `?vite-image` query |
101
- | `fill` | `boolean` | No | Fill container mode (default: `false`) |
102
- | `sizes` | `string` | No | Sizes attribute (default: `"100vw"`) |
103
- | `className` | `string` | No | Additional CSS classes |
104
- | `style` | `CSSProperties` | No | Additional inline styles |
105
- | `...props` | `ImgHTMLAttributes` | No | All standard img element attributes |
106
-
107
- **Note**: The `src` prop must be an object imported from `?vite-image` query. String URLs are not supported. The `width` and `height` are automatically extracted from the `src` object.
183
+ | Prop | Type | Required | Default | Description |
184
+ | ------------- | --------------------------------------------------------- | -------- | --------- | ----------------------------------------------------------------------- |
185
+ | `src` | `ResponsiveImageData` | Yes | - | Image data object from `?vite-image` query |
186
+ | `fill` | `boolean` | No | `false` | Fill container mode (requires parent with `position: relative`) |
187
+ | `sizes` | `string` | No | auto | Sizes attribute (auto-calculated from srcSet if not provided) |
188
+ | `priority` | `boolean` | No | `false` | High priority loading (preload + eager + fetchPriority high) |
189
+ | `placeholder` | `'empty' \| 'blur' \| string` | No | `'empty'` | Placeholder type: `'empty'` (none), `'blur'` (blurDataURL), or data URL |
190
+ | `onLoad` | `(event: React.SyntheticEvent<HTMLImageElement>) => void` | No | - | Callback fired when image loads successfully |
191
+ | `onError` | `(event: React.SyntheticEvent<HTMLImageElement>) => void` | No | - | Callback fired when image fails to load |
192
+ | `className` | `string` | No | - | Additional CSS classes |
193
+ | `style` | `CSSProperties` | No | - | Additional inline styles |
194
+ | `...props` | `ImgHTMLAttributes` | No | - | All standard img element attributes |
195
+
196
+ **Notes**:
197
+
198
+ - The `src` prop must be an object imported from `?vite-image` query. String URLs are not supported.
199
+ - The `width` and `height` are automatically extracted from the `src` object.
200
+ - When `priority={true}`, the image is preloaded using `react-dom`'s `preload` API and loaded with `loading="eager"` and `fetchPriority="high"`.
201
+ - When `sizes` is not provided, it's automatically calculated from `srcSet` breakpoints.
108
202
 
109
203
  ### ResponsiveImageData
110
204
 
@@ -116,7 +210,8 @@ interface ResponsiveImageData {
116
210
  width: number;
117
211
  height: number;
118
212
  srcSet?: string;
119
- lqipSrc?: string;
213
+ lqipSrc?: string; // Deprecated: use blurDataURL instead
214
+ blurDataURL?: string; // Base64 encoded blur placeholder (Next.js Image compatible)
120
215
  }
121
216
  ```
122
217
 
@@ -135,10 +230,12 @@ import type { ImageProps, ResponsiveImageData } from "@son426/vite-image/react";
135
230
 
136
231
  - Responsive srcSet (640px, 1024px, 1920px widths)
137
232
  - Image metadata (1920px width)
138
- - LQIP (20px width, blurred, low quality, inline base64)
233
+ - Blur placeholder (20px width, blurred, low quality, inline base64 as `blurDataURL`)
139
234
 
140
235
  2. **Image Component**: The `<Image />` component handles:
141
- - LQIP display while the main image loads
236
+ - Automatic `sizes` calculation from `srcSet` breakpoints
237
+ - Placeholder display (`blur`, `empty`, or custom data URL)
238
+ - Priority loading with `react-dom`'s `preload` API when `priority={true}`
142
239
  - Responsive image loading with srcSet
143
240
  - Proper aspect ratio maintenance
144
241
  - Fill mode for container-filling images
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import 'react';
2
+ import 'react-dom';
2
3
  import 'react/jsx-runtime';
3
4
  import { imagetools } from 'vite-imagetools';
4
5
 
@@ -24,7 +25,8 @@ function viteImage(options) {
24
25
  width: meta.width,
25
26
  height: meta.height,
26
27
  srcSet: srcSet,
27
- lqipSrc: lqipSrc
28
+ lqipSrc: lqipSrc, // Deprecated: \uD558\uC704 \uD638\uD658\uC131\uC744 \uC704\uD574 \uC720\uC9C0
29
+ blurDataURL: lqipSrc // Next.js Image \uD638\uD658\uC131\uC744 \uC704\uD55C blurDataURL
28
30
  };
29
31
  `;
30
32
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin/index.ts"],"names":[],"mappings":";;;;;AAyBO,SAAS,UAAU,OAAA,EAAkD;AAE1E,EAAA,MAAM,cAAA,GAA+B;AAAA,IACnC,IAAA,EAAM,8BAAA;AAAA,IACN,OAAA,EAAS,KAAA;AAAA,IACT,MAAM,KAAK,EAAA,EAAY;AACrB,MAAA,MAAM,CAAC,QAAA,EAAU,MAAM,CAAA,GAAI,EAAA,CAAG,MAAM,GAAG,CAAA;AACvC,MAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,MAAM,CAAA;AAEzC,MAAA,IAAI,MAAA,CAAO,GAAA,CAAI,YAAY,CAAA,EAAG;AAC5B,QAAA,MAAM,YAAA,GAAe,uCAAA;AACrB,QAAA,MAAM,UAAA,GAAa,4BAAA;AACnB,QAAA,MAAM,UAAA,GAAa,2CAAA;AAEnB,QAAA,OAAO;AAAA,8BAAA,EACiB,QAAQ,IAAI,YAAY,CAAA;AAAA,4BAAA,EAC1B,QAAQ,IAAI,UAAU,CAAA;AAAA,+BAAA,EACnB,QAAQ,IAAI,UAAU,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAAA,CAAA;AAAA,MAUjD;AAEA,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,GACF;AAEA,EAAA,OAAO,CAAC,cAAA,EAAgB,UAAA,CAAW,OAAO,CAAC,CAAA;AAC7C","file":"index.js","sourcesContent":["import type { PluginOption } from \"vite\";\nimport { imagetools } from \"vite-imagetools\";\n\nexport type ViteImagePluginOptions = Parameters<typeof imagetools>[0];\n\n/**\n * Vite plugin for image optimization using vite-imagetools\n * This plugin handles ?vite-image queries and uses imagetools for image processing\n *\n * @param options - Options to pass to vite-imagetools\n * @returns Array of Vite plugins (vite-image macro and imagetools)\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { defineConfig } from 'vite';\n * import { viteImage } from '@son426/vite-image/plugin';\n *\n * export default defineConfig({\n * plugins: [\n * ...viteImage(),\n * ],\n * });\n * ```\n */\nexport function viteImage(options?: ViteImagePluginOptions): PluginOption[] {\n // ์ปค์Šคํ…€ ํ”Œ๋Ÿฌ๊ทธ์ธ: ?vite-image ์ฟผ๋ฆฌ๋ฅผ ์ฒ˜๋ฆฌ\n const viteImageMacro: PluginOption = {\n name: \"vite-plugin-vite-image-macro\",\n enforce: \"pre\" as const,\n async load(id: string) {\n const [basePath, search] = id.split(\"?\");\n const params = new URLSearchParams(search);\n\n if (params.has(\"vite-image\")) {\n const srcSetParams = \"w=640;1024;1920&format=webp&as=srcset\";\n const metaParams = \"w=1920&format=webp&as=meta\";\n const lqipParams = \"w=20&blur=2&quality=20&format=webp&inline\";\n\n return `\n import srcSet from \"${basePath}?${srcSetParams}\";\n import meta from \"${basePath}?${metaParams}\";\n import lqipSrc from \"${basePath}?${lqipParams}\";\n \n export default {\n src: meta.src,\n width: meta.width,\n height: meta.height,\n srcSet: srcSet,\n lqipSrc: lqipSrc\n };\n `;\n }\n\n return null;\n },\n };\n\n return [viteImageMacro, imagetools(options)];\n}\n"]}
1
+ {"version":3,"sources":["../src/plugin/index.ts"],"names":[],"mappings":";;;;;;AAyBO,SAAS,UAAU,OAAA,EAAkD;AAE1E,EAAA,MAAM,cAAA,GAA+B;AAAA,IACnC,IAAA,EAAM,8BAAA;AAAA,IACN,OAAA,EAAS,KAAA;AAAA,IACT,MAAM,KAAK,EAAA,EAAY;AACrB,MAAA,MAAM,CAAC,QAAA,EAAU,MAAM,CAAA,GAAI,EAAA,CAAG,MAAM,GAAG,CAAA;AACvC,MAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,MAAM,CAAA;AAEzC,MAAA,IAAI,MAAA,CAAO,GAAA,CAAI,YAAY,CAAA,EAAG;AAC5B,QAAA,MAAM,YAAA,GAAe,uCAAA;AACrB,QAAA,MAAM,UAAA,GAAa,4BAAA;AACnB,QAAA,MAAM,UAAA,GAAa,2CAAA;AAEnB,QAAA,OAAO;AAAA,8BAAA,EACiB,QAAQ,IAAI,YAAY,CAAA;AAAA,4BAAA,EAC1B,QAAQ,IAAI,UAAU,CAAA;AAAA,+BAAA,EACnB,QAAQ,IAAI,UAAU,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAAA,CAAA;AAAA,MAWjD;AAEA,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,GACF;AAEA,EAAA,OAAO,CAAC,cAAA,EAAgB,UAAA,CAAW,OAAO,CAAC,CAAA;AAC7C","file":"index.js","sourcesContent":["import type { PluginOption } from \"vite\";\nimport { imagetools } from \"vite-imagetools\";\n\nexport type ViteImagePluginOptions = Parameters<typeof imagetools>[0];\n\n/**\n * Vite plugin for image optimization using vite-imagetools\n * This plugin handles ?vite-image queries and uses imagetools for image processing\n *\n * @param options - Options to pass to vite-imagetools\n * @returns Array of Vite plugins (vite-image macro and imagetools)\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { defineConfig } from 'vite';\n * import { viteImage } from '@son426/vite-image/plugin';\n *\n * export default defineConfig({\n * plugins: [\n * ...viteImage(),\n * ],\n * });\n * ```\n */\nexport function viteImage(options?: ViteImagePluginOptions): PluginOption[] {\n // ์ปค์Šคํ…€ ํ”Œ๋Ÿฌ๊ทธ์ธ: ?vite-image ์ฟผ๋ฆฌ๋ฅผ ์ฒ˜๋ฆฌ\n const viteImageMacro: PluginOption = {\n name: \"vite-plugin-vite-image-macro\",\n enforce: \"pre\" as const,\n async load(id: string) {\n const [basePath, search] = id.split(\"?\");\n const params = new URLSearchParams(search);\n\n if (params.has(\"vite-image\")) {\n const srcSetParams = \"w=640;1024;1920&format=webp&as=srcset\";\n const metaParams = \"w=1920&format=webp&as=meta\";\n const lqipParams = \"w=20&blur=2&quality=20&format=webp&inline\";\n\n return `\n import srcSet from \"${basePath}?${srcSetParams}\";\n import meta from \"${basePath}?${metaParams}\";\n import lqipSrc from \"${basePath}?${lqipParams}\";\n \n export default {\n src: meta.src,\n width: meta.width,\n height: meta.height,\n srcSet: srcSet,\n lqipSrc: lqipSrc, // Deprecated: ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด ์œ ์ง€\n blurDataURL: lqipSrc // Next.js Image ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•œ blurDataURL\n };\n `;\n }\n\n return null;\n },\n };\n\n return [viteImageMacro, imagetools(options)];\n}\n"]}
@@ -22,7 +22,8 @@ function viteImage(options) {
22
22
  width: meta.width,
23
23
  height: meta.height,
24
24
  srcSet: srcSet,
25
- lqipSrc: lqipSrc
25
+ lqipSrc: lqipSrc, // Deprecated: \uD558\uC704 \uD638\uD658\uC131\uC744 \uC704\uD574 \uC720\uC9C0
26
+ blurDataURL: lqipSrc // Next.js Image \uD638\uD658\uC131\uC744 \uC704\uD55C blurDataURL
26
27
  };
27
28
  `;
28
29
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugin/index.ts"],"names":[],"mappings":";;;AAyBO,SAAS,UAAU,OAAA,EAAkD;AAE1E,EAAA,MAAM,cAAA,GAA+B;AAAA,IACnC,IAAA,EAAM,8BAAA;AAAA,IACN,OAAA,EAAS,KAAA;AAAA,IACT,MAAM,KAAK,EAAA,EAAY;AACrB,MAAA,MAAM,CAAC,QAAA,EAAU,MAAM,CAAA,GAAI,EAAA,CAAG,MAAM,GAAG,CAAA;AACvC,MAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,MAAM,CAAA;AAEzC,MAAA,IAAI,MAAA,CAAO,GAAA,CAAI,YAAY,CAAA,EAAG;AAC5B,QAAA,MAAM,YAAA,GAAe,uCAAA;AACrB,QAAA,MAAM,UAAA,GAAa,4BAAA;AACnB,QAAA,MAAM,UAAA,GAAa,2CAAA;AAEnB,QAAA,OAAO;AAAA,8BAAA,EACiB,QAAQ,IAAI,YAAY,CAAA;AAAA,4BAAA,EAC1B,QAAQ,IAAI,UAAU,CAAA;AAAA,+BAAA,EACnB,QAAQ,IAAI,UAAU,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAAA,CAAA;AAAA,MAUjD;AAEA,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,GACF;AAEA,EAAA,OAAO,CAAC,cAAA,EAAgB,UAAA,CAAW,OAAO,CAAC,CAAA;AAC7C","file":"index.js","sourcesContent":["import type { PluginOption } from \"vite\";\nimport { imagetools } from \"vite-imagetools\";\n\nexport type ViteImagePluginOptions = Parameters<typeof imagetools>[0];\n\n/**\n * Vite plugin for image optimization using vite-imagetools\n * This plugin handles ?vite-image queries and uses imagetools for image processing\n *\n * @param options - Options to pass to vite-imagetools\n * @returns Array of Vite plugins (vite-image macro and imagetools)\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { defineConfig } from 'vite';\n * import { viteImage } from '@son426/vite-image/plugin';\n *\n * export default defineConfig({\n * plugins: [\n * ...viteImage(),\n * ],\n * });\n * ```\n */\nexport function viteImage(options?: ViteImagePluginOptions): PluginOption[] {\n // ์ปค์Šคํ…€ ํ”Œ๋Ÿฌ๊ทธ์ธ: ?vite-image ์ฟผ๋ฆฌ๋ฅผ ์ฒ˜๋ฆฌ\n const viteImageMacro: PluginOption = {\n name: \"vite-plugin-vite-image-macro\",\n enforce: \"pre\" as const,\n async load(id: string) {\n const [basePath, search] = id.split(\"?\");\n const params = new URLSearchParams(search);\n\n if (params.has(\"vite-image\")) {\n const srcSetParams = \"w=640;1024;1920&format=webp&as=srcset\";\n const metaParams = \"w=1920&format=webp&as=meta\";\n const lqipParams = \"w=20&blur=2&quality=20&format=webp&inline\";\n\n return `\n import srcSet from \"${basePath}?${srcSetParams}\";\n import meta from \"${basePath}?${metaParams}\";\n import lqipSrc from \"${basePath}?${lqipParams}\";\n \n export default {\n src: meta.src,\n width: meta.width,\n height: meta.height,\n srcSet: srcSet,\n lqipSrc: lqipSrc\n };\n `;\n }\n\n return null;\n },\n };\n\n return [viteImageMacro, imagetools(options)];\n}\n"]}
1
+ {"version":3,"sources":["../../src/plugin/index.ts"],"names":[],"mappings":";;;AAyBO,SAAS,UAAU,OAAA,EAAkD;AAE1E,EAAA,MAAM,cAAA,GAA+B;AAAA,IACnC,IAAA,EAAM,8BAAA;AAAA,IACN,OAAA,EAAS,KAAA;AAAA,IACT,MAAM,KAAK,EAAA,EAAY;AACrB,MAAA,MAAM,CAAC,QAAA,EAAU,MAAM,CAAA,GAAI,EAAA,CAAG,MAAM,GAAG,CAAA;AACvC,MAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,MAAM,CAAA;AAEzC,MAAA,IAAI,MAAA,CAAO,GAAA,CAAI,YAAY,CAAA,EAAG;AAC5B,QAAA,MAAM,YAAA,GAAe,uCAAA;AACrB,QAAA,MAAM,UAAA,GAAa,4BAAA;AACnB,QAAA,MAAM,UAAA,GAAa,2CAAA;AAEnB,QAAA,OAAO;AAAA,8BAAA,EACiB,QAAQ,IAAI,YAAY,CAAA;AAAA,4BAAA,EAC1B,QAAQ,IAAI,UAAU,CAAA;AAAA,+BAAA,EACnB,QAAQ,IAAI,UAAU,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAAA,CAAA;AAAA,MAWjD;AAEA,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,GACF;AAEA,EAAA,OAAO,CAAC,cAAA,EAAgB,UAAA,CAAW,OAAO,CAAC,CAAA;AAC7C","file":"index.js","sourcesContent":["import type { PluginOption } from \"vite\";\nimport { imagetools } from \"vite-imagetools\";\n\nexport type ViteImagePluginOptions = Parameters<typeof imagetools>[0];\n\n/**\n * Vite plugin for image optimization using vite-imagetools\n * This plugin handles ?vite-image queries and uses imagetools for image processing\n *\n * @param options - Options to pass to vite-imagetools\n * @returns Array of Vite plugins (vite-image macro and imagetools)\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { defineConfig } from 'vite';\n * import { viteImage } from '@son426/vite-image/plugin';\n *\n * export default defineConfig({\n * plugins: [\n * ...viteImage(),\n * ],\n * });\n * ```\n */\nexport function viteImage(options?: ViteImagePluginOptions): PluginOption[] {\n // ์ปค์Šคํ…€ ํ”Œ๋Ÿฌ๊ทธ์ธ: ?vite-image ์ฟผ๋ฆฌ๋ฅผ ์ฒ˜๋ฆฌ\n const viteImageMacro: PluginOption = {\n name: \"vite-plugin-vite-image-macro\",\n enforce: \"pre\" as const,\n async load(id: string) {\n const [basePath, search] = id.split(\"?\");\n const params = new URLSearchParams(search);\n\n if (params.has(\"vite-image\")) {\n const srcSetParams = \"w=640;1024;1920&format=webp&as=srcset\";\n const metaParams = \"w=1920&format=webp&as=meta\";\n const lqipParams = \"w=20&blur=2&quality=20&format=webp&inline\";\n\n return `\n import srcSet from \"${basePath}?${srcSetParams}\";\n import meta from \"${basePath}?${metaParams}\";\n import lqipSrc from \"${basePath}?${lqipParams}\";\n \n export default {\n src: meta.src,\n width: meta.width,\n height: meta.height,\n srcSet: srcSet,\n lqipSrc: lqipSrc, // Deprecated: ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด ์œ ์ง€\n blurDataURL: lqipSrc // Next.js Image ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•œ blurDataURL\n };\n `;\n }\n\n return null;\n },\n };\n\n return [viteImageMacro, imagetools(options)];\n}\n"]}
@@ -10,11 +10,17 @@ interface ResponsiveImageData {
10
10
  height: number;
11
11
  srcSet?: string;
12
12
  lqipSrc?: string;
13
+ blurDataURL?: string;
13
14
  }
14
15
 
16
+ type PlaceholderValue = "empty" | "blur" | string;
15
17
  interface BaseImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "srcSet" | "width" | "height"> {
16
18
  src: ResponsiveImageData;
17
19
  sizes?: string;
20
+ placeholder?: PlaceholderValue;
21
+ priority?: boolean;
22
+ onLoad?: (event: React.SyntheticEvent<HTMLImageElement, Event>) => void;
23
+ onError?: (event: React.SyntheticEvent<HTMLImageElement, Event>) => void;
18
24
  }
19
25
  interface FillImageProps extends BaseImageProps {
20
26
  fill: true;
@@ -24,6 +30,8 @@ interface StandardImageProps extends BaseImageProps {
24
30
  }
25
31
  type ImageProps = FillImageProps | StandardImageProps;
26
32
  declare function Image({ src, // ์ด์ œ ์ด src๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
27
- fill, sizes, className, style, ...props }: ImageProps): react_jsx_runtime.JSX.Element;
33
+ fill, sizes, placeholder, // ๊ธฐ๋ณธ๊ฐ’: empty (Next.js Image ํ˜ธํ™˜)
34
+ priority, // ๊ธฐ๋ณธ๊ฐ’: false (Next.js Image ํ˜ธํ™˜)
35
+ className, style, onLoad, onError, ...props }: ImageProps): react_jsx_runtime.JSX.Element;
28
36
 
29
37
  export { type ImageProps, type ResponsiveImageData, Image as default };
@@ -1,14 +1,44 @@
1
1
  import { useState } from 'react';
2
+ import { preload } from 'react-dom';
2
3
  import { jsxs, jsx } from 'react/jsx-runtime';
3
4
 
4
5
  // src/react/Image.tsx
6
+ function generateSizesFromSrcSet(srcSet) {
7
+ if (!srcSet) {
8
+ return "100vw";
9
+ }
10
+ const widthMatches = srcSet.match(/(\d+)w/g);
11
+ if (!widthMatches || widthMatches.length === 0) {
12
+ return "100vw";
13
+ }
14
+ const breakpoints = widthMatches.map((match) => parseInt(match.replace("w", ""), 10)).sort((a, b) => a - b);
15
+ if (breakpoints.length === 0) {
16
+ return "100vw";
17
+ }
18
+ const sizeParts = [];
19
+ for (let i = 0; i < breakpoints.length; i++) {
20
+ const breakpoint = breakpoints[i];
21
+ if (i === breakpoints.length - 1) {
22
+ sizeParts.push(`${breakpoint}px`);
23
+ } else {
24
+ sizeParts.push(`(max-width: ${breakpoint}px) 100vw`);
25
+ }
26
+ }
27
+ return sizeParts.join(", ");
28
+ }
5
29
  function Image({
6
30
  src,
7
31
  // ์ด์ œ ์ด src๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
8
32
  fill = false,
9
- sizes = "100vw",
33
+ sizes,
34
+ placeholder = "empty",
35
+ // ๊ธฐ๋ณธ๊ฐ’: empty (Next.js Image ํ˜ธํ™˜)
36
+ priority = false,
37
+ // ๊ธฐ๋ณธ๊ฐ’: false (Next.js Image ํ˜ธํ™˜)
10
38
  className = "",
11
39
  style,
40
+ onLoad,
41
+ onError,
12
42
  ...props
13
43
  }) {
14
44
  const [isImageLoaded, setIsImageLoaded] = useState(false);
@@ -16,9 +46,33 @@ function Image({
16
46
  src: currentSrc,
17
47
  srcSet: currentSrcSet,
18
48
  lqipSrc: currentLqip,
49
+ blurDataURL,
19
50
  width: currentWidth,
20
51
  height: currentHeight
21
52
  } = src;
53
+ const computedSizes = sizes ?? (fill ? "100vw" : generateSizesFromSrcSet(currentSrcSet));
54
+ if (priority && currentSrc) {
55
+ preload(currentSrc, {
56
+ as: "image",
57
+ fetchPriority: "high",
58
+ ...currentSrcSet ? { imageSrcSet: currentSrcSet } : {},
59
+ ...computedSizes ? { imageSizes: computedSizes } : {}
60
+ });
61
+ }
62
+ const getPlaceholderSrc = () => {
63
+ if (placeholder === "empty") {
64
+ return void 0;
65
+ }
66
+ if (placeholder === "blur") {
67
+ return blurDataURL ?? currentLqip;
68
+ }
69
+ if (placeholder.startsWith("data:image/")) {
70
+ return placeholder;
71
+ }
72
+ return void 0;
73
+ };
74
+ const placeholderSrc = getPlaceholderSrc();
75
+ const hasShowPlaceholder = !!placeholderSrc;
22
76
  const containerStyle = fill ? {
23
77
  position: "absolute",
24
78
  top: 0,
@@ -46,13 +100,17 @@ function Image({
46
100
  height: "100%",
47
101
  objectFit: "cover"
48
102
  };
49
- const lqipStyle = {
103
+ const placeholderStyle = {
50
104
  ...imgStyle,
51
- filter: "blur(20px)",
52
- transform: "scale(1.1)",
53
105
  transition: "opacity 500ms ease-out",
54
106
  opacity: isImageLoaded ? 0 : 1,
55
- zIndex: 1
107
+ zIndex: 1,
108
+ pointerEvents: "none",
109
+ // blur placeholder์ผ ๋•Œ๋งŒ blur ํšจ๊ณผ ์ ์šฉ
110
+ ...placeholder === "blur" ? {
111
+ filter: "blur(20px)",
112
+ transform: "scale(1.1)"
113
+ } : {}
56
114
  };
57
115
  return /* @__PURE__ */ jsxs("div", { className, style: mergedContainerStyle, children: [
58
116
  /* @__PURE__ */ jsx(
@@ -61,14 +119,28 @@ function Image({
61
119
  ...props,
62
120
  src: currentSrc,
63
121
  srcSet: currentSrcSet,
64
- sizes,
122
+ sizes: computedSizes,
65
123
  width: fill ? void 0 : currentWidth,
66
124
  height: fill ? void 0 : currentHeight,
67
- onLoad: () => setIsImageLoaded(true),
125
+ loading: priority ? "eager" : "lazy",
126
+ fetchPriority: priority ? "high" : void 0,
127
+ onLoad: (e) => {
128
+ setIsImageLoaded(true);
129
+ onLoad?.(e);
130
+ },
131
+ onError,
68
132
  style: { ...imgStyle, zIndex: 0 }
69
133
  }
70
134
  ),
71
- currentLqip && /* @__PURE__ */ jsx("img", { src: currentLqip, alt: "", "aria-hidden": "true", style: lqipStyle })
135
+ hasShowPlaceholder && /* @__PURE__ */ jsx(
136
+ "img",
137
+ {
138
+ src: placeholderSrc,
139
+ alt: "",
140
+ "aria-hidden": "true",
141
+ style: placeholderStyle
142
+ }
143
+ )
72
144
  ] });
73
145
  }
74
146
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/Image.tsx"],"names":[],"mappings":";;;;AAwBe,SAAR,KAAA,CAAuB;AAAA,EAC5B,GAAA;AAAA;AAAA,EACA,IAAA,GAAO,KAAA;AAAA,EACP,KAAA,GAAQ,OAAA;AAAA,EACR,SAAA,GAAY,EAAA;AAAA,EACZ,KAAA;AAAA,EACA,GAAG;AACL,CAAA,EAAe;AACb,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAI,SAAS,KAAK,CAAA;AAGxD,EAAA,MAAM;AAAA,IACJ,GAAA,EAAK,UAAA;AAAA,IACL,MAAA,EAAQ,aAAA;AAAA,IACR,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO,YAAA;AAAA,IACP,MAAA,EAAQ;AAAA,GACV,GAAI,GAAA;AAGJ,EAAA,MAAM,iBAAgC,IAAA,GAClC;AAAA,IACE,QAAA,EAAU,UAAA;AAAA,IACV,GAAA,EAAK,CAAA;AAAA,IACL,IAAA,EAAM,CAAA;AAAA,IACN,KAAA,EAAO,CAAA;AAAA,IACP,MAAA,EAAQ,CAAA;AAAA,IACR,KAAA,EAAO,MAAA;AAAA,IACP,MAAA,EAAQ,MAAA;AAAA,IACR,QAAA,EAAU;AAAA,GACZ,GACA;AAAA,IACE,QAAA,EAAU,UAAA;AAAA,IACV,KAAA,EAAO,MAAA;AAAA,IACP,QAAA,EAAU,QAAA;AAAA;AAAA,IAEV,GAAI,YAAA,IAAgB,aAAA,GAChB,EAAE,WAAA,EAAa,CAAA,EAAG,YAAY,CAAA,GAAA,EAAM,aAAa,CAAA,CAAA,EAAG,GACpD;AAAC,GACP;AAEJ,EAAA,MAAM,oBAAA,GAAuB,EAAE,GAAG,cAAA,EAAgB,GAAG,KAAA,EAAM;AAG3D,EAAA,MAAM,QAAA,GAA0B;AAAA,IAC9B,QAAA,EAAU,UAAA;AAAA,IACV,GAAA,EAAK,CAAA;AAAA,IACL,IAAA,EAAM,CAAA;AAAA,IACN,KAAA,EAAO,CAAA;AAAA,IACP,MAAA,EAAQ,CAAA;AAAA,IACR,KAAA,EAAO,MAAA;AAAA,IACP,MAAA,EAAQ,MAAA;AAAA,IACR,SAAA,EAAW;AAAA,GACb;AAGA,EAAA,MAAM,SAAA,GAA2B;AAAA,IAC/B,GAAG,QAAA;AAAA,IACH,MAAA,EAAQ,YAAA;AAAA,IACR,SAAA,EAAW,YAAA;AAAA,IACX,UAAA,EAAY,wBAAA;AAAA,IACZ,OAAA,EAAS,gBAAgB,CAAA,GAAI,CAAA;AAAA,IAC7B,MAAA,EAAQ;AAAA,GACV;AAEA,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAsB,KAAA,EAAO,oBAAA,EAEhC,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACE,GAAG,KAAA;AAAA,QACJ,GAAA,EAAK,UAAA;AAAA,QACL,MAAA,EAAQ,aAAA;AAAA,QACR,KAAA;AAAA,QACA,KAAA,EAAO,OAAO,MAAA,GAAY,YAAA;AAAA,QAC1B,MAAA,EAAQ,OAAO,MAAA,GAAY,aAAA;AAAA,QAC3B,MAAA,EAAQ,MAAM,gBAAA,CAAiB,IAAI,CAAA;AAAA,QACnC,KAAA,EAAO,EAAE,GAAG,QAAA,EAAU,QAAQ,CAAA;AAAE;AAAA,KAClC;AAAA,IAGC,WAAA,oBACC,GAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAK,WAAA,EAAa,KAAI,EAAA,EAAG,aAAA,EAAY,MAAA,EAAO,KAAA,EAAO,SAAA,EAAW;AAAA,GAAA,EAEvE,CAAA;AAEJ","file":"index.js","sourcesContent":["import { useState, type ImgHTMLAttributes, type CSSProperties } from \"react\";\nimport type { ResponsiveImageData } from \"../types\";\n\n// ?vite-image import ๊ฒฐ๊ณผ ํƒ€์ž…\ninterface BaseImageProps\n extends Omit<\n ImgHTMLAttributes<HTMLImageElement>,\n \"src\" | \"srcSet\" | \"width\" | \"height\"\n > {\n // ํ•ต์‹ฌ ๋ณ€๊ฒฝ: src๋Š” ๋ฌด์กฐ๊ฑด ์ตœ์ ํ™”๋œ ์ด๋ฏธ์ง€ ๊ฐ์ฒด๋งŒ ๋ฐ›์Œ\n src: ResponsiveImageData;\n sizes?: string;\n}\n\ninterface FillImageProps extends BaseImageProps {\n fill: true;\n}\n\ninterface StandardImageProps extends BaseImageProps {\n fill?: false | undefined;\n}\n\nexport type ImageProps = FillImageProps | StandardImageProps;\n\nexport default function Image({\n src, // ์ด์ œ ์ด src๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.\n fill = false,\n sizes = \"100vw\",\n className = \"\",\n style,\n ...props\n}: ImageProps) {\n const [isImageLoaded, setIsImageLoaded] = useState(false);\n\n // 1. ๋ฐ์ดํ„ฐ ์ถ”์ถœ: src ๊ฐ์ฒด์—์„œ ๋ฐ”๋กœ ๊บผ๋‚ด์”€ (๋ณ‘ํ•ฉ ๋กœ์ง ์ œ๊ฑฐ)\n const {\n src: currentSrc,\n srcSet: currentSrcSet,\n lqipSrc: currentLqip,\n width: currentWidth,\n height: currentHeight,\n } = src;\n\n // 2. ์ปจํ…Œ์ด๋„ˆ ์Šคํƒ€์ผ ๊ณ„์‚ฐ (๊ธฐ์กด ๋กœ์ง ์œ ์ง€)\n const containerStyle: CSSProperties = fill\n ? {\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n width: \"100%\",\n height: \"100%\",\n overflow: \"hidden\",\n }\n : {\n position: \"relative\",\n width: \"100%\",\n overflow: \"hidden\",\n // Standard ๋ชจ๋“œ์ผ ๋•Œ aspect-ratio ์ฒ˜๋ฆฌ\n ...(currentWidth && currentHeight\n ? { aspectRatio: `${currentWidth} / ${currentHeight}` }\n : {}),\n };\n\n const mergedContainerStyle = { ...containerStyle, ...style };\n\n // 3. ์‹ค์ œ ์ด๋ฏธ์ง€ ์Šคํƒ€์ผ (๊ธฐ์กด ๋กœ์ง ์œ ์ง€)\n const imgStyle: CSSProperties = {\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\",\n };\n\n // 4. LQIP ์Šคํƒ€์ผ (๊ธฐ์กด ๋กœ์ง ์œ ์ง€)\n const lqipStyle: CSSProperties = {\n ...imgStyle,\n filter: \"blur(20px)\",\n transform: \"scale(1.1)\",\n transition: \"opacity 500ms ease-out\",\n opacity: isImageLoaded ? 0 : 1,\n zIndex: 1,\n };\n\n return (\n <div className={className} style={mergedContainerStyle}>\n {/* ์‹ค์ œ ์ด๋ฏธ์ง€ */}\n <img\n {...props}\n src={currentSrc}\n srcSet={currentSrcSet}\n sizes={sizes}\n width={fill ? undefined : currentWidth}\n height={fill ? undefined : currentHeight}\n onLoad={() => setIsImageLoaded(true)}\n style={{ ...imgStyle, zIndex: 0 }}\n />\n\n {/* LQIP ๋ ˆ์ด์–ด */}\n {currentLqip && (\n <img src={currentLqip} alt=\"\" aria-hidden=\"true\" style={lqipStyle} />\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../../src/react/Image.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { useState, type ImgHTMLAttributes, type CSSProperties } from \"react\";\nimport { preload } from \"react-dom\";\nimport type { ResponsiveImageData } from \"../types\";\n\n// placeholder ํƒ€์ž… ์ •์˜ (Next.js Image ํ˜ธํ™˜)\ntype PlaceholderValue = \"empty\" | \"blur\" | string; // string์€ data:image/... ํ˜•์‹\n\n// ?vite-image import ๊ฒฐ๊ณผ ํƒ€์ž…\ninterface BaseImageProps\n extends Omit<\n ImgHTMLAttributes<HTMLImageElement>,\n \"src\" | \"srcSet\" | \"width\" | \"height\"\n > {\n // ํ•ต์‹ฌ ๋ณ€๊ฒฝ: src๋Š” ๋ฌด์กฐ๊ฑด ์ตœ์ ํ™”๋œ ์ด๋ฏธ์ง€ ๊ฐ์ฒด๋งŒ ๋ฐ›์Œ\n src: ResponsiveImageData;\n sizes?: string; // Optional: ์ œ๊ณต๋˜์ง€ ์•Š์œผ๋ฉด ์ž๋™ ๊ณ„์‚ฐ\n placeholder?: PlaceholderValue; // Next.js Image ํ˜ธํ™˜: 'empty' | 'blur' | 'data:image/...'\n priority?: boolean; // Next.js Image ํ˜ธํ™˜: true์ผ ๊ฒฝ์šฐ ๋†’์€ ์šฐ์„ ์ˆœ์œ„๋กœ preload\n onLoad?: (event: React.SyntheticEvent<HTMLImageElement, Event>) => void;\n onError?: (event: React.SyntheticEvent<HTMLImageElement, Event>) => void;\n}\n\ninterface FillImageProps extends BaseImageProps {\n fill: true;\n}\n\ninterface StandardImageProps extends BaseImageProps {\n fill?: false | undefined;\n}\n\nexport type ImageProps = FillImageProps | StandardImageProps;\n\n/**\n * srcSet์—์„œ breakpoints๋ฅผ ์ถ”์ถœํ•˜์—ฌ sizes ๋ฌธ์ž์—ด์„ ์ž๋™ ์ƒ์„ฑ\n * @param srcSet - \"url1 640w, url2 1024w, url3 1920w\" ํ˜•์‹์˜ ๋ฌธ์ž์—ด\n * @returns sizes ์†์„ฑ ๋ฌธ์ž์—ด\n */\nfunction generateSizesFromSrcSet(srcSet?: string): string {\n if (!srcSet) {\n return \"100vw\";\n }\n\n // srcSet์—์„œ width ๊ฐ’๋“ค ์ถ”์ถœ (์˜ˆ: \"640w\", \"1024w\", \"1920w\")\n const widthMatches = srcSet.match(/(\\d+)w/g);\n if (!widthMatches || widthMatches.length === 0) {\n return \"100vw\";\n }\n\n // width ๊ฐ’๋“ค์„ ์ˆซ์ž๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  ์ •๋ ฌ\n const breakpoints = widthMatches\n .map((match) => parseInt(match.replace(\"w\", \"\"), 10))\n .sort((a, b) => a - b);\n\n if (breakpoints.length === 0) {\n return \"100vw\";\n }\n\n // breakpoints๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ sizes ๋ฌธ์ž์—ด ์ƒ์„ฑ\n // ์˜ˆ: \"(max-width: 640px) 100vw, (max-width: 1024px) 100vw, 1920px\"\n const sizeParts: string[] = [];\n\n for (let i = 0; i < breakpoints.length; i++) {\n const breakpoint = breakpoints[i];\n if (i === breakpoints.length - 1) {\n // ๋งˆ์ง€๋ง‰ breakpoint๋Š” ์ตœ๋Œ€ ํฌ๊ธฐ๋กœ ์„ค์ •\n sizeParts.push(`${breakpoint}px`);\n } else {\n // ์ค‘๊ฐ„ breakpoint๋“ค์€ ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ๋กœ ์„ค์ •\n sizeParts.push(`(max-width: ${breakpoint}px) 100vw`);\n }\n }\n\n return sizeParts.join(\", \");\n}\n\nexport default function Image({\n src, // ์ด์ œ ์ด src๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.\n fill = false,\n sizes,\n placeholder = \"empty\", // ๊ธฐ๋ณธ๊ฐ’: empty (Next.js Image ํ˜ธํ™˜)\n priority = false, // ๊ธฐ๋ณธ๊ฐ’: false (Next.js Image ํ˜ธํ™˜)\n className = \"\",\n style,\n onLoad,\n onError,\n ...props\n}: ImageProps) {\n const [isImageLoaded, setIsImageLoaded] = useState(false);\n\n // 1. ๋ฐ์ดํ„ฐ ์ถ”์ถœ: src ๊ฐ์ฒด์—์„œ ๋ฐ”๋กœ ๊บผ๋‚ด์”€ (๋ณ‘ํ•ฉ ๋กœ์ง ์ œ๊ฑฐ)\n const {\n src: currentSrc,\n srcSet: currentSrcSet,\n lqipSrc: currentLqip,\n blurDataURL,\n width: currentWidth,\n height: currentHeight,\n } = src;\n\n // 2. sizes ์ž๋™ ๊ณ„์‚ฐ: ์ œ๊ณต๋˜์ง€ ์•Š์œผ๋ฉด srcSet ๊ธฐ๋ฐ˜์œผ๋กœ ์ž๋™ ์ƒ์„ฑ\n const computedSizes =\n sizes ?? (fill ? \"100vw\" : generateSizesFromSrcSet(currentSrcSet));\n\n if (priority && currentSrc) {\n preload(currentSrc, {\n as: \"image\",\n fetchPriority: \"high\",\n ...(currentSrcSet ? { imageSrcSet: currentSrcSet } : {}),\n ...(computedSizes ? { imageSizes: computedSizes } : {}),\n });\n }\n\n // 3. placeholder ์ฒ˜๋ฆฌ (Next.js Image ํ˜ธํ™˜)\n const getPlaceholderSrc = (): string | undefined => {\n if (placeholder === \"empty\") {\n return undefined;\n }\n if (placeholder === \"blur\") {\n // blurDataURL ์šฐ์„ , ์—†์œผ๋ฉด lqipSrc ์‚ฌ์šฉ (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ)\n return blurDataURL ?? currentLqip;\n }\n // data:image/... ํ˜•์‹์˜ ์ง์ ‘ ์ œ๊ณต๋œ placeholder\n if (placeholder.startsWith(\"data:image/\")) {\n return placeholder;\n }\n // ๊ธฐ๋ณธ๊ฐ’: empty\n return undefined;\n };\n\n const placeholderSrc = getPlaceholderSrc();\n const hasShowPlaceholder = !!placeholderSrc;\n\n // 5. ์ปจํ…Œ์ด๋„ˆ ์Šคํƒ€์ผ ๊ณ„์‚ฐ (๊ธฐ์กด ๋กœ์ง ์œ ์ง€)\n const containerStyle: CSSProperties = fill\n ? {\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n width: \"100%\",\n height: \"100%\",\n overflow: \"hidden\",\n }\n : {\n position: \"relative\",\n width: \"100%\",\n overflow: \"hidden\",\n // Standard ๋ชจ๋“œ์ผ ๋•Œ aspect-ratio ์ฒ˜๋ฆฌ\n ...(currentWidth && currentHeight\n ? { aspectRatio: `${currentWidth} / ${currentHeight}` }\n : {}),\n };\n\n const mergedContainerStyle = { ...containerStyle, ...style };\n\n // 6. ์‹ค์ œ ์ด๋ฏธ์ง€ ์Šคํƒ€์ผ (๊ธฐ์กด ๋กœ์ง ์œ ์ง€)\n const imgStyle: CSSProperties = {\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\",\n };\n\n // 7. Placeholder ์Šคํƒ€์ผ (blur ๋ชจ๋“œ์ผ ๋•Œ๋งŒ blur ํšจ๊ณผ ์ ์šฉ)\n const placeholderStyle: CSSProperties = {\n ...imgStyle,\n transition: \"opacity 500ms ease-out\",\n opacity: isImageLoaded ? 0 : 1,\n zIndex: 1,\n pointerEvents: \"none\",\n\n // blur placeholder์ผ ๋•Œ๋งŒ blur ํšจ๊ณผ ์ ์šฉ\n ...(placeholder === \"blur\"\n ? {\n filter: \"blur(20px)\",\n transform: \"scale(1.1)\",\n }\n : {}),\n };\n\n return (\n <div className={className} style={mergedContainerStyle}>\n {/* ์‹ค์ œ ์ด๋ฏธ์ง€ */}\n <img\n {...props}\n src={currentSrc}\n srcSet={currentSrcSet}\n sizes={computedSizes}\n width={fill ? undefined : currentWidth}\n height={fill ? undefined : currentHeight}\n loading={priority ? \"eager\" : \"lazy\"}\n fetchPriority={priority ? \"high\" : undefined}\n onLoad={(e) => {\n setIsImageLoaded(true);\n onLoad?.(e);\n }}\n onError={onError}\n style={{ ...imgStyle, zIndex: 0 }}\n />\n\n {/* Placeholder ๋ ˆ์ด์–ด (placeholder prop์— ๋”ฐ๋ผ ํ‘œ์‹œ) */}\n {hasShowPlaceholder && (\n <img\n src={placeholderSrc}\n alt=\"\"\n aria-hidden=\"true\"\n style={placeholderStyle}\n />\n )}\n </div>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@son426/vite-image",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "A Vite plugin and React component for optimized images with LQIP support using vite-imagetools",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -47,19 +47,23 @@
47
47
  "type": "git",
48
48
  "url": ""
49
49
  },
50
+ "dependencies": {
51
+ "vite-imagetools": "^9.0.2"
52
+ },
50
53
  "peerDependencies": {
51
54
  "react": "^18.0.0 || ^19.0.0",
52
- "vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0",
53
- "vite-imagetools": "^9.0.0"
55
+ "react-dom": "^18.0.0 || ^19.0.0",
56
+ "vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
54
57
  },
55
58
  "devDependencies": {
56
59
  "@types/node": "^20.0.0",
57
60
  "@types/react": "^18.0.0 || ^19.0.0",
61
+ "@types/react-dom": "^19.2.3",
58
62
  "react": "^19.1.0",
63
+ "react-dom": "^19.1.0",
59
64
  "tsup": "^8.0.0",
60
65
  "typescript": "^5.0.0",
61
- "vite": "^7.0.4",
62
- "vite-imagetools": "^9.0.2"
66
+ "vite": "^7.0.4"
63
67
  },
64
68
  "publishConfig": {
65
69
  "access": "public"