@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 +1 -0
- package/README.md +138 -41
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin/index.js +2 -1
- package/dist/plugin/index.js.map +1 -1
- package/dist/react/index.d.ts +9 -1
- package/dist/react/index.js +80 -8
- package/dist/react/index.js.map +1 -1
- package/package.json +9 -5
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,19 +1,47 @@
|
|
|
1
1
|
# @son426/vite-image
|
|
2
2
|
|
|
3
|
-
**Next.js
|
|
3
|
+
**The Next.js `<Image />` experience, now in Vite.**
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
8
|
+
Simply add the plugin to your config, and start using the `<Image />` component immediately. No complex setups, just performant images.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
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
|
-
##
|
|
53
|
+
## Requirements
|
|
26
54
|
|
|
27
|
-
|
|
55
|
+
Just a standard Vite + React project.
|
|
28
56
|
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
- `
|
|
100
|
+
- `blurDataURL`: Low Quality Image Placeholder (base64 inline)
|
|
82
101
|
- `width` and `height`: Image dimensions
|
|
83
102
|
|
|
84
|
-
|
|
103
|
+
#### Usage Examples
|
|
85
104
|
|
|
86
|
-
|
|
105
|
+
**Basic usage:**
|
|
87
106
|
|
|
88
|
-
```
|
|
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
|
|
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
|
|
99
|
-
|
|
|
100
|
-
| `src`
|
|
101
|
-
| `fill`
|
|
102
|
-
| `sizes`
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
|
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
-
|
|
233
|
+
- Blur placeholder (20px width, blurred, low quality, inline base64 as `blurDataURL`)
|
|
139
234
|
|
|
140
235
|
2. **Image Component**: The `<Image />` component handles:
|
|
141
|
-
-
|
|
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":"
|
|
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"]}
|
package/dist/plugin/index.js
CHANGED
|
@@ -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
|
}
|
package/dist/plugin/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,
|
|
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"]}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -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,
|
|
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 };
|
package/dist/react/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/dist/react/index.js.map
CHANGED
|
@@ -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
|
+
"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
|
-
"
|
|
53
|
-
"vite
|
|
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"
|