@son426/vite-image 0.2.2 → 0.3.1

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/README.md CHANGED
@@ -62,7 +62,7 @@ export default defineConfig({
62
62
  plugins: [
63
63
  viteImage({
64
64
  autoApply: {
65
- extensions: [".jpg"],
65
+ extensions: [".jpg"], // Required — must include the leading dot
66
66
  },
67
67
  }),
68
68
  ],
@@ -152,12 +152,15 @@ viteImage({
152
152
 
153
153
  **Auto-apply without query string:**
154
154
 
155
+ > **`extensions` is required for autoApply.** Without it, autoApply silently does nothing.
156
+ > Values must include the leading dot and are case-sensitive (e.g., `".jpg"`, not `"jpg"` or `".JPG"`).
157
+
155
158
  ```typescript
156
159
  viteImage({
157
160
  autoApply: {
158
- extensions: [".jpg", ".png", ".webp"],
159
- include: ["src/assets/**"],
160
- exclude: ["src/icons/**"],
161
+ extensions: [".jpg", ".png", ".webp"], // Required — must include the leading dot
162
+ include: ["src/assets/**"], // Optional — glob pattern for file paths
163
+ exclude: ["src/icons/**"], // Optional — glob pattern to exclude
161
164
  },
162
165
  });
163
166
  ```
@@ -205,7 +208,7 @@ function MyComponent() {
205
208
  // vite.config.ts
206
209
  viteImage({
207
210
  autoApply: {
208
- extensions: [".jpg", ".png"],
211
+ extensions: [".jpg", ".png"], // Required — must include the leading dot
209
212
  include: ["src/assets/**"],
210
213
  },
211
214
  });
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AutoApplyConfig, R as ResponsiveImageData, V as ViteImageConfig } from './types-B08JIQxT.js';
1
+ export { A as AutoApplyConfig, R as ResponsiveImageData, V as ViteImageConfig } from './types-C729SuaB.js';
2
2
  export { ImageProps } from './react/index.js';
3
3
  export { ViteImagePluginOptions, viteImage } from './plugin/index.js';
4
4
  import 'vite-imagetools';
package/dist/index.js CHANGED
@@ -6,6 +6,12 @@ import { createFilter } from '@rollup/pluginutils';
6
6
 
7
7
  // src/react/Image.tsx
8
8
  var DEFAULT_BREAKPOINTS = [640, 1024, 1920];
9
+ function validateBreakpoints(breakpoints) {
10
+ if (!breakpoints || breakpoints.length === 0) {
11
+ return DEFAULT_BREAKPOINTS;
12
+ }
13
+ return breakpoints;
14
+ }
9
15
  function getFileExtension(id) {
10
16
  const [basePath] = id.split("?");
11
17
  const match = basePath.match(/\.([^.]+)$/);
@@ -56,20 +62,27 @@ function shouldAutoApply(id, autoApply, filter) {
56
62
  return true;
57
63
  }
58
64
  function viteImage(config) {
59
- const breakpoints = config?.breakpoints ?? DEFAULT_BREAKPOINTS;
65
+ const breakpoints = validateBreakpoints(config?.breakpoints ?? DEFAULT_BREAKPOINTS);
60
66
  const autoApply = config?.autoApply;
61
67
  const imagetoolsOptions = config?.imagetools;
68
+ if (autoApply && (!autoApply.extensions || autoApply.extensions.length === 0)) {
69
+ console.warn(
70
+ '[vite-image] autoApply is enabled but "extensions" is empty or missing. No images will be auto-processed. Example: autoApply: { extensions: [".jpg", ".png", ".webp"] }'
71
+ );
72
+ }
62
73
  const filter = autoApply ? createFilter(autoApply.include, autoApply.exclude) : null;
63
74
  const viteImageMacro = {
64
75
  name: "vite-plugin-vite-image-macro",
65
76
  enforce: "pre",
66
77
  async load(id) {
67
- const [basePath, search] = id.split("?");
78
+ const qIndex = id.indexOf("?");
79
+ const basePath = qIndex === -1 ? id : id.slice(0, qIndex);
80
+ const search = qIndex === -1 ? "" : id.slice(qIndex + 1);
68
81
  const params = new URLSearchParams(search);
69
82
  if (params.has("vite-image")) {
70
83
  return generateImageCode(basePath, breakpoints);
71
84
  }
72
- if (shouldAutoApply(id, autoApply, filter)) {
85
+ if (!search && shouldAutoApply(basePath, autoApply, filter)) {
73
86
  return generateImageCode(basePath, breakpoints);
74
87
  }
75
88
  return null;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// src/plugin/index.ts\n\nimport 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"]}
1
+ {"version":3,"sources":["../src/plugin/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// src/plugin/index.ts\n\nimport 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\nfunction validateBreakpoints(breakpoints: number[]): number[] {\n if (!breakpoints || breakpoints.length === 0) {\n return DEFAULT_BREAKPOINTS;\n }\n return breakpoints;\n}\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 = validateBreakpoints(config?.breakpoints ?? DEFAULT_BREAKPOINTS);\n const autoApply = config?.autoApply;\n const imagetoolsOptions = config?.imagetools;\n\n // autoApply 설정 검증: extensions 누락 시 경고\n if (autoApply && (!autoApply.extensions || autoApply.extensions.length === 0)) {\n console.warn(\n '[vite-image] autoApply is enabled but \"extensions\" is empty or missing. ' +\n \"No images will be auto-processed. \" +\n 'Example: autoApply: { extensions: [\".jpg\", \".png\", \".webp\"] }'\n );\n }\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 qIndex = id.indexOf(\"?\");\n const basePath = qIndex === -1 ? id : id.slice(0, qIndex);\n const search = qIndex === -1 ? \"\" : id.slice(qIndex + 1);\n const params = new URLSearchParams(search);\n\n // 1. 명시적 ?vite-image 쿼리: 항상 처리\n if (params.has(\"vite-image\")) {\n return generateImageCode(basePath, breakpoints);\n }\n\n // 2. autoApply: 쿼리스트링이 없는 bare import만 대상\n // 생성된 서브임포트는 항상 쿼리가 있으므로 구조적으로 무한 루프 불가능\n if (!search && shouldAutoApply(basePath, autoApply, filter)) {\n return generateImageCode(basePath, breakpoints);\n }\n\n return null;\n },\n };\n\n return [viteImageMacro, imagetools(imagetoolsOptions)];\n}\n"]}
@@ -1,7 +1,7 @@
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
+ import { V as ViteImageConfig } from '../types-C729SuaB.js';
4
+ export { A as AutoApplyConfig } from '../types-C729SuaB.js';
5
5
 
6
6
  type ViteImagePluginOptions = Parameters<typeof imagetools>[0];
7
7
 
@@ -3,6 +3,12 @@ import { createFilter } from '@rollup/pluginutils';
3
3
 
4
4
  // src/plugin/index.ts
5
5
  var DEFAULT_BREAKPOINTS = [640, 1024, 1920];
6
+ function validateBreakpoints(breakpoints) {
7
+ if (!breakpoints || breakpoints.length === 0) {
8
+ return DEFAULT_BREAKPOINTS;
9
+ }
10
+ return breakpoints;
11
+ }
6
12
  function getFileExtension(id) {
7
13
  const [basePath] = id.split("?");
8
14
  const match = basePath.match(/\.([^.]+)$/);
@@ -53,20 +59,27 @@ function shouldAutoApply(id, autoApply, filter) {
53
59
  return true;
54
60
  }
55
61
  function viteImage(config) {
56
- const breakpoints = config?.breakpoints ?? DEFAULT_BREAKPOINTS;
62
+ const breakpoints = validateBreakpoints(config?.breakpoints ?? DEFAULT_BREAKPOINTS);
57
63
  const autoApply = config?.autoApply;
58
64
  const imagetoolsOptions = config?.imagetools;
65
+ if (autoApply && (!autoApply.extensions || autoApply.extensions.length === 0)) {
66
+ console.warn(
67
+ '[vite-image] autoApply is enabled but "extensions" is empty or missing. No images will be auto-processed. Example: autoApply: { extensions: [".jpg", ".png", ".webp"] }'
68
+ );
69
+ }
59
70
  const filter = autoApply ? createFilter(autoApply.include, autoApply.exclude) : null;
60
71
  const viteImageMacro = {
61
72
  name: "vite-plugin-vite-image-macro",
62
73
  enforce: "pre",
63
74
  async load(id) {
64
- const [basePath, search] = id.split("?");
75
+ const qIndex = id.indexOf("?");
76
+ const basePath = qIndex === -1 ? id : id.slice(0, qIndex);
77
+ const search = qIndex === -1 ? "" : id.slice(qIndex + 1);
65
78
  const params = new URLSearchParams(search);
66
79
  if (params.has("vite-image")) {
67
80
  return generateImageCode(basePath, breakpoints);
68
81
  }
69
- if (shouldAutoApply(id, autoApply, filter)) {
82
+ if (!search && shouldAutoApply(basePath, autoApply, filter)) {
70
83
  return generateImageCode(basePath, breakpoints);
71
84
  }
72
85
  return null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugin/index.ts"],"names":[],"mappings":";;;;AAaA,IAAM,mBAAA,GAAsB,CAAC,GAAA,EAAK,IAAA,EAAM,IAAI,CAAA;AAG5C,SAAS,iBAAiB,EAAA,EAA2B;AAEnD,EAAA,MAAM,CAAC,QAAQ,CAAA,GAAI,EAAA,CAAG,MAAM,GAAG,CAAA;AAG/B,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,KAAA,CAAM,YAAY,CAAA;AACzC,EAAA,OAAO,KAAA,GAAQ,CAAA,CAAA,EAAI,KAAA,CAAM,CAAC,CAAC,CAAA,CAAA,GAAK,IAAA;AAClC;AAEA,SAAS,gBAAA,CAAiB,IAAY,UAAA,EAA+B;AACnE,EAAA,IAAI,CAAC,UAAA,IAAc,UAAA,CAAW,MAAA,KAAW,GAAG,OAAO,KAAA;AAEnD,EAAA,MAAM,GAAA,GAAM,iBAAiB,EAAE,CAAA;AAC/B,EAAA,IAAI,CAAC,KAAK,OAAO,KAAA;AAEjB,EAAA,OAAO,UAAA,CAAW,SAAS,GAAG,CAAA;AAChC;AAEA,SAAS,qBAAqB,WAAA,EAA+B;AAC3D,EAAA,OAAO,CAAA,EAAA,EAAK,WAAA,CAAY,IAAA,CAAK,GAAG,CAAC,CAAA,sBAAA,CAAA;AACnC;AAEA,SAAS,mBAAmB,WAAA,EAA+B;AACzD,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,GAAG,WAAW,CAAA;AACxC,EAAA,OAAO,KAAK,QAAQ,CAAA,oBAAA,CAAA;AACtB;AAEA,SAAS,iBAAA,CAAkB,UAAkB,WAAA,EAA+B;AAC1E,EAAA,MAAM,YAAA,GAAe,qBAAqB,WAAW,CAAA;AACrD,EAAA,MAAM,UAAA,GAAa,mBAAmB,WAAW,CAAA;AACjD,EAAA,MAAM,UAAA,GAAa,2CAAA;AAInB,EAAA,OAAO;AAAA,sBAAA,EACe,QAAQ,IAAI,UAAU,CAAA;AAAA,wBAAA,EACpB,QAAQ,IAAI,YAAY,CAAA;AAAA,6BAAA,EACnB,QAAQ,IAAI,UAAU,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAA,CAAA;AAUrD;AAEA,SAAS,eAAA,CACP,EAAA,EACA,SAAA,EACA,MAAA,EACS;AAET,EAAA,IAAI,CAAC,WAAW,OAAO,KAAA;AAGvB,EAAA,IAAI,CAAC,SAAA,CAAU,UAAA,IAAc,SAAA,CAAU,UAAA,CAAW,WAAW,CAAA,EAAG;AAC9D,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,IAAI,CAAC,gBAAA,CAAiB,EAAA,EAAI,SAAA,CAAU,UAAU,CAAA,EAAG;AAC/C,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,IAAI,MAAA,IAAU,CAAC,MAAA,CAAO,EAAE,CAAA,EAAG;AACzB,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,IAAA;AACT;AA6BO,SAAS,UAAU,MAAA,EAA0C;AAElE,EAAA,MAAM,WAAA,GAAc,QAAQ,WAAA,IAAe,mBAAA;AAC3C,EAAA,MAAM,YAAY,MAAA,EAAQ,SAAA;AAC1B,EAAA,MAAM,oBAAoB,MAAA,EAAQ,UAAA;AAGlC,EAAA,MAAM,SAAS,SAAA,GACX,YAAA,CAAa,UAAU,OAAA,EAAS,SAAA,CAAU,OAAO,CAAA,GACjD,IAAA;AAGJ,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;AAGzC,MAAA,IAAI,MAAA,CAAO,GAAA,CAAI,YAAY,CAAA,EAAG;AAC5B,QAAA,OAAO,iBAAA,CAAkB,UAAU,WAAW,CAAA;AAAA,MAChD;AAGA,MAAA,IAAI,eAAA,CAAgB,EAAA,EAAI,SAAA,EAAW,MAAM,CAAA,EAAG;AAE1C,QAAA,OAAO,iBAAA,CAAkB,UAAU,WAAW,CAAA;AAAA,MAChD;AAEA,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,GACF;AAEA,EAAA,OAAO,CAAC,cAAA,EAAgB,UAAA,CAAW,iBAAiB,CAAC,CAAA;AACvD","file":"index.js","sourcesContent":["// src/plugin/index.ts\n\nimport 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"]}
1
+ {"version":3,"sources":["../../src/plugin/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// src/plugin/index.ts\n\nimport 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\nfunction validateBreakpoints(breakpoints: number[]): number[] {\n if (!breakpoints || breakpoints.length === 0) {\n return DEFAULT_BREAKPOINTS;\n }\n return breakpoints;\n}\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 = validateBreakpoints(config?.breakpoints ?? DEFAULT_BREAKPOINTS);\n const autoApply = config?.autoApply;\n const imagetoolsOptions = config?.imagetools;\n\n // autoApply 설정 검증: extensions 누락 시 경고\n if (autoApply && (!autoApply.extensions || autoApply.extensions.length === 0)) {\n console.warn(\n '[vite-image] autoApply is enabled but \"extensions\" is empty or missing. ' +\n \"No images will be auto-processed. \" +\n 'Example: autoApply: { extensions: [\".jpg\", \".png\", \".webp\"] }'\n );\n }\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 qIndex = id.indexOf(\"?\");\n const basePath = qIndex === -1 ? id : id.slice(0, qIndex);\n const search = qIndex === -1 ? \"\" : id.slice(qIndex + 1);\n const params = new URLSearchParams(search);\n\n // 1. 명시적 ?vite-image 쿼리: 항상 처리\n if (params.has(\"vite-image\")) {\n return generateImageCode(basePath, breakpoints);\n }\n\n // 2. autoApply: 쿼리스트링이 없는 bare import만 대상\n // 생성된 서브임포트는 항상 쿼리가 있으므로 구조적으로 무한 루프 불가능\n if (!search && shouldAutoApply(basePath, autoApply, filter)) {\n return generateImageCode(basePath, breakpoints);\n }\n\n return null;\n },\n };\n\n return [viteImageMacro, imagetools(imagetoolsOptions)];\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ImgHTMLAttributes } from 'react';
3
- import { R as ResponsiveImageData } from '../types-B08JIQxT.js';
3
+ import { R as ResponsiveImageData } from '../types-C729SuaB.js';
4
4
  import 'vite-imagetools';
5
5
 
6
6
  type PlaceholderValue = "empty" | "blur" | `data:image/${string}`;
@@ -1,4 +1,4 @@
1
- import { useState } from 'react';
1
+ import { useState, useEffect } from 'react';
2
2
  import { preload } from 'react-dom';
3
3
  import { jsxs, jsx } from 'react/jsx-runtime';
4
4
 
@@ -50,6 +50,7 @@ function Image({
50
50
  ...props
51
51
  }) {
52
52
  const [isImageLoaded, setIsImageLoaded] = useState(false);
53
+ const [isPlaceholderRemoved, setIsPlaceholderRemoved] = useState(false);
53
54
  const {
54
55
  src: currentSrc,
55
56
  srcSet: currentSrcSet,
@@ -58,11 +59,17 @@ function Image({
58
59
  width: currentWidth,
59
60
  height: currentHeight
60
61
  } = src;
62
+ const [prevSrc, setPrevSrc] = useState(currentSrc);
63
+ if (prevSrc !== currentSrc) {
64
+ setPrevSrc(currentSrc);
65
+ setIsImageLoaded(false);
66
+ setIsPlaceholderRemoved(false);
67
+ }
61
68
  const blurDataURL = customBlurDataURL ?? srcBlurDataURL;
62
69
  const loadingAttr = priority ? "eager" : loading ?? "lazy";
63
70
  const finalSrc = overrideSrc ?? currentSrc;
64
71
  const computedSizes = sizes ?? (fill ? "100vw" : generateSizesFromSrcSet(currentSrcSet));
65
- if (priority && currentSrc) {
72
+ if (priority && currentSrc && typeof preload === "function") {
66
73
  preload(currentSrc, {
67
74
  as: "image",
68
75
  fetchPriority: "high",
@@ -87,6 +94,12 @@ function Image({
87
94
  };
88
95
  const placeholderSrc = getPlaceholderSrc();
89
96
  const hasShowPlaceholder = !!placeholderSrc;
97
+ useEffect(() => {
98
+ if (isImageLoaded && !isPlaceholderRemoved) {
99
+ const timer = setTimeout(() => setIsPlaceholderRemoved(true), 600);
100
+ return () => clearTimeout(timer);
101
+ }
102
+ }, [isImageLoaded, isPlaceholderRemoved]);
90
103
  const containerStyle = fill ? {
91
104
  position: "absolute",
92
105
  top: 0,
@@ -147,13 +160,18 @@ function Image({
147
160
  style: { ...imgStyle, zIndex: 0 }
148
161
  }
149
162
  ),
150
- !overrideSrc && hasShowPlaceholder && /* @__PURE__ */ jsx(
163
+ !overrideSrc && hasShowPlaceholder && !isPlaceholderRemoved && /* @__PURE__ */ jsx(
151
164
  "img",
152
165
  {
153
166
  src: placeholderSrc,
154
167
  alt: "",
155
168
  "aria-hidden": "true",
156
- style: placeholderStyle
169
+ style: placeholderStyle,
170
+ onTransitionEnd: (e) => {
171
+ if (e.propertyName === "opacity" && isImageLoaded) {
172
+ setIsPlaceholderRemoved(true);
173
+ }
174
+ }
157
175
  }
158
176
  )
159
177
  ] });
@@ -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 loading?: \"lazy\" | \"eager\"; // Next.js Image 호환: 이미지 로딩 방식\n priority?: boolean; // Next.js Image 호환: true일 경우 높은 우선순위로 preload\n decoding?: \"async\" | \"sync\" | \"auto\"; // Next.js Image 호환: 이미지 디코딩 방식\n overrideSrc?: string; // Next.js Image 호환: SEO를 위해 src 속성을 유지하면서 최적화된 이미지 사용\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 decoding = \"async\", // 기본값: async (Next.js Image 호환)\n overrideSrc, // Next.js Image 호환: SEO를 위해 src 속성을 유지하면서 최적화된 이미지 사용\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. overrideSrc 처리: SEO를 위해 src 속성을 유지하면서 최적화된 이미지 사용\n const finalSrc = overrideSrc ?? currentSrc;\n\n // 4. sizes 자동 계산: 제공되지 않으면 srcSet 기반으로 자동 생성\n const computedSizes =\n sizes ?? (fill ? \"100vw\" : generateSizesFromSrcSet(currentSrcSet));\n\n // 5. 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 // 6. placeholder 처리 (Next.js Image 호환) - overrideSrc가 있으면 placeholder 비활성화\n const getPlaceholderSrc = (): string | undefined => {\n // overrideSrc가 있으면 placeholder를 표시하지 않음\n if (overrideSrc) {\n return undefined;\n }\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 // 7. 컨테이너 스타일 계산 (기존 로직 유지)\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 // 8. 실제 이미지 스타일 (기존 로직 유지)\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 // 9. 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={finalSrc}\n srcSet={overrideSrc ? undefined : currentSrcSet}\n sizes={overrideSrc ? undefined : computedSizes}\n width={fill ? undefined : currentWidth}\n height={fill ? undefined : currentHeight}\n loading={loadingAttr}\n fetchPriority={priority ? \"high\" : undefined}\n decoding={decoding}\n onLoad={(e) => {\n setIsImageLoaded(true);\n onLoad?.(e);\n }}\n onError={onError}\n style={{ ...imgStyle, zIndex: 0 }}\n />\n\n {/* Placeholder 레이어 (overrideSrc가 없을 때만 표시) */}\n {!overrideSrc && 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, useEffect, 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 decoding?: \"async\" | \"sync\" | \"auto\"; // Next.js Image 호환: 이미지 디코딩 방식\n overrideSrc?: string; // Next.js Image 호환: SEO를 위해 src 속성을 유지하면서 최적화된 이미지 사용\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 decoding = \"async\", // 기본값: async (Next.js Image 호환)\n overrideSrc, // Next.js Image 호환: SEO를 위해 src 속성을 유지하면서 최적화된 이미지 사용\n className = \"\",\n style,\n onLoad,\n onError,\n ...props\n}: ImageProps) {\n const [isImageLoaded, setIsImageLoaded] = useState(false);\n const [isPlaceholderRemoved, setIsPlaceholderRemoved] = 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 // src 변경 시 상태 리셋 (이미지 스왑, 캐러셀 등)\n const [prevSrc, setPrevSrc] = useState(currentSrc);\n if (prevSrc !== currentSrc) {\n setPrevSrc(currentSrc);\n setIsImageLoaded(false);\n setIsPlaceholderRemoved(false);\n }\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. overrideSrc 처리: SEO를 위해 src 속성을 유지하면서 최적화된 이미지 사용\n const finalSrc = overrideSrc ?? currentSrc;\n\n // 4. sizes 자동 계산: 제공되지 않으면 srcSet 기반으로 자동 생성\n const computedSizes =\n sizes ?? (fill ? \"100vw\" : generateSizesFromSrcSet(currentSrcSet));\n\n // 5. Priority 처리: priority={true}일 때 preload\n // preload()는 렌더 중 호출하도록 설계된 API (paint 전에 fetch 시작)\n // React 18에서는 preload가 없으므로 가드 필요 (graceful degradation)\n if (priority && currentSrc && typeof preload === \"function\") {\n preload(currentSrc, {\n as: \"image\",\n fetchPriority: \"high\",\n ...(currentSrcSet ? { imageSrcSet: currentSrcSet } : {}),\n ...(computedSizes ? { imageSizes: computedSizes } : {}),\n });\n }\n\n // 6. placeholder 처리 (Next.js Image 호환) - overrideSrc가 있으면 placeholder 비활성화\n const getPlaceholderSrc = (): string | undefined => {\n // overrideSrc가 있으면 placeholder를 표시하지 않음\n if (overrideSrc) {\n return undefined;\n }\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 // Placeholder 제거: onTransitionEnd + fallback timer\n // onTransitionEnd가 발생하지 않는 케이스 대비 (캐시 히트, 백그라운드 탭, reduced-motion 등)\n useEffect(() => {\n if (isImageLoaded && !isPlaceholderRemoved) {\n const timer = setTimeout(() => setIsPlaceholderRemoved(true), 600);\n return () => clearTimeout(timer);\n }\n }, [isImageLoaded, isPlaceholderRemoved]);\n\n // 7. 컨테이너 스타일 계산 (기존 로직 유지)\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 // 8. 실제 이미지 스타일 (기존 로직 유지)\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 // 9. 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={finalSrc}\n srcSet={overrideSrc ? undefined : currentSrcSet}\n sizes={overrideSrc ? undefined : computedSizes}\n width={fill ? undefined : currentWidth}\n height={fill ? undefined : currentHeight}\n loading={loadingAttr}\n fetchPriority={priority ? \"high\" : undefined}\n decoding={decoding}\n onLoad={(e) => {\n setIsImageLoaded(true);\n onLoad?.(e);\n }}\n onError={onError}\n style={{ ...imgStyle, zIndex: 0 }}\n />\n\n {/* Placeholder 레이어: 트랜지션 완료 또는 fallback timer 후 DOM에서 제거 */}\n {!overrideSrc && hasShowPlaceholder && !isPlaceholderRemoved && (\n <img\n src={placeholderSrc}\n alt=\"\"\n aria-hidden=\"true\"\n style={placeholderStyle}\n onTransitionEnd={(e) => {\n if (e.propertyName === \"opacity\" && isImageLoaded) {\n setIsPlaceholderRemoved(true);\n }\n }}\n />\n )}\n </div>\n );\n}\n"]}
@@ -11,7 +11,8 @@ interface ResponsiveImageData {
11
11
  blurDataURL?: string;
12
12
  }
13
13
  interface AutoApplyConfig {
14
- extensions?: string[];
14
+ /** File extensions to process. Must include the leading dot (e.g., `[".jpg", ".png"]`). Case-sensitive. */
15
+ extensions: string[];
15
16
  include?: string[];
16
17
  exclude?: string[];
17
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@son426/vite-image",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
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",
@@ -29,11 +29,6 @@
29
29
  "README.md",
30
30
  "LICENSE"
31
31
  ],
32
- "scripts": {
33
- "build": "tsup",
34
- "dev": "tsup --watch",
35
- "prepublishOnly": "pnpm run build"
36
- },
37
32
  "keywords": [
38
33
  "vite",
39
34
  "vite-plugin",
@@ -64,7 +59,6 @@
64
59
  "vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
65
60
  },
66
61
  "devDependencies": {
67
- "@rollup/pluginutils": "^5.3.0",
68
62
  "@types/node": "^20.0.0",
69
63
  "@types/react": "^18.0.0 || ^19.0.0",
70
64
  "@types/react-dom": "^19.2.3",
@@ -76,5 +70,9 @@
76
70
  },
77
71
  "publishConfig": {
78
72
  "access": "public"
73
+ },
74
+ "scripts": {
75
+ "build": "tsup",
76
+ "dev": "tsup --watch"
79
77
  }
80
- }
78
+ }