@son426/vite-image 0.1.6 → 0.1.8
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 +2 -0
- package/README.md +90 -17
- package/dist/index.d.ts +3 -2
- package/dist/index.js +61 -18
- package/dist/index.js.map +1 -1
- package/dist/plugin/index.d.ts +14 -4
- package/dist/plugin/index.js +61 -18
- package/dist/plugin/index.js.map +1 -1
- package/dist/react/index.d.ts +5 -12
- package/dist/react/index.js +4 -1
- package/dist/react/index.js.map +1 -1
- package/dist/types-B08JIQxT.d.ts +25 -0
- package/package.json +8 -2
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -22,17 +22,26 @@ Simply add the plugin to your config, and start using the `<Image />` component
|
|
|
22
22
|
Add it to `vite.config.ts`, and use it like this:
|
|
23
23
|
|
|
24
24
|
```tsx
|
|
25
|
+
// vite.config.ts
|
|
26
|
+
viteImage({
|
|
27
|
+
autoApply: {
|
|
28
|
+
extensions: [".jpg", ".png", ".webp"],
|
|
29
|
+
include: ["src/assets/**"],
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Component
|
|
25
34
|
import Image from "@son426/vite-image/react";
|
|
26
|
-
|
|
27
|
-
import myBg from "./assets/background.jpg?vite-image";
|
|
35
|
+
import myBg from "./assets/background.jpg"; // No query needed
|
|
28
36
|
|
|
29
37
|
export default function Page() {
|
|
30
38
|
return (
|
|
31
|
-
// 2. Pass the object directly to src
|
|
32
39
|
<Image
|
|
33
40
|
src={myBg}
|
|
34
41
|
alt="Optimized Background"
|
|
35
|
-
fill
|
|
42
|
+
fill
|
|
43
|
+
priority
|
|
44
|
+
placeholder="blur"
|
|
36
45
|
/>
|
|
37
46
|
);
|
|
38
47
|
}
|
|
@@ -40,7 +49,7 @@ export default function Page() {
|
|
|
40
49
|
|
|
41
50
|
## Installation
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
Install the package. `vite-imagetools` and `@rollup/pluginutils` are included as dependencies.
|
|
44
53
|
|
|
45
54
|
```bash
|
|
46
55
|
pnpm add @son426/vite-image
|
|
@@ -70,17 +79,65 @@ import { viteImage } from "@son426/vite-image/plugin";
|
|
|
70
79
|
|
|
71
80
|
export default defineConfig({
|
|
72
81
|
plugins: [
|
|
73
|
-
//
|
|
74
|
-
viteImage(),
|
|
82
|
+
viteImage(), // Default: breakpoints [640, 1024, 1920], no autoApply
|
|
75
83
|
],
|
|
76
84
|
});
|
|
77
85
|
```
|
|
78
86
|
|
|
87
|
+
**Default configuration:**
|
|
88
|
+
|
|
89
|
+
- `breakpoints: [640, 1024, 1920]`
|
|
90
|
+
- `autoApply: undefined` (requires `?vite-image` query)
|
|
91
|
+
|
|
92
|
+
#### Configuration Options
|
|
93
|
+
|
|
94
|
+
**Custom breakpoints:**
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
viteImage({
|
|
98
|
+
breakpoints: [800, 1200, 1920],
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Auto-apply without query string:**
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
viteImage({
|
|
106
|
+
autoApply: {
|
|
107
|
+
extensions: [".jpg", ".png", ".webp"],
|
|
108
|
+
include: ["src/assets/**"],
|
|
109
|
+
exclude: ["src/icons/**"],
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Note**: `include` and `exclude` patterns are matched against actual image file paths (after alias resolution). For example, `@/assets/image.jpg` resolves to `src/assets/image.jpg`.
|
|
115
|
+
|
|
116
|
+
**With vite-imagetools options:**
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
viteImage({
|
|
120
|
+
breakpoints: [640, 1024, 1920],
|
|
121
|
+
autoApply: {
|
|
122
|
+
extensions: [".jpg", ".png"],
|
|
123
|
+
include: ["src/**"],
|
|
124
|
+
},
|
|
125
|
+
imagetools: {
|
|
126
|
+
// vite-imagetools options
|
|
127
|
+
defaultDirectives: (url) => {
|
|
128
|
+
if (url.searchParams.has("vite-image")) {
|
|
129
|
+
return new URLSearchParams("format=webp");
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
79
136
|
### 2. Use the Component
|
|
80
137
|
|
|
81
|
-
#### Using `?vite-image` query
|
|
138
|
+
#### Using `?vite-image` query
|
|
82
139
|
|
|
83
|
-
The `?vite-image` query parameter
|
|
140
|
+
The `?vite-image` query parameter automatically generates all required image data. When using `?vite-image`, the `src` prop must be an object (not a string).
|
|
84
141
|
|
|
85
142
|
```typescript
|
|
86
143
|
import Image from "@son426/vite-image/react";
|
|
@@ -91,9 +148,25 @@ function MyComponent() {
|
|
|
91
148
|
}
|
|
92
149
|
```
|
|
93
150
|
|
|
94
|
-
**
|
|
151
|
+
**Without query string (autoApply enabled):**
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// vite.config.ts
|
|
155
|
+
viteImage({
|
|
156
|
+
autoApply: {
|
|
157
|
+
extensions: [".jpg", ".png"],
|
|
158
|
+
include: ["src/assets/**"],
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Component
|
|
163
|
+
import bgImage from "@/assets/background.jpg"; // No query needed
|
|
164
|
+
<Image src={bgImage} alt="Background" />;
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Important**: The `src` prop must receive the imported object directly. String URLs are not supported.
|
|
95
168
|
|
|
96
|
-
The `?vite-image` query automatically generates:
|
|
169
|
+
The `?vite-image` query (or autoApply) automatically generates:
|
|
97
170
|
|
|
98
171
|
- `src`: Optimized image URL
|
|
99
172
|
- `srcSet`: Responsive srcSet string
|
|
@@ -182,7 +255,7 @@ import heroImage from "@/assets/hero.jpg?vite-image";
|
|
|
182
255
|
|
|
183
256
|
| Prop | Type | Required | Default | Description |
|
|
184
257
|
| ------------- | --------------------------------------------------------- | -------- | --------- | ----------------------------------------------------------------------- |
|
|
185
|
-
| `src` | `ResponsiveImageData` | Yes | - | Image data object from `?vite-image` query
|
|
258
|
+
| `src` | `ResponsiveImageData` | Yes | - | Image data object from `?vite-image` query or `autoApply` |
|
|
186
259
|
| `fill` | `boolean` | No | `false` | Fill container mode (requires parent with `position: relative`) |
|
|
187
260
|
| `sizes` | `string` | No | auto | Sizes attribute (auto-calculated from srcSet if not provided) |
|
|
188
261
|
| `priority` | `boolean` | No | `false` | High priority loading (preload + eager + fetchPriority high) |
|
|
@@ -195,14 +268,14 @@ import heroImage from "@/assets/hero.jpg?vite-image";
|
|
|
195
268
|
|
|
196
269
|
**Notes**:
|
|
197
270
|
|
|
198
|
-
- The `src` prop must be an object imported from `?vite-image` query
|
|
271
|
+
- The `src` prop must be an object imported from `?vite-image` query or via `autoApply`. String URLs are not supported.
|
|
199
272
|
- The `width` and `height` are automatically extracted from the `src` object.
|
|
200
273
|
- When `priority={true}`, the image is preloaded using `react-dom`'s `preload` API and loaded with `loading="eager"` and `fetchPriority="high"`.
|
|
201
274
|
- When `sizes` is not provided, it's automatically calculated from `srcSet` breakpoints.
|
|
202
275
|
|
|
203
276
|
### ResponsiveImageData
|
|
204
277
|
|
|
205
|
-
The type returned from `?vite-image` query
|
|
278
|
+
The type returned from `?vite-image` query or `autoApply`:
|
|
206
279
|
|
|
207
280
|
```typescript
|
|
208
281
|
interface ResponsiveImageData {
|
|
@@ -225,10 +298,10 @@ import type { ImageProps, ResponsiveImageData } from "@son426/vite-image/react";
|
|
|
225
298
|
|
|
226
299
|
## How It Works
|
|
227
300
|
|
|
228
|
-
1.
|
|
301
|
+
1. **Image Processing**: When you import an image with `?vite-image` query or via `autoApply`, the plugin automatically generates:
|
|
229
302
|
|
|
230
|
-
- Responsive srcSet (640px, 1024px, 1920px widths)
|
|
231
|
-
- Image metadata (
|
|
303
|
+
- Responsive srcSet (default: 640px, 1024px, 1920px widths, customizable via `breakpoints`)
|
|
304
|
+
- Image metadata (largest breakpoint width)
|
|
232
305
|
- Blur placeholder (20px width, blurred, low quality, inline base64 as `blurDataURL`)
|
|
233
306
|
|
|
234
307
|
2. **Image Component**: The `<Image />` component handles:
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export { ImageProps
|
|
1
|
+
export { ImageProps } from './react/index.js';
|
|
2
|
+
export { A as AutoApplyConfig, R as ResponsiveImageData, V as ViteImageConfig } from './types-B08JIQxT.js';
|
|
2
3
|
export { ViteImagePluginOptions, viteImage } from './plugin/index.js';
|
|
3
4
|
import 'react/jsx-runtime';
|
|
4
5
|
import 'react';
|
|
5
|
-
import 'vite';
|
|
6
6
|
import 'vite-imagetools';
|
|
7
|
+
import 'vite';
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,64 @@ import 'react';
|
|
|
2
2
|
import 'react-dom';
|
|
3
3
|
import 'react/jsx-runtime';
|
|
4
4
|
import { imagetools } from 'vite-imagetools';
|
|
5
|
+
import { createFilter } from '@rollup/pluginutils';
|
|
5
6
|
|
|
6
7
|
// src/react/Image.tsx
|
|
7
|
-
|
|
8
|
+
var DEFAULT_BREAKPOINTS = [640, 1024, 1920];
|
|
9
|
+
function getFileExtension(id) {
|
|
10
|
+
const [basePath] = id.split("?");
|
|
11
|
+
const match = basePath.match(/\.([^.]+)$/);
|
|
12
|
+
return match ? `.${match[1]}` : null;
|
|
13
|
+
}
|
|
14
|
+
function matchesExtension(id, extensions) {
|
|
15
|
+
if (!extensions || extensions.length === 0) return false;
|
|
16
|
+
const ext = getFileExtension(id);
|
|
17
|
+
if (!ext) return false;
|
|
18
|
+
return extensions.includes(ext);
|
|
19
|
+
}
|
|
20
|
+
function generateSrcSetParams(breakpoints) {
|
|
21
|
+
return `w=${breakpoints.join(";")}&format=webp&as=srcset`;
|
|
22
|
+
}
|
|
23
|
+
function generateMetaParams(breakpoints) {
|
|
24
|
+
const maxWidth = Math.max(...breakpoints);
|
|
25
|
+
return `w=${maxWidth}&format=webp&as=meta`;
|
|
26
|
+
}
|
|
27
|
+
function generateImageCode(basePath, breakpoints) {
|
|
28
|
+
const srcSetParams = generateSrcSetParams(breakpoints);
|
|
29
|
+
const metaParams = generateMetaParams(breakpoints);
|
|
30
|
+
const lqipParams = "w=20&blur=2&quality=20&format=webp&inline";
|
|
31
|
+
return `
|
|
32
|
+
import meta from "${basePath}?${metaParams}";
|
|
33
|
+
import srcSet from "${basePath}?${srcSetParams}";
|
|
34
|
+
import blurDataURL from "${basePath}?${lqipParams}";
|
|
35
|
+
|
|
36
|
+
export default {
|
|
37
|
+
src: meta.src,
|
|
38
|
+
width: meta.width,
|
|
39
|
+
height: meta.height,
|
|
40
|
+
srcSet: srcSet,
|
|
41
|
+
blurDataURL: blurDataURL
|
|
42
|
+
};
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
45
|
+
function shouldAutoApply(id, autoApply, filter) {
|
|
46
|
+
if (!autoApply) return false;
|
|
47
|
+
if (!autoApply.extensions || autoApply.extensions.length === 0) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (!matchesExtension(id, autoApply.extensions)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (filter && !filter(id)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
function viteImage(config) {
|
|
59
|
+
const breakpoints = config?.breakpoints ?? DEFAULT_BREAKPOINTS;
|
|
60
|
+
const autoApply = config?.autoApply;
|
|
61
|
+
const imagetoolsOptions = config?.imagetools;
|
|
62
|
+
const filter = autoApply ? createFilter(autoApply.include, autoApply.exclude) : null;
|
|
8
63
|
const viteImageMacro = {
|
|
9
64
|
name: "vite-plugin-vite-image-macro",
|
|
10
65
|
enforce: "pre",
|
|
@@ -12,27 +67,15 @@ function viteImage(options) {
|
|
|
12
67
|
const [basePath, search] = id.split("?");
|
|
13
68
|
const params = new URLSearchParams(search);
|
|
14
69
|
if (params.has("vite-image")) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return
|
|
19
|
-
import srcSet from "${basePath}?${srcSetParams}";
|
|
20
|
-
import meta from "${basePath}?${metaParams}";
|
|
21
|
-
import blurDataURL from "${basePath}?${lqipParams}";
|
|
22
|
-
|
|
23
|
-
export default {
|
|
24
|
-
src: meta.src,
|
|
25
|
-
width: meta.width,
|
|
26
|
-
height: meta.height,
|
|
27
|
-
srcSet: srcSet,
|
|
28
|
-
blurDataURL: blurDataURL
|
|
29
|
-
};
|
|
30
|
-
`;
|
|
70
|
+
return generateImageCode(basePath, breakpoints);
|
|
71
|
+
}
|
|
72
|
+
if (shouldAutoApply(id, autoApply, filter)) {
|
|
73
|
+
return generateImageCode(basePath, breakpoints);
|
|
31
74
|
}
|
|
32
75
|
return null;
|
|
33
76
|
}
|
|
34
77
|
};
|
|
35
|
-
return [viteImageMacro, imagetools(
|
|
78
|
+
return [viteImageMacro, imagetools(imagetoolsOptions)];
|
|
36
79
|
}
|
|
37
80
|
|
|
38
81
|
export { viteImage };
|
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":"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","file":"index.js","sourcesContent":["import type { PluginOption } from \"vite\";\nimport { imagetools } from \"vite-imagetools\";\nimport { createFilter } from \"@rollup/pluginutils\";\nimport type { ViteImageConfig, AutoApplyConfig } from \"../types\";\n\nexport type ViteImagePluginOptions = Parameters<typeof imagetools>[0];\n\n// Re-export types for convenience\nexport type { ViteImageConfig, AutoApplyConfig } from \"../types\";\n\n// Default configuration\nconst DEFAULT_BREAKPOINTS = [640, 1024, 1920];\n\n// Utility functions\nfunction getFileExtension(id: string): string | null {\n // 쿼리 파라미터 제거\n const [basePath] = id.split(\"?\");\n\n // 확장자 추출\n const match = basePath.match(/\\.([^.]+)$/);\n return match ? `.${match[1]}` : null;\n}\n\nfunction matchesExtension(id: string, extensions: string[]): boolean {\n if (!extensions || extensions.length === 0) return false;\n\n const ext = getFileExtension(id);\n if (!ext) return false;\n\n return extensions.includes(ext);\n}\n\nfunction generateSrcSetParams(breakpoints: number[]): string {\n return `w=${breakpoints.join(\";\")}&format=webp&as=srcset`;\n}\n\nfunction generateMetaParams(breakpoints: number[]): string {\n const maxWidth = Math.max(...breakpoints);\n return `w=${maxWidth}&format=webp&as=meta`;\n}\n\nfunction generateImageCode(basePath: string, breakpoints: number[]): string {\n const srcSetParams = generateSrcSetParams(breakpoints);\n const metaParams = generateMetaParams(breakpoints);\n const lqipParams = \"w=20&blur=2&quality=20&format=webp&inline\";\n\n // meta를 먼저 import하고, 그 다음에 srcSet과 blurDataURL을 import\n // 이렇게 하면 초기화 순서 문제를 방지할 수 있음\n return `\n import meta from \"${basePath}?${metaParams}\";\n import srcSet from \"${basePath}?${srcSetParams}\";\n import blurDataURL from \"${basePath}?${lqipParams}\";\n \n export default {\n src: meta.src,\n width: meta.width,\n height: meta.height,\n srcSet: srcSet,\n blurDataURL: blurDataURL\n };\n `;\n}\n\nfunction shouldAutoApply(\n id: string,\n autoApply: AutoApplyConfig | undefined,\n filter: ((id: string) => boolean) | null\n): boolean {\n // autoApply 설정이 없으면 false\n if (!autoApply) return false;\n\n // extensions가 없거나 빈 배열이면 false\n if (!autoApply.extensions || autoApply.extensions.length === 0) {\n return false;\n }\n\n // 확장자 매칭\n if (!matchesExtension(id, autoApply.extensions)) {\n return false;\n }\n\n // glob 패턴 매칭 (include/exclude)\n if (filter && !filter(id)) {\n return false;\n }\n\n return true;\n}\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 config - Configuration options for vite-image plugin\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 * breakpoints: [640, 1024, 1920],\n * autoApply: {\n * extensions: ['.jpg', '.png'],\n * include: ['src/**'],\n * exclude: ['src/icons/**']\n * }\n * }),\n * ],\n * });\n * ```\n */\nexport function viteImage(config?: ViteImageConfig): PluginOption[] {\n // Config 병합\n const breakpoints = config?.breakpoints ?? DEFAULT_BREAKPOINTS;\n const autoApply = config?.autoApply;\n const imagetoolsOptions = config?.imagetools;\n\n // Glob 필터 생성 (autoApply가 있을 때만)\n const filter = autoApply\n ? createFilter(autoApply.include, autoApply.exclude)\n : null;\n\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 // 1. 명시적 쿼리 체크 (기존 로직)\n if (params.has(\"vite-image\")) {\n return generateImageCode(basePath, breakpoints);\n }\n\n // 2. autoApply 체크\n if (shouldAutoApply(id, autoApply, filter)) {\n // ?vite-image 쿼리를 자동으로 추가하여 처리\n return generateImageCode(basePath, breakpoints);\n }\n\n return null;\n },\n };\n\n return [viteImageMacro, imagetools(imagetoolsOptions)];\n}\n"]}
|
package/dist/plugin/index.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { PluginOption } from 'vite';
|
|
2
2
|
import { imagetools } from 'vite-imagetools';
|
|
3
|
+
import { V as ViteImageConfig } from '../types-B08JIQxT.js';
|
|
4
|
+
export { A as AutoApplyConfig } from '../types-B08JIQxT.js';
|
|
3
5
|
|
|
4
6
|
type ViteImagePluginOptions = Parameters<typeof imagetools>[0];
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Vite plugin for image optimization using vite-imagetools
|
|
7
10
|
* This plugin handles ?vite-image queries and uses imagetools for image processing
|
|
8
11
|
*
|
|
9
|
-
* @param
|
|
12
|
+
* @param config - Configuration options for vite-image plugin
|
|
10
13
|
* @returns Array of Vite plugins (vite-image macro and imagetools)
|
|
11
14
|
*
|
|
12
15
|
* @example
|
|
@@ -17,11 +20,18 @@ type ViteImagePluginOptions = Parameters<typeof imagetools>[0];
|
|
|
17
20
|
*
|
|
18
21
|
* export default defineConfig({
|
|
19
22
|
* plugins: [
|
|
20
|
-
* ...viteImage(
|
|
23
|
+
* ...viteImage({
|
|
24
|
+
* breakpoints: [640, 1024, 1920],
|
|
25
|
+
* autoApply: {
|
|
26
|
+
* extensions: ['.jpg', '.png'],
|
|
27
|
+
* include: ['src/**'],
|
|
28
|
+
* exclude: ['src/icons/**']
|
|
29
|
+
* }
|
|
30
|
+
* }),
|
|
21
31
|
* ],
|
|
22
32
|
* });
|
|
23
33
|
* ```
|
|
24
34
|
*/
|
|
25
|
-
declare function viteImage(
|
|
35
|
+
declare function viteImage(config?: ViteImageConfig): PluginOption[];
|
|
26
36
|
|
|
27
|
-
export { type ViteImagePluginOptions, viteImage };
|
|
37
|
+
export { ViteImageConfig, type ViteImagePluginOptions, viteImage };
|
package/dist/plugin/index.js
CHANGED
|
@@ -1,7 +1,62 @@
|
|
|
1
1
|
import { imagetools } from 'vite-imagetools';
|
|
2
|
+
import { createFilter } from '@rollup/pluginutils';
|
|
2
3
|
|
|
3
4
|
// src/plugin/index.ts
|
|
4
|
-
|
|
5
|
+
var DEFAULT_BREAKPOINTS = [640, 1024, 1920];
|
|
6
|
+
function getFileExtension(id) {
|
|
7
|
+
const [basePath] = id.split("?");
|
|
8
|
+
const match = basePath.match(/\.([^.]+)$/);
|
|
9
|
+
return match ? `.${match[1]}` : null;
|
|
10
|
+
}
|
|
11
|
+
function matchesExtension(id, extensions) {
|
|
12
|
+
if (!extensions || extensions.length === 0) return false;
|
|
13
|
+
const ext = getFileExtension(id);
|
|
14
|
+
if (!ext) return false;
|
|
15
|
+
return extensions.includes(ext);
|
|
16
|
+
}
|
|
17
|
+
function generateSrcSetParams(breakpoints) {
|
|
18
|
+
return `w=${breakpoints.join(";")}&format=webp&as=srcset`;
|
|
19
|
+
}
|
|
20
|
+
function generateMetaParams(breakpoints) {
|
|
21
|
+
const maxWidth = Math.max(...breakpoints);
|
|
22
|
+
return `w=${maxWidth}&format=webp&as=meta`;
|
|
23
|
+
}
|
|
24
|
+
function generateImageCode(basePath, breakpoints) {
|
|
25
|
+
const srcSetParams = generateSrcSetParams(breakpoints);
|
|
26
|
+
const metaParams = generateMetaParams(breakpoints);
|
|
27
|
+
const lqipParams = "w=20&blur=2&quality=20&format=webp&inline";
|
|
28
|
+
return `
|
|
29
|
+
import meta from "${basePath}?${metaParams}";
|
|
30
|
+
import srcSet from "${basePath}?${srcSetParams}";
|
|
31
|
+
import blurDataURL from "${basePath}?${lqipParams}";
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
src: meta.src,
|
|
35
|
+
width: meta.width,
|
|
36
|
+
height: meta.height,
|
|
37
|
+
srcSet: srcSet,
|
|
38
|
+
blurDataURL: blurDataURL
|
|
39
|
+
};
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
function shouldAutoApply(id, autoApply, filter) {
|
|
43
|
+
if (!autoApply) return false;
|
|
44
|
+
if (!autoApply.extensions || autoApply.extensions.length === 0) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (!matchesExtension(id, autoApply.extensions)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (filter && !filter(id)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
function viteImage(config) {
|
|
56
|
+
const breakpoints = config?.breakpoints ?? DEFAULT_BREAKPOINTS;
|
|
57
|
+
const autoApply = config?.autoApply;
|
|
58
|
+
const imagetoolsOptions = config?.imagetools;
|
|
59
|
+
const filter = autoApply ? createFilter(autoApply.include, autoApply.exclude) : null;
|
|
5
60
|
const viteImageMacro = {
|
|
6
61
|
name: "vite-plugin-vite-image-macro",
|
|
7
62
|
enforce: "pre",
|
|
@@ -9,27 +64,15 @@ function viteImage(options) {
|
|
|
9
64
|
const [basePath, search] = id.split("?");
|
|
10
65
|
const params = new URLSearchParams(search);
|
|
11
66
|
if (params.has("vite-image")) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return
|
|
16
|
-
import srcSet from "${basePath}?${srcSetParams}";
|
|
17
|
-
import meta from "${basePath}?${metaParams}";
|
|
18
|
-
import blurDataURL from "${basePath}?${lqipParams}";
|
|
19
|
-
|
|
20
|
-
export default {
|
|
21
|
-
src: meta.src,
|
|
22
|
-
width: meta.width,
|
|
23
|
-
height: meta.height,
|
|
24
|
-
srcSet: srcSet,
|
|
25
|
-
blurDataURL: blurDataURL
|
|
26
|
-
};
|
|
27
|
-
`;
|
|
67
|
+
return generateImageCode(basePath, breakpoints);
|
|
68
|
+
}
|
|
69
|
+
if (shouldAutoApply(id, autoApply, filter)) {
|
|
70
|
+
return generateImageCode(basePath, breakpoints);
|
|
28
71
|
}
|
|
29
72
|
return null;
|
|
30
73
|
}
|
|
31
74
|
};
|
|
32
|
-
return [viteImageMacro, imagetools(
|
|
75
|
+
return [viteImageMacro, imagetools(imagetoolsOptions)];
|
|
33
76
|
}
|
|
34
77
|
|
|
35
78
|
export { viteImage };
|
package/dist/plugin/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":"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","file":"index.js","sourcesContent":["import type { PluginOption } from \"vite\";\nimport { imagetools } from \"vite-imagetools\";\nimport { createFilter } from \"@rollup/pluginutils\";\nimport type { ViteImageConfig, AutoApplyConfig } from \"../types\";\n\nexport type ViteImagePluginOptions = Parameters<typeof imagetools>[0];\n\n// Re-export types for convenience\nexport type { ViteImageConfig, AutoApplyConfig } from \"../types\";\n\n// Default configuration\nconst DEFAULT_BREAKPOINTS = [640, 1024, 1920];\n\n// Utility functions\nfunction getFileExtension(id: string): string | null {\n // 쿼리 파라미터 제거\n const [basePath] = id.split(\"?\");\n\n // 확장자 추출\n const match = basePath.match(/\\.([^.]+)$/);\n return match ? `.${match[1]}` : null;\n}\n\nfunction matchesExtension(id: string, extensions: string[]): boolean {\n if (!extensions || extensions.length === 0) return false;\n\n const ext = getFileExtension(id);\n if (!ext) return false;\n\n return extensions.includes(ext);\n}\n\nfunction generateSrcSetParams(breakpoints: number[]): string {\n return `w=${breakpoints.join(\";\")}&format=webp&as=srcset`;\n}\n\nfunction generateMetaParams(breakpoints: number[]): string {\n const maxWidth = Math.max(...breakpoints);\n return `w=${maxWidth}&format=webp&as=meta`;\n}\n\nfunction generateImageCode(basePath: string, breakpoints: number[]): string {\n const srcSetParams = generateSrcSetParams(breakpoints);\n const metaParams = generateMetaParams(breakpoints);\n const lqipParams = \"w=20&blur=2&quality=20&format=webp&inline\";\n\n // meta를 먼저 import하고, 그 다음에 srcSet과 blurDataURL을 import\n // 이렇게 하면 초기화 순서 문제를 방지할 수 있음\n return `\n import meta from \"${basePath}?${metaParams}\";\n import srcSet from \"${basePath}?${srcSetParams}\";\n import blurDataURL from \"${basePath}?${lqipParams}\";\n \n export default {\n src: meta.src,\n width: meta.width,\n height: meta.height,\n srcSet: srcSet,\n blurDataURL: blurDataURL\n };\n `;\n}\n\nfunction shouldAutoApply(\n id: string,\n autoApply: AutoApplyConfig | undefined,\n filter: ((id: string) => boolean) | null\n): boolean {\n // autoApply 설정이 없으면 false\n if (!autoApply) return false;\n\n // extensions가 없거나 빈 배열이면 false\n if (!autoApply.extensions || autoApply.extensions.length === 0) {\n return false;\n }\n\n // 확장자 매칭\n if (!matchesExtension(id, autoApply.extensions)) {\n return false;\n }\n\n // glob 패턴 매칭 (include/exclude)\n if (filter && !filter(id)) {\n return false;\n }\n\n return true;\n}\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 config - Configuration options for vite-image plugin\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 * breakpoints: [640, 1024, 1920],\n * autoApply: {\n * extensions: ['.jpg', '.png'],\n * include: ['src/**'],\n * exclude: ['src/icons/**']\n * }\n * }),\n * ],\n * });\n * ```\n */\nexport function viteImage(config?: ViteImageConfig): PluginOption[] {\n // Config 병합\n const breakpoints = config?.breakpoints ?? DEFAULT_BREAKPOINTS;\n const autoApply = config?.autoApply;\n const imagetoolsOptions = config?.imagetools;\n\n // Glob 필터 생성 (autoApply가 있을 때만)\n const filter = autoApply\n ? createFilter(autoApply.include, autoApply.exclude)\n : null;\n\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 // 1. 명시적 쿼리 체크 (기존 로직)\n if (params.has(\"vite-image\")) {\n return generateImageCode(basePath, breakpoints);\n }\n\n // 2. autoApply 체크\n if (shouldAutoApply(id, autoApply, filter)) {\n // ?vite-image 쿼리를 자동으로 추가하여 처리\n return generateImageCode(basePath, breakpoints);\n }\n\n return null;\n },\n };\n\n return [viteImageMacro, imagetools(imagetoolsOptions)];\n}\n"]}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ImgHTMLAttributes } from 'react';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Type definitions for vite-image
|
|
6
|
-
*/
|
|
7
|
-
interface ResponsiveImageData {
|
|
8
|
-
src: string;
|
|
9
|
-
width: number;
|
|
10
|
-
height: number;
|
|
11
|
-
srcSet?: string;
|
|
12
|
-
blurDataURL?: string;
|
|
13
|
-
}
|
|
3
|
+
import { R as ResponsiveImageData } from '../types-B08JIQxT.js';
|
|
4
|
+
import 'vite-imagetools';
|
|
14
5
|
|
|
15
6
|
type PlaceholderValue = "empty" | "blur" | `data:image/${string}`;
|
|
16
7
|
interface BaseImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "srcSet" | "width" | "height"> {
|
|
@@ -18,6 +9,7 @@ interface BaseImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, "src"
|
|
|
18
9
|
sizes?: string;
|
|
19
10
|
placeholder?: PlaceholderValue;
|
|
20
11
|
blurDataURL?: string;
|
|
12
|
+
loading?: "lazy" | "eager";
|
|
21
13
|
priority?: boolean;
|
|
22
14
|
onLoad?: (event: React.SyntheticEvent<HTMLImageElement, Event>) => void;
|
|
23
15
|
onError?: (event: React.SyntheticEvent<HTMLImageElement, Event>) => void;
|
|
@@ -32,7 +24,8 @@ type ImageProps = FillImageProps | StandardImageProps;
|
|
|
32
24
|
declare function Image({ src, // 이제 이 src는 객체입니다.
|
|
33
25
|
fill, sizes, placeholder, // 기본값: empty (Next.js Image 호환)
|
|
34
26
|
blurDataURL: customBlurDataURL, // 사용자가 직접 제공한 blurDataURL (우선순위 높음)
|
|
27
|
+
loading, // loading prop (priority보다 낮은 우선순위)
|
|
35
28
|
priority, // 기본값: false (Next.js Image 호환)
|
|
36
29
|
className, style, onLoad, onError, ...props }: ImageProps): react_jsx_runtime.JSX.Element;
|
|
37
30
|
|
|
38
|
-
export { type ImageProps,
|
|
31
|
+
export { type ImageProps, ResponsiveImageData, Image as default };
|
package/dist/react/index.js
CHANGED
|
@@ -35,6 +35,8 @@ function Image({
|
|
|
35
35
|
// 기본값: empty (Next.js Image 호환)
|
|
36
36
|
blurDataURL: customBlurDataURL,
|
|
37
37
|
// 사용자가 직접 제공한 blurDataURL (우선순위 높음)
|
|
38
|
+
loading,
|
|
39
|
+
// loading prop (priority보다 낮은 우선순위)
|
|
38
40
|
priority = false,
|
|
39
41
|
// 기본값: false (Next.js Image 호환)
|
|
40
42
|
className = "",
|
|
@@ -53,6 +55,7 @@ function Image({
|
|
|
53
55
|
height: currentHeight
|
|
54
56
|
} = src;
|
|
55
57
|
const blurDataURL = customBlurDataURL ?? srcBlurDataURL;
|
|
58
|
+
const loadingAttr = priority ? "eager" : loading ?? "lazy";
|
|
56
59
|
const computedSizes = sizes ?? (fill ? "100vw" : generateSizesFromSrcSet(currentSrcSet));
|
|
57
60
|
if (priority && currentSrc) {
|
|
58
61
|
preload(currentSrc, {
|
|
@@ -125,7 +128,7 @@ function Image({
|
|
|
125
128
|
sizes: computedSizes,
|
|
126
129
|
width: fill ? void 0 : currentWidth,
|
|
127
130
|
height: fill ? void 0 : currentHeight,
|
|
128
|
-
loading:
|
|
131
|
+
loading: loadingAttr,
|
|
129
132
|
fetchPriority: priority ? "high" : void 0,
|
|
130
133
|
onLoad: (e) => {
|
|
131
134
|
setIsImageLoaded(true);
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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\" | `data:image/${string}`;\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 blurDataURL?: string; // Optional: 커스텀 blur placeholder (src.blurDataURL보다 우선)\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 blurDataURL: customBlurDataURL, // 사용자가 직접 제공한 blurDataURL (우선순위 높음)\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 blurDataURL: srcBlurDataURL, // 번들러가 생성한 blurDataURL\n width: currentWidth,\n height: currentHeight,\n } = src;\n\n // blurDataURL 우선순위: prop으로 제공된 것 > src 객체의 것\n const blurDataURL = customBlurDataURL ?? srcBlurDataURL;\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 return blurDataURL;\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"]}
|
|
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\" | `data:image/${string}`;\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 blurDataURL?: string; // Optional: 커스텀 blur placeholder (src.blurDataURL보다 우선)\n loading?: \"lazy\" | \"eager\"; // Next.js 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 blurDataURL: customBlurDataURL, // 사용자가 직접 제공한 blurDataURL (우선순위 높음)\n loading, // loading prop (priority보다 낮은 우선순위)\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 blurDataURL: srcBlurDataURL, // 번들러가 생성한 blurDataURL\n width: currentWidth,\n height: currentHeight,\n } = src;\n\n // blurDataURL 우선순위: prop으로 제공된 것 > src 객체의 것\n const blurDataURL = customBlurDataURL ?? srcBlurDataURL;\n\n // 2. loading 속성 결정: 우선순위 priority > loading prop > 기본값('lazy')\n const loadingAttr = priority ? \"eager\" : loading ?? \"lazy\";\n\n // 3. sizes 자동 계산: 제공되지 않으면 srcSet 기반으로 자동 생성\n const computedSizes =\n sizes ?? (fill ? \"100vw\" : generateSizesFromSrcSet(currentSrcSet));\n\n // 4. Priority 처리: priority={true}일 때 preload\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 // 5. placeholder 처리 (Next.js Image 호환)\n const getPlaceholderSrc = (): string | undefined => {\n if (placeholder === \"empty\") {\n return undefined;\n }\n if (placeholder === \"blur\") {\n return blurDataURL;\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={loadingAttr}\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"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as vite_imagetools from 'vite-imagetools';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Type definitions for vite-image
|
|
5
|
+
*/
|
|
6
|
+
interface ResponsiveImageData {
|
|
7
|
+
src: string;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
srcSet?: string;
|
|
11
|
+
blurDataURL?: string;
|
|
12
|
+
}
|
|
13
|
+
interface AutoApplyConfig {
|
|
14
|
+
extensions?: string[];
|
|
15
|
+
include?: string[];
|
|
16
|
+
exclude?: string[];
|
|
17
|
+
}
|
|
18
|
+
type ViteImagePluginOptions = Parameters<typeof vite_imagetools.imagetools>[0];
|
|
19
|
+
interface ViteImageConfig {
|
|
20
|
+
breakpoints?: number[];
|
|
21
|
+
autoApply?: AutoApplyConfig;
|
|
22
|
+
imagetools?: ViteImagePluginOptions;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type { AutoApplyConfig as A, ResponsiveImageData as R, ViteImageConfig as V };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@son426/vite-image",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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",
|
|
@@ -45,9 +45,14 @@
|
|
|
45
45
|
"license": "MIT",
|
|
46
46
|
"repository": {
|
|
47
47
|
"type": "git",
|
|
48
|
-
"url": ""
|
|
48
|
+
"url": "https://github.com/son426/vite-image.git"
|
|
49
49
|
},
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/son426/vite-image/issues"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/son426/vite-image#readme",
|
|
50
54
|
"dependencies": {
|
|
55
|
+
"@rollup/pluginutils": "^5.3.0",
|
|
51
56
|
"vite-imagetools": "^9.0.2"
|
|
52
57
|
},
|
|
53
58
|
"peerDependencies": {
|
|
@@ -56,6 +61,7 @@
|
|
|
56
61
|
"vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
|
57
62
|
},
|
|
58
63
|
"devDependencies": {
|
|
64
|
+
"@rollup/pluginutils": "^5.3.0",
|
|
59
65
|
"@types/node": "^20.0.0",
|
|
60
66
|
"@types/react": "^18.0.0 || ^19.0.0",
|
|
61
67
|
"@types/react-dom": "^19.2.3",
|