@flightdev/image 0.0.2
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 +21 -0
- package/README.md +219 -0
- package/dist/adapters/cloudinary.d.ts +46 -0
- package/dist/adapters/cloudinary.js +160 -0
- package/dist/adapters/cloudinary.js.map +1 -0
- package/dist/adapters/imgix.d.ts +39 -0
- package/dist/adapters/imgix.js +152 -0
- package/dist/adapters/imgix.js.map +1 -0
- package/dist/adapters/sharp.d.ts +37 -0
- package/dist/adapters/sharp.js +165 -0
- package/dist/adapters/sharp.js.map +1 -0
- package/dist/adapters/squoosh.d.ts +36 -0
- package/dist/adapters/squoosh.js +141 -0
- package/dist/adapters/squoosh.js.map +1 -0
- package/dist/components/react.d.ts +66 -0
- package/dist/components/react.js +129 -0
- package/dist/components/react.js.map +1 -0
- package/dist/components/solid.d.ts +44 -0
- package/dist/components/solid.js +101 -0
- package/dist/components/solid.js.map +1 -0
- package/dist/components/svelte.d.ts +70 -0
- package/dist/components/svelte.js +89 -0
- package/dist/components/svelte.js.map +1 -0
- package/dist/components/vue.d.ts +133 -0
- package/dist/components/vue.js +109 -0
- package/dist/components/vue.js.map +1 -0
- package/dist/index.d.ts +210 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/package.json +97 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { defineComponent, ref, computed, h } from 'vue';
|
|
2
|
+
|
|
3
|
+
// src/components/vue.ts
|
|
4
|
+
function buildOptimizedUrl(src, options, baseUrl = "") {
|
|
5
|
+
const params = new URLSearchParams();
|
|
6
|
+
if (options.width) params.set("w", options.width.toString());
|
|
7
|
+
if (options.height) params.set("h", options.height.toString());
|
|
8
|
+
if (options.format && options.format !== "auto") params.set("f", options.format);
|
|
9
|
+
if (options.quality) params.set("q", options.quality.toString());
|
|
10
|
+
if (options.fit) params.set("fit", options.fit);
|
|
11
|
+
const queryString = params.toString();
|
|
12
|
+
const normalizedSrc = src.startsWith("/") ? src : `/${src}`;
|
|
13
|
+
return `${baseUrl}/_flight/image${normalizedSrc}${queryString ? "?" + queryString : ""}`;
|
|
14
|
+
}
|
|
15
|
+
var FlightImage = defineComponent({
|
|
16
|
+
name: "FlightImage",
|
|
17
|
+
props: {
|
|
18
|
+
src: { type: String, required: true },
|
|
19
|
+
alt: { type: String, required: true },
|
|
20
|
+
width: { type: Number, default: void 0 },
|
|
21
|
+
height: { type: Number, default: void 0 },
|
|
22
|
+
format: { type: String, default: "auto" },
|
|
23
|
+
quality: { type: Number, default: 80 },
|
|
24
|
+
fit: { type: String, default: "cover" },
|
|
25
|
+
priority: { type: Boolean, default: false },
|
|
26
|
+
placeholder: { type: String, default: "empty" },
|
|
27
|
+
blurDataUrl: { type: String, default: void 0 },
|
|
28
|
+
sizes: { type: String, default: void 0 },
|
|
29
|
+
baseUrl: { type: String, default: "" }
|
|
30
|
+
},
|
|
31
|
+
setup(props) {
|
|
32
|
+
const isLoaded = ref(false);
|
|
33
|
+
const error = ref(false);
|
|
34
|
+
const optimizedSrc = computed(
|
|
35
|
+
() => buildOptimizedUrl(props.src, {
|
|
36
|
+
width: props.width,
|
|
37
|
+
height: props.height,
|
|
38
|
+
format: props.format,
|
|
39
|
+
quality: props.quality,
|
|
40
|
+
fit: props.fit
|
|
41
|
+
}, props.baseUrl)
|
|
42
|
+
);
|
|
43
|
+
const srcSet = computed(() => {
|
|
44
|
+
if (!props.width) return void 0;
|
|
45
|
+
const widths = [props.width, props.width * 1.5, props.width * 2].map(Math.round);
|
|
46
|
+
return widths.map((w) => {
|
|
47
|
+
const h2 = props.height ? Math.round(props.height * w / props.width) : void 0;
|
|
48
|
+
return `${buildOptimizedUrl(props.src, { width: w, height: h2, format: props.format, quality: props.quality, fit: props.fit }, props.baseUrl)} ${w}w`;
|
|
49
|
+
}).join(", ");
|
|
50
|
+
});
|
|
51
|
+
const defaultSizes = computed(
|
|
52
|
+
() => props.width ? `(max-width: ${props.width}px) 100vw, ${props.width}px` : void 0
|
|
53
|
+
);
|
|
54
|
+
const handleLoad = () => {
|
|
55
|
+
isLoaded.value = true;
|
|
56
|
+
};
|
|
57
|
+
const handleError = () => {
|
|
58
|
+
error.value = true;
|
|
59
|
+
};
|
|
60
|
+
return () => {
|
|
61
|
+
const containerStyle = {
|
|
62
|
+
position: "relative",
|
|
63
|
+
overflow: "hidden",
|
|
64
|
+
width: props.width ? `${props.width}px` : void 0,
|
|
65
|
+
height: props.height ? `${props.height}px` : void 0
|
|
66
|
+
};
|
|
67
|
+
const imageStyle = {
|
|
68
|
+
width: "100%",
|
|
69
|
+
height: "100%",
|
|
70
|
+
objectFit: props.fit,
|
|
71
|
+
opacity: isLoaded.value ? 1 : 0,
|
|
72
|
+
transition: "opacity 0.3s ease-in-out"
|
|
73
|
+
};
|
|
74
|
+
const blurStyle = {
|
|
75
|
+
position: "absolute",
|
|
76
|
+
inset: 0,
|
|
77
|
+
backgroundImage: props.blurDataUrl ? `url(${props.blurDataUrl})` : void 0,
|
|
78
|
+
backgroundSize: "cover",
|
|
79
|
+
backgroundPosition: "center",
|
|
80
|
+
filter: "blur(20px)",
|
|
81
|
+
transform: "scale(1.1)"
|
|
82
|
+
};
|
|
83
|
+
return h("div", { style: containerStyle }, [
|
|
84
|
+
// Blur placeholder
|
|
85
|
+
props.placeholder === "blur" && props.blurDataUrl && !isLoaded.value ? h("div", { style: blurStyle, "aria-hidden": true }) : null,
|
|
86
|
+
// Image
|
|
87
|
+
h("img", {
|
|
88
|
+
src: optimizedSrc.value,
|
|
89
|
+
srcset: srcSet.value,
|
|
90
|
+
sizes: props.sizes ?? defaultSizes.value,
|
|
91
|
+
alt: props.alt,
|
|
92
|
+
width: props.width,
|
|
93
|
+
height: props.height,
|
|
94
|
+
loading: props.priority ? "eager" : "lazy",
|
|
95
|
+
decoding: props.priority ? "sync" : "async",
|
|
96
|
+
fetchpriority: props.priority ? "high" : void 0,
|
|
97
|
+
onLoad: handleLoad,
|
|
98
|
+
onError: handleError,
|
|
99
|
+
style: imageStyle
|
|
100
|
+
})
|
|
101
|
+
]);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
var vue_default = FlightImage;
|
|
106
|
+
|
|
107
|
+
export { FlightImage, vue_default as default };
|
|
108
|
+
//# sourceMappingURL=vue.js.map
|
|
109
|
+
//# sourceMappingURL=vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/vue.ts"],"names":["h"],"mappings":"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","file":"vue.js","sourcesContent":["/**\r\n * Vue Image Component for @flightdev/image\r\n * \r\n * @example\r\n * ```vue\r\n * <script setup>\r\n * import { FlightImage } from '@flightdev/image/vue';\r\n * </script>\r\n * \r\n * <template>\r\n * <FlightImage\r\n * src=\"/hero.jpg\"\r\n * alt=\"Hero\"\r\n * :width=\"800\"\r\n * :height=\"600\"\r\n * priority\r\n * placeholder=\"blur\"\r\n * />\r\n * </template>\r\n * ```\r\n */\r\n\r\nimport { defineComponent, ref, computed, h, type PropType } from 'vue';\r\nimport type { ImageFormat, ImageFit } from '../index.js';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport interface FlightImageProps {\r\n src: string;\r\n alt: string;\r\n width?: number;\r\n height?: number;\r\n format?: ImageFormat;\r\n quality?: number;\r\n fit?: ImageFit;\r\n priority?: boolean;\r\n placeholder?: 'blur' | 'empty' | 'none';\r\n blurDataUrl?: string;\r\n sizes?: string;\r\n baseUrl?: string;\r\n}\r\n\r\n// ============================================================================\r\n// URL Builder\r\n// ============================================================================\r\n\r\nfunction buildOptimizedUrl(\r\n src: string,\r\n options: { width?: number; height?: number; format?: ImageFormat; quality?: number; fit?: ImageFit },\r\n baseUrl: string = ''\r\n): string {\r\n const params = new URLSearchParams();\r\n\r\n if (options.width) params.set('w', options.width.toString());\r\n if (options.height) params.set('h', options.height.toString());\r\n if (options.format && options.format !== 'auto') params.set('f', options.format);\r\n if (options.quality) params.set('q', options.quality.toString());\r\n if (options.fit) params.set('fit', options.fit);\r\n\r\n const queryString = params.toString();\r\n const normalizedSrc = src.startsWith('/') ? src : `/${src}`;\r\n return `${baseUrl}/_flight/image${normalizedSrc}${queryString ? '?' + queryString : ''}`;\r\n}\r\n\r\n// ============================================================================\r\n// Vue Component\r\n// ============================================================================\r\n\r\nexport const FlightImage = defineComponent({\r\n name: 'FlightImage',\r\n\r\n props: {\r\n src: { type: String, required: true },\r\n alt: { type: String, required: true },\r\n width: { type: Number, default: undefined },\r\n height: { type: Number, default: undefined },\r\n format: { type: String as PropType<ImageFormat>, default: 'auto' },\r\n quality: { type: Number, default: 80 },\r\n fit: { type: String as PropType<ImageFit>, default: 'cover' },\r\n priority: { type: Boolean, default: false },\r\n placeholder: { type: String as PropType<'blur' | 'empty' | 'none'>, default: 'empty' },\r\n blurDataUrl: { type: String, default: undefined },\r\n sizes: { type: String, default: undefined },\r\n baseUrl: { type: String, default: '' },\r\n },\r\n\r\n setup(props) {\r\n const isLoaded = ref(false);\r\n const error = ref(false);\r\n\r\n const optimizedSrc = computed(() =>\r\n buildOptimizedUrl(props.src, {\r\n width: props.width,\r\n height: props.height,\r\n format: props.format,\r\n quality: props.quality,\r\n fit: props.fit,\r\n }, props.baseUrl)\r\n );\r\n\r\n const srcSet = computed(() => {\r\n if (!props.width) return undefined;\r\n\r\n const widths = [props.width, props.width * 1.5, props.width * 2].map(Math.round);\r\n return widths\r\n .map(w => {\r\n const h = props.height ? Math.round(props.height * w / props.width!) : undefined;\r\n return `${buildOptimizedUrl(props.src, { width: w, height: h, format: props.format, quality: props.quality, fit: props.fit }, props.baseUrl)} ${w}w`;\r\n })\r\n .join(', ');\r\n });\r\n\r\n const defaultSizes = computed(() =>\r\n props.width ? `(max-width: ${props.width}px) 100vw, ${props.width}px` : undefined\r\n );\r\n\r\n const handleLoad = () => {\r\n isLoaded.value = true;\r\n };\r\n\r\n const handleError = () => {\r\n error.value = true;\r\n };\r\n\r\n return () => {\r\n const containerStyle = {\r\n position: 'relative' as const,\r\n overflow: 'hidden' as const,\r\n width: props.width ? `${props.width}px` : undefined,\r\n height: props.height ? `${props.height}px` : undefined,\r\n };\r\n\r\n const imageStyle = {\r\n width: '100%',\r\n height: '100%',\r\n objectFit: props.fit,\r\n opacity: isLoaded.value ? 1 : 0,\r\n transition: 'opacity 0.3s ease-in-out',\r\n };\r\n\r\n const blurStyle = {\r\n position: 'absolute' as const,\r\n inset: 0,\r\n backgroundImage: props.blurDataUrl ? `url(${props.blurDataUrl})` : undefined,\r\n backgroundSize: 'cover',\r\n backgroundPosition: 'center',\r\n filter: 'blur(20px)',\r\n transform: 'scale(1.1)',\r\n };\r\n\r\n return h('div', { style: containerStyle }, [\r\n // Blur placeholder\r\n props.placeholder === 'blur' && props.blurDataUrl && !isLoaded.value\r\n ? h('div', { style: blurStyle, 'aria-hidden': true })\r\n : null,\r\n\r\n // Image\r\n h('img', {\r\n src: optimizedSrc.value,\r\n srcset: srcSet.value,\r\n sizes: props.sizes ?? defaultSizes.value,\r\n alt: props.alt,\r\n width: props.width,\r\n height: props.height,\r\n loading: props.priority ? 'eager' : 'lazy',\r\n decoding: props.priority ? 'sync' : 'async',\r\n fetchpriority: props.priority ? 'high' : undefined,\r\n onLoad: handleLoad,\r\n onError: handleError,\r\n style: imageStyle,\r\n }),\r\n ]);\r\n };\r\n },\r\n});\r\n\r\nexport default FlightImage;\r\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
export { Buffer } from 'node:buffer';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @flightdev/image - Agnostic Image Optimization
|
|
5
|
+
*
|
|
6
|
+
* Flight provides the interface, you choose the processor.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { createImage } from '@flightdev/image';
|
|
11
|
+
* import { sharp } from '@flightdev/image/sharp';
|
|
12
|
+
*
|
|
13
|
+
* const image = createImage(sharp());
|
|
14
|
+
* const optimized = await image.optimize('./input.jpg', { width: 800, format: 'webp' });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
/** Supported image formats */
|
|
18
|
+
type ImageFormat = 'jpeg' | 'png' | 'webp' | 'avif' | 'gif' | 'tiff' | 'auto';
|
|
19
|
+
/** Image fit modes for resizing */
|
|
20
|
+
type ImageFit = 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
|
|
21
|
+
/** Image position for cropping */
|
|
22
|
+
type ImagePosition = 'center' | 'top' | 'right' | 'bottom' | 'left' | 'top left' | 'top right' | 'bottom left' | 'bottom right' | 'entropy' | 'attention';
|
|
23
|
+
/** Image optimization options */
|
|
24
|
+
interface ImageOptimizeOptions {
|
|
25
|
+
/** Target width in pixels */
|
|
26
|
+
width?: number;
|
|
27
|
+
/** Target height in pixels */
|
|
28
|
+
height?: number;
|
|
29
|
+
/** Output format ('auto' will choose WebP/AVIF based on browser support) */
|
|
30
|
+
format?: ImageFormat;
|
|
31
|
+
/** Quality (1-100) */
|
|
32
|
+
quality?: number;
|
|
33
|
+
/** Fit mode for resizing */
|
|
34
|
+
fit?: ImageFit;
|
|
35
|
+
/** Position for cropping when using cover/contain */
|
|
36
|
+
position?: ImagePosition;
|
|
37
|
+
/** Generate blur placeholder (returns base64 data URL) */
|
|
38
|
+
blur?: boolean | number;
|
|
39
|
+
/** Prevent upscaling smaller images */
|
|
40
|
+
withoutEnlargement?: boolean;
|
|
41
|
+
/** Preserve metadata (EXIF, etc.) */
|
|
42
|
+
preserveMetadata?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** Result of image optimization */
|
|
45
|
+
interface ImageOptimizeResult {
|
|
46
|
+
/** Optimized image as Buffer */
|
|
47
|
+
buffer: Buffer;
|
|
48
|
+
/** Detected/output format */
|
|
49
|
+
format: ImageFormat;
|
|
50
|
+
/** Final width */
|
|
51
|
+
width: number;
|
|
52
|
+
/** Final height */
|
|
53
|
+
height: number;
|
|
54
|
+
/** File size in bytes */
|
|
55
|
+
size: number;
|
|
56
|
+
/** Base64 blur placeholder (if blur option was set) */
|
|
57
|
+
blurDataUrl?: string;
|
|
58
|
+
}
|
|
59
|
+
/** Image metadata */
|
|
60
|
+
interface ImageMetadata {
|
|
61
|
+
/** Image width in pixels */
|
|
62
|
+
width: number;
|
|
63
|
+
/** Image height in pixels */
|
|
64
|
+
height: number;
|
|
65
|
+
/** Image format */
|
|
66
|
+
format: string;
|
|
67
|
+
/** File size in bytes */
|
|
68
|
+
size?: number;
|
|
69
|
+
/** Color space */
|
|
70
|
+
space?: string;
|
|
71
|
+
/** Has alpha channel */
|
|
72
|
+
hasAlpha?: boolean;
|
|
73
|
+
/** EXIF data */
|
|
74
|
+
exif?: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
/** Responsive image sizes configuration */
|
|
77
|
+
interface ResponsiveConfig {
|
|
78
|
+
/** Breakpoint widths to generate */
|
|
79
|
+
widths: number[];
|
|
80
|
+
/** Sizes attribute for HTML */
|
|
81
|
+
sizes?: string;
|
|
82
|
+
/** Base format */
|
|
83
|
+
format?: ImageFormat;
|
|
84
|
+
/** Additional formats to generate */
|
|
85
|
+
formats?: ImageFormat[];
|
|
86
|
+
}
|
|
87
|
+
/** Responsive image result */
|
|
88
|
+
interface ResponsiveResult {
|
|
89
|
+
/** Generated srcset string */
|
|
90
|
+
srcset: string;
|
|
91
|
+
/** Sizes attribute */
|
|
92
|
+
sizes: string;
|
|
93
|
+
/** All generated variants */
|
|
94
|
+
variants: Array<{
|
|
95
|
+
width: number;
|
|
96
|
+
format: ImageFormat;
|
|
97
|
+
url: string;
|
|
98
|
+
buffer: Buffer;
|
|
99
|
+
}>;
|
|
100
|
+
/** Blur placeholder */
|
|
101
|
+
blurDataUrl?: string;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Image Adapter Interface
|
|
105
|
+
*
|
|
106
|
+
* Implement this interface to create a custom image processor.
|
|
107
|
+
* Flight provides Sharp, Squoosh, Cloudinary, and Imgix adapters.
|
|
108
|
+
*/
|
|
109
|
+
interface ImageAdapter {
|
|
110
|
+
/** Adapter name for debugging */
|
|
111
|
+
readonly name: string;
|
|
112
|
+
/**
|
|
113
|
+
* Check if the adapter supports a specific format
|
|
114
|
+
*/
|
|
115
|
+
supportsFormat(format: ImageFormat): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Get image metadata without processing
|
|
118
|
+
*/
|
|
119
|
+
getMetadata(input: Buffer | string): Promise<ImageMetadata>;
|
|
120
|
+
/**
|
|
121
|
+
* Optimize an image with the given options
|
|
122
|
+
*/
|
|
123
|
+
optimize(input: Buffer | string, options: ImageOptimizeOptions): Promise<ImageOptimizeResult>;
|
|
124
|
+
/**
|
|
125
|
+
* Generate a blur placeholder (tiny base64 image)
|
|
126
|
+
*/
|
|
127
|
+
generateBlurPlaceholder(input: Buffer | string, size?: number): Promise<string>;
|
|
128
|
+
/**
|
|
129
|
+
* Generate responsive image variants
|
|
130
|
+
*/
|
|
131
|
+
generateResponsive(input: Buffer | string, config: ResponsiveConfig): Promise<ResponsiveResult>;
|
|
132
|
+
}
|
|
133
|
+
/** Image adapter factory function type */
|
|
134
|
+
type ImageAdapterFactory<TConfig = unknown> = (config?: TConfig) => ImageAdapter;
|
|
135
|
+
/**
|
|
136
|
+
* Image service options
|
|
137
|
+
*/
|
|
138
|
+
interface ImageServiceOptions {
|
|
139
|
+
/** Base URL for generated images (for URL generation) */
|
|
140
|
+
baseUrl?: string;
|
|
141
|
+
/** Default optimization options */
|
|
142
|
+
defaults?: Partial<ImageOptimizeOptions>;
|
|
143
|
+
/** Cache directory for processed images */
|
|
144
|
+
cacheDir?: string;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Image service interface
|
|
148
|
+
*/
|
|
149
|
+
interface ImageService {
|
|
150
|
+
/** The underlying adapter */
|
|
151
|
+
readonly adapter: ImageAdapter;
|
|
152
|
+
/** Get metadata for an image */
|
|
153
|
+
getMetadata(input: Buffer | string): Promise<ImageMetadata>;
|
|
154
|
+
/** Optimize an image */
|
|
155
|
+
optimize(input: Buffer | string, options?: ImageOptimizeOptions): Promise<ImageOptimizeResult>;
|
|
156
|
+
/** Generate blur placeholder */
|
|
157
|
+
blur(input: Buffer | string, size?: number): Promise<string>;
|
|
158
|
+
/** Generate responsive variants */
|
|
159
|
+
responsive(input: Buffer | string, config: ResponsiveConfig): Promise<ResponsiveResult>;
|
|
160
|
+
/** Generate a URL for optimized image (for CDN adapters) */
|
|
161
|
+
url(src: string, options?: ImageOptimizeOptions): string;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Create an image service with the given adapter
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```typescript
|
|
168
|
+
* import { createImage } from '@flightdev/image';
|
|
169
|
+
* import { sharp } from '@flightdev/image/sharp';
|
|
170
|
+
*
|
|
171
|
+
* const image = createImage(sharp());
|
|
172
|
+
*
|
|
173
|
+
* // Optimize an image
|
|
174
|
+
* const result = await image.optimize('./hero.jpg', {
|
|
175
|
+
* width: 800,
|
|
176
|
+
* format: 'webp',
|
|
177
|
+
* quality: 80,
|
|
178
|
+
* });
|
|
179
|
+
*
|
|
180
|
+
* // Get blur placeholder
|
|
181
|
+
* const blur = await image.blur('./hero.jpg');
|
|
182
|
+
*
|
|
183
|
+
* // Generate responsive variants
|
|
184
|
+
* const responsive = await image.responsive('./hero.jpg', {
|
|
185
|
+
* widths: [640, 768, 1024, 1280],
|
|
186
|
+
* formats: ['webp', 'avif'],
|
|
187
|
+
* });
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
declare function createImage(adapter: ImageAdapter, options?: ImageServiceOptions): ImageService;
|
|
191
|
+
/**
|
|
192
|
+
* Determine best format based on Accept header
|
|
193
|
+
*/
|
|
194
|
+
declare function getBestFormat(acceptHeader: string | null | undefined): ImageFormat;
|
|
195
|
+
/**
|
|
196
|
+
* Generate sizes attribute from breakpoints
|
|
197
|
+
*/
|
|
198
|
+
declare function generateSizes(breakpoints: Array<{
|
|
199
|
+
maxWidth: number;
|
|
200
|
+
size: string;
|
|
201
|
+
}>): string;
|
|
202
|
+
/**
|
|
203
|
+
* Parse dimensions from string (e.g., "800x600", "800", "x600")
|
|
204
|
+
*/
|
|
205
|
+
declare function parseDimensions(dimensions: string): {
|
|
206
|
+
width?: number;
|
|
207
|
+
height?: number;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export { type ImageAdapter, type ImageAdapterFactory, type ImageFit, type ImageFormat, type ImageMetadata, type ImageOptimizeOptions, type ImageOptimizeResult, type ImagePosition, type ImageService, type ImageServiceOptions, type ResponsiveConfig, type ResponsiveResult, createImage, generateSizes, getBestFormat, parseDimensions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
function createImage(adapter, options = {}) {
|
|
3
|
+
const { defaults = {}, baseUrl = "" } = options;
|
|
4
|
+
return {
|
|
5
|
+
adapter,
|
|
6
|
+
async getMetadata(input) {
|
|
7
|
+
return adapter.getMetadata(input);
|
|
8
|
+
},
|
|
9
|
+
async optimize(input, opts = {}) {
|
|
10
|
+
const mergedOptions = { ...defaults, ...opts };
|
|
11
|
+
return adapter.optimize(input, mergedOptions);
|
|
12
|
+
},
|
|
13
|
+
async blur(input, size = 10) {
|
|
14
|
+
return adapter.generateBlurPlaceholder(input, size);
|
|
15
|
+
},
|
|
16
|
+
async responsive(input, config) {
|
|
17
|
+
return adapter.generateResponsive(input, config);
|
|
18
|
+
},
|
|
19
|
+
url(src, opts = {}) {
|
|
20
|
+
const params = new URLSearchParams();
|
|
21
|
+
if (opts.width) params.set("w", opts.width.toString());
|
|
22
|
+
if (opts.height) params.set("h", opts.height.toString());
|
|
23
|
+
if (opts.format && opts.format !== "auto") params.set("f", opts.format);
|
|
24
|
+
if (opts.quality) params.set("q", opts.quality.toString());
|
|
25
|
+
if (opts.fit) params.set("fit", opts.fit);
|
|
26
|
+
const queryString = params.toString();
|
|
27
|
+
return `${baseUrl}${src}${queryString ? "?" + queryString : ""}`;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function getBestFormat(acceptHeader) {
|
|
32
|
+
if (!acceptHeader) return "webp";
|
|
33
|
+
if (acceptHeader.includes("image/avif")) return "avif";
|
|
34
|
+
if (acceptHeader.includes("image/webp")) return "webp";
|
|
35
|
+
return "jpeg";
|
|
36
|
+
}
|
|
37
|
+
function generateSizes(breakpoints) {
|
|
38
|
+
const parts = breakpoints.map((bp) => `(max-width: ${bp.maxWidth}px) ${bp.size}`);
|
|
39
|
+
parts.push("100vw");
|
|
40
|
+
return parts.join(", ");
|
|
41
|
+
}
|
|
42
|
+
function parseDimensions(dimensions) {
|
|
43
|
+
if (dimensions.includes("x")) {
|
|
44
|
+
const [w, h] = dimensions.split("x");
|
|
45
|
+
return {
|
|
46
|
+
width: w ? parseInt(w, 10) : void 0,
|
|
47
|
+
height: h ? parseInt(h, 10) : void 0
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return { width: parseInt(dimensions, 10) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { createImage, generateSizes, getBestFormat, parseDimensions };
|
|
54
|
+
//# sourceMappingURL=index.js.map
|
|
55
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * @flightdev/image - Agnostic Image Optimization\r\n * \r\n * Flight provides the interface, you choose the processor.\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createImage } from '@flightdev/image';\r\n * import { sharp } from '@flightdev/image/sharp';\r\n * \r\n * const image = createImage(sharp());\r\n * const optimized = await image.optimize('./input.jpg', { width: 800, format: 'webp' });\r\n * ```\r\n */\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\n/** Supported image formats */\r\nexport type ImageFormat = 'jpeg' | 'png' | 'webp' | 'avif' | 'gif' | 'tiff' | 'auto';\r\n\r\n/** Image fit modes for resizing */\r\nexport type ImageFit = 'cover' | 'contain' | 'fill' | 'inside' | 'outside';\r\n\r\n/** Image position for cropping */\r\nexport type ImagePosition =\r\n | 'center' | 'top' | 'right' | 'bottom' | 'left'\r\n | 'top left' | 'top right' | 'bottom left' | 'bottom right'\r\n | 'entropy' | 'attention';\r\n\r\n/** Image optimization options */\r\nexport interface ImageOptimizeOptions {\r\n /** Target width in pixels */\r\n width?: number;\r\n /** Target height in pixels */\r\n height?: number;\r\n /** Output format ('auto' will choose WebP/AVIF based on browser support) */\r\n format?: ImageFormat;\r\n /** Quality (1-100) */\r\n quality?: number;\r\n /** Fit mode for resizing */\r\n fit?: ImageFit;\r\n /** Position for cropping when using cover/contain */\r\n position?: ImagePosition;\r\n /** Generate blur placeholder (returns base64 data URL) */\r\n blur?: boolean | number;\r\n /** Prevent upscaling smaller images */\r\n withoutEnlargement?: boolean;\r\n /** Preserve metadata (EXIF, etc.) */\r\n preserveMetadata?: boolean;\r\n}\r\n\r\n/** Result of image optimization */\r\nexport interface ImageOptimizeResult {\r\n /** Optimized image as Buffer */\r\n buffer: Buffer;\r\n /** Detected/output format */\r\n format: ImageFormat;\r\n /** Final width */\r\n width: number;\r\n /** Final height */\r\n height: number;\r\n /** File size in bytes */\r\n size: number;\r\n /** Base64 blur placeholder (if blur option was set) */\r\n blurDataUrl?: string;\r\n}\r\n\r\n/** Image metadata */\r\nexport interface ImageMetadata {\r\n /** Image width in pixels */\r\n width: number;\r\n /** Image height in pixels */\r\n height: number;\r\n /** Image format */\r\n format: string;\r\n /** File size in bytes */\r\n size?: number;\r\n /** Color space */\r\n space?: string;\r\n /** Has alpha channel */\r\n hasAlpha?: boolean;\r\n /** EXIF data */\r\n exif?: Record<string, unknown>;\r\n}\r\n\r\n/** Responsive image sizes configuration */\r\nexport interface ResponsiveConfig {\r\n /** Breakpoint widths to generate */\r\n widths: number[];\r\n /** Sizes attribute for HTML */\r\n sizes?: string;\r\n /** Base format */\r\n format?: ImageFormat;\r\n /** Additional formats to generate */\r\n formats?: ImageFormat[];\r\n}\r\n\r\n/** Responsive image result */\r\nexport interface ResponsiveResult {\r\n /** Generated srcset string */\r\n srcset: string;\r\n /** Sizes attribute */\r\n sizes: string;\r\n /** All generated variants */\r\n variants: Array<{\r\n width: number;\r\n format: ImageFormat;\r\n url: string;\r\n buffer: Buffer;\r\n }>;\r\n /** Blur placeholder */\r\n blurDataUrl?: string;\r\n}\r\n\r\n// ============================================================================\r\n// Adapter Interface\r\n// ============================================================================\r\n\r\n/**\r\n * Image Adapter Interface\r\n * \r\n * Implement this interface to create a custom image processor.\r\n * Flight provides Sharp, Squoosh, Cloudinary, and Imgix adapters.\r\n */\r\nexport interface ImageAdapter {\r\n /** Adapter name for debugging */\r\n readonly name: string;\r\n\r\n /**\r\n * Check if the adapter supports a specific format\r\n */\r\n supportsFormat(format: ImageFormat): boolean;\r\n\r\n /**\r\n * Get image metadata without processing\r\n */\r\n getMetadata(input: Buffer | string): Promise<ImageMetadata>;\r\n\r\n /**\r\n * Optimize an image with the given options\r\n */\r\n optimize(input: Buffer | string, options: ImageOptimizeOptions): Promise<ImageOptimizeResult>;\r\n\r\n /**\r\n * Generate a blur placeholder (tiny base64 image)\r\n */\r\n generateBlurPlaceholder(input: Buffer | string, size?: number): Promise<string>;\r\n\r\n /**\r\n * Generate responsive image variants\r\n */\r\n generateResponsive(input: Buffer | string, config: ResponsiveConfig): Promise<ResponsiveResult>;\r\n}\r\n\r\n/** Image adapter factory function type */\r\nexport type ImageAdapterFactory<TConfig = unknown> = (config?: TConfig) => ImageAdapter;\r\n\r\n// ============================================================================\r\n// Image Service\r\n// ============================================================================\r\n\r\n/**\r\n * Image service options\r\n */\r\nexport interface ImageServiceOptions {\r\n /** Base URL for generated images (for URL generation) */\r\n baseUrl?: string;\r\n /** Default optimization options */\r\n defaults?: Partial<ImageOptimizeOptions>;\r\n /** Cache directory for processed images */\r\n cacheDir?: string;\r\n}\r\n\r\n/**\r\n * Image service interface\r\n */\r\nexport interface ImageService {\r\n /** The underlying adapter */\r\n readonly adapter: ImageAdapter;\r\n\r\n /** Get metadata for an image */\r\n getMetadata(input: Buffer | string): Promise<ImageMetadata>;\r\n\r\n /** Optimize an image */\r\n optimize(input: Buffer | string, options?: ImageOptimizeOptions): Promise<ImageOptimizeResult>;\r\n\r\n /** Generate blur placeholder */\r\n blur(input: Buffer | string, size?: number): Promise<string>;\r\n\r\n /** Generate responsive variants */\r\n responsive(input: Buffer | string, config: ResponsiveConfig): Promise<ResponsiveResult>;\r\n\r\n /** Generate a URL for optimized image (for CDN adapters) */\r\n url(src: string, options?: ImageOptimizeOptions): string;\r\n}\r\n\r\n/**\r\n * Create an image service with the given adapter\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createImage } from '@flightdev/image';\r\n * import { sharp } from '@flightdev/image/sharp';\r\n * \r\n * const image = createImage(sharp());\r\n * \r\n * // Optimize an image\r\n * const result = await image.optimize('./hero.jpg', {\r\n * width: 800,\r\n * format: 'webp',\r\n * quality: 80,\r\n * });\r\n * \r\n * // Get blur placeholder\r\n * const blur = await image.blur('./hero.jpg');\r\n * \r\n * // Generate responsive variants\r\n * const responsive = await image.responsive('./hero.jpg', {\r\n * widths: [640, 768, 1024, 1280],\r\n * formats: ['webp', 'avif'],\r\n * });\r\n * ```\r\n */\r\nexport function createImage(adapter: ImageAdapter, options: ImageServiceOptions = {}): ImageService {\r\n const { defaults = {}, baseUrl = '' } = options;\r\n\r\n return {\r\n adapter,\r\n\r\n async getMetadata(input) {\r\n return adapter.getMetadata(input);\r\n },\r\n\r\n async optimize(input, opts = {}) {\r\n const mergedOptions: ImageOptimizeOptions = { ...defaults, ...opts };\r\n return adapter.optimize(input, mergedOptions);\r\n },\r\n\r\n async blur(input, size = 10) {\r\n return adapter.generateBlurPlaceholder(input, size);\r\n },\r\n\r\n async responsive(input, config) {\r\n return adapter.generateResponsive(input, config);\r\n },\r\n\r\n url(src, opts = {}) {\r\n // For local adapters, generate a URL with query params\r\n // CDN adapters will override this with their own URL generation\r\n const params = new URLSearchParams();\r\n\r\n if (opts.width) params.set('w', opts.width.toString());\r\n if (opts.height) params.set('h', opts.height.toString());\r\n if (opts.format && opts.format !== 'auto') params.set('f', opts.format);\r\n if (opts.quality) params.set('q', opts.quality.toString());\r\n if (opts.fit) params.set('fit', opts.fit);\r\n\r\n const queryString = params.toString();\r\n return `${baseUrl}${src}${queryString ? '?' + queryString : ''}`;\r\n },\r\n };\r\n}\r\n\r\n// ============================================================================\r\n// Utilities\r\n// ============================================================================\r\n\r\n/**\r\n * Determine best format based on Accept header\r\n */\r\nexport function getBestFormat(acceptHeader: string | null | undefined): ImageFormat {\r\n if (!acceptHeader) return 'webp';\r\n\r\n // AVIF has best compression but slower encoding\r\n if (acceptHeader.includes('image/avif')) return 'avif';\r\n // WebP is widely supported and good compression\r\n if (acceptHeader.includes('image/webp')) return 'webp';\r\n // Fallback to JPEG\r\n return 'jpeg';\r\n}\r\n\r\n/**\r\n * Generate sizes attribute from breakpoints\r\n */\r\nexport function generateSizes(breakpoints: Array<{ maxWidth: number; size: string }>): string {\r\n const parts = breakpoints.map(bp => `(max-width: ${bp.maxWidth}px) ${bp.size}`);\r\n parts.push('100vw'); // Default\r\n return parts.join(', ');\r\n}\r\n\r\n/**\r\n * Parse dimensions from string (e.g., \"800x600\", \"800\", \"x600\")\r\n */\r\nexport function parseDimensions(dimensions: string): { width?: number; height?: number } {\r\n if (dimensions.includes('x')) {\r\n const [w, h] = dimensions.split('x');\r\n return {\r\n width: w ? parseInt(w, 10) : undefined,\r\n height: h ? parseInt(h, 10) : undefined,\r\n };\r\n }\r\n return { width: parseInt(dimensions, 10) };\r\n}\r\n\r\n// Re-export types\r\nexport type { Buffer } from 'node:buffer';\r\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flightdev/image",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Agnostic image optimization for Flight Framework. Choose your processor: Sharp, Squoosh, Cloudinary, or custom.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./sharp": {
|
|
12
|
+
"types": "./dist/adapters/sharp.d.ts",
|
|
13
|
+
"import": "./dist/adapters/sharp.js"
|
|
14
|
+
},
|
|
15
|
+
"./squoosh": {
|
|
16
|
+
"types": "./dist/adapters/squoosh.d.ts",
|
|
17
|
+
"import": "./dist/adapters/squoosh.js"
|
|
18
|
+
},
|
|
19
|
+
"./cloudinary": {
|
|
20
|
+
"types": "./dist/adapters/cloudinary.d.ts",
|
|
21
|
+
"import": "./dist/adapters/cloudinary.js"
|
|
22
|
+
},
|
|
23
|
+
"./imgix": {
|
|
24
|
+
"types": "./dist/adapters/imgix.d.ts",
|
|
25
|
+
"import": "./dist/adapters/imgix.js"
|
|
26
|
+
},
|
|
27
|
+
"./react": {
|
|
28
|
+
"types": "./dist/components/react.d.ts",
|
|
29
|
+
"import": "./dist/components/react.js"
|
|
30
|
+
},
|
|
31
|
+
"./vue": {
|
|
32
|
+
"types": "./dist/components/vue.d.ts",
|
|
33
|
+
"import": "./dist/components/vue.js"
|
|
34
|
+
},
|
|
35
|
+
"./svelte": {
|
|
36
|
+
"types": "./dist/components/svelte.d.ts",
|
|
37
|
+
"import": "./dist/components/svelte.js"
|
|
38
|
+
},
|
|
39
|
+
"./solid": {
|
|
40
|
+
"types": "./dist/components/solid.d.ts",
|
|
41
|
+
"import": "./dist/components/solid.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist"
|
|
46
|
+
],
|
|
47
|
+
"dependencies": {},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"sharp": ">=0.33.0",
|
|
50
|
+
"react": ">=18.0.0",
|
|
51
|
+
"vue": ">=3.0.0",
|
|
52
|
+
"svelte": ">=4.0.0",
|
|
53
|
+
"solid-js": ">=1.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"sharp": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"react": {
|
|
60
|
+
"optional": true
|
|
61
|
+
},
|
|
62
|
+
"vue": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"svelte": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"solid-js": {
|
|
69
|
+
"optional": true
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/node": "^22.0.0",
|
|
74
|
+
"tsup": "^8.0.0",
|
|
75
|
+
"typescript": "^5.7.0",
|
|
76
|
+
"vitest": "^2.0.0"
|
|
77
|
+
},
|
|
78
|
+
"keywords": [
|
|
79
|
+
"flight",
|
|
80
|
+
"image",
|
|
81
|
+
"optimization",
|
|
82
|
+
"sharp",
|
|
83
|
+
"webp",
|
|
84
|
+
"avif",
|
|
85
|
+
"responsive",
|
|
86
|
+
"agnostic"
|
|
87
|
+
],
|
|
88
|
+
"author": "Flight Contributors",
|
|
89
|
+
"license": "MIT",
|
|
90
|
+
"scripts": {
|
|
91
|
+
"build": "tsup",
|
|
92
|
+
"dev": "tsup --watch",
|
|
93
|
+
"test": "vitest run",
|
|
94
|
+
"test:watch": "vitest",
|
|
95
|
+
"typecheck": "tsc --noEmit"
|
|
96
|
+
}
|
|
97
|
+
}
|