smart-glide-react 1.0.0
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 +333 -0
- package/dist/index.d.mts +241 -0
- package/dist/index.d.ts +241 -0
- package/dist/index.js +436 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +421 -0
- package/dist/index.mjs.map +1 -0
- package/dist/next.d.mts +21 -0
- package/dist/next.d.ts +21 -0
- package/dist/next.js +21 -0
- package/dist/next.js.map +1 -0
- package/dist/next.mjs +16 -0
- package/dist/next.mjs.map +1 -0
- package/dist/server.d.mts +112 -0
- package/dist/server.d.ts +112 -0
- package/dist/server.js +83 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +79 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +81 -0
package/dist/next.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/next.ts"],"names":[],"mappings":";;;;;AAuBO,SAAS,oBAAA,CAAqB;AAAA,EACnC,GAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA,GAAU;AACZ,CAAA,EAAmC;AACjC,EAAA,MAAM,QACJ,OAAA,CAAQ,GAAA,CAAI,+BAA+B,EAAA,EAC3C,OAAA,CAAQ,OAAO,EAAE,CAAA;AAEnB,EAAA,MAAM,UACJ,OAAA,CAAQ,GAAA,CAAI,yCAAyC,MAAA,EACrD,OAAA,CAAQ,OAAO,EAAE,CAAA;AAEnB,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAEtC,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,MAAM,IAAI,QAAQ,CAAA,GAAA,EAAM,KAAK,CAAA,GAAA,EAAM,OAAO,CAAA,QAAA,CAAA;AAC9D;AAEA,IAAO,YAAA,GAAQ","file":"next.js","sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// smart-glide-react — Next.js entry point\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SmartGlideLoaderConfig {\n src: string;\n width: number;\n quality?: number;\n}\n\n/**\n * Smart Glide loader for `next/image`.\n *\n * Usage in `smart-glide-loader.ts` (project root):\n * ```ts\n * export { smartGlideNextLoader as default } from 'smart-glide-react/next';\n * ```\n *\n * Then in `next.config.ts`:\n * ```ts\n * images: { loader: 'custom', loaderFile: './smart-glide-loader.ts' }\n * ```\n */\nexport function smartGlideNextLoader({\n src,\n width,\n quality = 80,\n}: SmartGlideLoaderConfig): string {\n const base = (\n process.env.NEXT_PUBLIC_SMART_GLIDE_URL ?? ''\n ).replace(/\\/$/, '');\n\n const prefix = (\n process.env.NEXT_PUBLIC_SMART_GLIDE_DELIVERY_PATH ?? '/img'\n ).replace(/^\\//, '');\n\n const cleanSrc = src.replace(/^\\//, '');\n\n return `${base}/${prefix}/${cleanSrc}?w=${width}&q=${quality}&fm=webp`;\n}\n\nexport default smartGlideNextLoader;\n"]}
|
package/dist/next.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/next.ts
|
|
2
|
+
function smartGlideNextLoader({
|
|
3
|
+
src,
|
|
4
|
+
width,
|
|
5
|
+
quality = 80
|
|
6
|
+
}) {
|
|
7
|
+
const base = (process.env.NEXT_PUBLIC_SMART_GLIDE_URL ?? "").replace(/\/$/, "");
|
|
8
|
+
const prefix = (process.env.NEXT_PUBLIC_SMART_GLIDE_DELIVERY_PATH ?? "/img").replace(/^\//, "");
|
|
9
|
+
const cleanSrc = src.replace(/^\//, "");
|
|
10
|
+
return `${base}/${prefix}/${cleanSrc}?w=${width}&q=${quality}&fm=webp`;
|
|
11
|
+
}
|
|
12
|
+
var next_default = smartGlideNextLoader;
|
|
13
|
+
|
|
14
|
+
export { next_default as default, smartGlideNextLoader };
|
|
15
|
+
//# sourceMappingURL=next.mjs.map
|
|
16
|
+
//# sourceMappingURL=next.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/next.ts"],"names":[],"mappings":";AAuBO,SAAS,oBAAA,CAAqB;AAAA,EACnC,GAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA,GAAU;AACZ,CAAA,EAAmC;AACjC,EAAA,MAAM,QACJ,OAAA,CAAQ,GAAA,CAAI,+BAA+B,EAAA,EAC3C,OAAA,CAAQ,OAAO,EAAE,CAAA;AAEnB,EAAA,MAAM,UACJ,OAAA,CAAQ,GAAA,CAAI,yCAAyC,MAAA,EACrD,OAAA,CAAQ,OAAO,EAAE,CAAA;AAEnB,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAEtC,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,MAAM,IAAI,QAAQ,CAAA,GAAA,EAAM,KAAK,CAAA,GAAA,EAAM,OAAO,CAAA,QAAA,CAAA;AAC9D;AAEA,IAAO,YAAA,GAAQ","file":"next.mjs","sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// smart-glide-react — Next.js entry point\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SmartGlideLoaderConfig {\n src: string;\n width: number;\n quality?: number;\n}\n\n/**\n * Smart Glide loader for `next/image`.\n *\n * Usage in `smart-glide-loader.ts` (project root):\n * ```ts\n * export { smartGlideNextLoader as default } from 'smart-glide-react/next';\n * ```\n *\n * Then in `next.config.ts`:\n * ```ts\n * images: { loader: 'custom', loaderFile: './smart-glide-loader.ts' }\n * ```\n */\nexport function smartGlideNextLoader({\n src,\n width,\n quality = 80,\n}: SmartGlideLoaderConfig): string {\n const base = (\n process.env.NEXT_PUBLIC_SMART_GLIDE_URL ?? ''\n ).replace(/\\/$/, '');\n\n const prefix = (\n process.env.NEXT_PUBLIC_SMART_GLIDE_DELIVERY_PATH ?? '/img'\n ).replace(/^\\//, '');\n\n const cleanSrc = src.replace(/^\\//, '');\n\n return `${base}/${prefix}/${cleanSrc}?w=${width}&q=${quality}&fm=webp`;\n}\n\nexport default smartGlideNextLoader;\n"]}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** Named responsive presets available on the Laravel backend. */
|
|
2
|
+
type ResponsivePreset = 'hero' | 'thumbnails' | 'square' | 'portrait' | 'hd' | 'fhd' | 'retina' | (string & {});
|
|
3
|
+
/** Named image profiles available on the Laravel backend. */
|
|
4
|
+
type ImageProfile = 'default' | 'thumbnail' | 'hero' | 'portrait' | 'square' | 'profile_photo' | 'cover' | 'background' | (string & {});
|
|
5
|
+
/** Response shape returned by the `/img-data` API. */
|
|
6
|
+
interface SmartGlideData {
|
|
7
|
+
src: string;
|
|
8
|
+
srcset?: string | null;
|
|
9
|
+
sizes?: string | null;
|
|
10
|
+
widths?: number[];
|
|
11
|
+
blurDataUrl?: string | null;
|
|
12
|
+
schema?: Record<string, unknown> | null;
|
|
13
|
+
}
|
|
14
|
+
/** Options passed to `useSmartGlide` and lower-level helpers. */
|
|
15
|
+
interface SmartGlideOptions {
|
|
16
|
+
/** Relative path of the image (e.g. `"products/phone.jpg"`). */
|
|
17
|
+
path: string;
|
|
18
|
+
/**
|
|
19
|
+
* Base URL of your Laravel app (e.g. `"https://api.example.com"`).
|
|
20
|
+
* Defaults to the `NEXT_PUBLIC_SMART_GLIDE_URL` / `VITE_SMART_GLIDE_URL`
|
|
21
|
+
* environment variable when omitted.
|
|
22
|
+
*/
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
/** Named or custom compression profile (default: `"default"`). */
|
|
25
|
+
profile?: ImageProfile;
|
|
26
|
+
/**
|
|
27
|
+
* Responsive breakpoints:
|
|
28
|
+
* - `"hero"` — named preset
|
|
29
|
+
* - `[640, 960, 1280]` — custom widths
|
|
30
|
+
* - `false` — disable srcset
|
|
31
|
+
*/
|
|
32
|
+
responsive?: ResponsivePreset | number[] | false;
|
|
33
|
+
/** Fetch the blurred Base64 LQIP from the server. Enables `blurDataUrl`. */
|
|
34
|
+
blurPlaceholder?: boolean;
|
|
35
|
+
/** Request JSON-LD `ImageObject` data from the server. */
|
|
36
|
+
schema?: boolean;
|
|
37
|
+
/** Extra Glide transformation params (w, h, fit, focus, q…). */
|
|
38
|
+
params?: Record<string, string | number>;
|
|
39
|
+
/**
|
|
40
|
+
* Additional fetch options forwarded to the underlying `fetch()` call.
|
|
41
|
+
* (e.g. `{ next: { revalidate: 3600 } }` for Next.js ISR)
|
|
42
|
+
*/
|
|
43
|
+
fetchOptions?: RequestInit;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Fetch Smart Glide image data on the server.
|
|
48
|
+
* Use this inside Next.js Server Components or `generateStaticParams`.
|
|
49
|
+
*
|
|
50
|
+
* Supports Next.js ISR via `fetchOptions.next.revalidate`.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // app/products/[id]/page.tsx (Server Component)
|
|
54
|
+
* import { fetchSmartGlideData } from 'smart-glide-react/server';
|
|
55
|
+
*
|
|
56
|
+
* export default async function ProductPage({ params }) {
|
|
57
|
+
* const image = await fetchSmartGlideData({
|
|
58
|
+
* path: `products/${params.id}.jpg`,
|
|
59
|
+
* profile: 'hero',
|
|
60
|
+
* responsive: 'retina',
|
|
61
|
+
* blurPlaceholder: true,
|
|
62
|
+
* schema: true,
|
|
63
|
+
* baseUrl: process.env.SMART_GLIDE_URL, // server-side env (no NEXT_PUBLIC_ prefix)
|
|
64
|
+
* fetchOptions: { next: { revalidate: 3600 } }, // ISR: revalidate every hour
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* return (
|
|
68
|
+
* <img
|
|
69
|
+
* src={image.src}
|
|
70
|
+
* srcSet={image.srcset ?? undefined}
|
|
71
|
+
* sizes={image.sizes ?? undefined}
|
|
72
|
+
* alt="Product"
|
|
73
|
+
* {...(image.blurDataUrl
|
|
74
|
+
* ? { style: { backgroundImage: `url(${image.blurDataUrl})` } }
|
|
75
|
+
* : {})}
|
|
76
|
+
* />
|
|
77
|
+
* );
|
|
78
|
+
* }
|
|
79
|
+
*/
|
|
80
|
+
declare function fetchSmartGlideData(options: SmartGlideOptions): Promise<SmartGlideData>;
|
|
81
|
+
/**
|
|
82
|
+
* Fetch multiple images in parallel — useful for generating static params
|
|
83
|
+
* or pre-loading image data for a collection of items.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* const [hero, thumbnail, avatar] = await fetchSmartGlideMany([
|
|
87
|
+
* { path: 'home/hero.jpg', profile: 'hero', responsive: 'fhd' },
|
|
88
|
+
* { path: 'home/thumb.jpg', profile: 'thumbnail', responsive: 'thumbnails' },
|
|
89
|
+
* { path: 'avatars/user.jpg', profile: 'profile_photo' },
|
|
90
|
+
* ], { baseUrl: process.env.SMART_GLIDE_URL });
|
|
91
|
+
*/
|
|
92
|
+
declare function fetchSmartGlideMany(items: Omit<SmartGlideOptions, 'baseUrl'>[], shared: Pick<SmartGlideOptions, 'baseUrl' | 'fetchOptions'>): Promise<SmartGlideData[]>;
|
|
93
|
+
/**
|
|
94
|
+
* Fetch Smart Glide data and return a preload `<link>` hint header value.
|
|
95
|
+
* Use with `headers()` in Next.js route handlers or middleware.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* // app/api/page-data/route.ts
|
|
99
|
+
* import { headers } from 'next/headers';
|
|
100
|
+
* import { buildPreloadHeader } from 'smart-glide-react/server';
|
|
101
|
+
*
|
|
102
|
+
* export async function GET() {
|
|
103
|
+
* const preload = await buildPreloadHeader('home/hero.jpg', {
|
|
104
|
+
* baseUrl: process.env.SMART_GLIDE_URL,
|
|
105
|
+
* profile: 'hero',
|
|
106
|
+
* });
|
|
107
|
+
* return new Response(null, { headers: { Link: preload } });
|
|
108
|
+
* }
|
|
109
|
+
*/
|
|
110
|
+
declare function buildPreloadHeader(path: string, options: SmartGlideOptions): Promise<string>;
|
|
111
|
+
|
|
112
|
+
export { buildPreloadHeader, fetchSmartGlideData, fetchSmartGlideMany };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** Named responsive presets available on the Laravel backend. */
|
|
2
|
+
type ResponsivePreset = 'hero' | 'thumbnails' | 'square' | 'portrait' | 'hd' | 'fhd' | 'retina' | (string & {});
|
|
3
|
+
/** Named image profiles available on the Laravel backend. */
|
|
4
|
+
type ImageProfile = 'default' | 'thumbnail' | 'hero' | 'portrait' | 'square' | 'profile_photo' | 'cover' | 'background' | (string & {});
|
|
5
|
+
/** Response shape returned by the `/img-data` API. */
|
|
6
|
+
interface SmartGlideData {
|
|
7
|
+
src: string;
|
|
8
|
+
srcset?: string | null;
|
|
9
|
+
sizes?: string | null;
|
|
10
|
+
widths?: number[];
|
|
11
|
+
blurDataUrl?: string | null;
|
|
12
|
+
schema?: Record<string, unknown> | null;
|
|
13
|
+
}
|
|
14
|
+
/** Options passed to `useSmartGlide` and lower-level helpers. */
|
|
15
|
+
interface SmartGlideOptions {
|
|
16
|
+
/** Relative path of the image (e.g. `"products/phone.jpg"`). */
|
|
17
|
+
path: string;
|
|
18
|
+
/**
|
|
19
|
+
* Base URL of your Laravel app (e.g. `"https://api.example.com"`).
|
|
20
|
+
* Defaults to the `NEXT_PUBLIC_SMART_GLIDE_URL` / `VITE_SMART_GLIDE_URL`
|
|
21
|
+
* environment variable when omitted.
|
|
22
|
+
*/
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
/** Named or custom compression profile (default: `"default"`). */
|
|
25
|
+
profile?: ImageProfile;
|
|
26
|
+
/**
|
|
27
|
+
* Responsive breakpoints:
|
|
28
|
+
* - `"hero"` — named preset
|
|
29
|
+
* - `[640, 960, 1280]` — custom widths
|
|
30
|
+
* - `false` — disable srcset
|
|
31
|
+
*/
|
|
32
|
+
responsive?: ResponsivePreset | number[] | false;
|
|
33
|
+
/** Fetch the blurred Base64 LQIP from the server. Enables `blurDataUrl`. */
|
|
34
|
+
blurPlaceholder?: boolean;
|
|
35
|
+
/** Request JSON-LD `ImageObject` data from the server. */
|
|
36
|
+
schema?: boolean;
|
|
37
|
+
/** Extra Glide transformation params (w, h, fit, focus, q…). */
|
|
38
|
+
params?: Record<string, string | number>;
|
|
39
|
+
/**
|
|
40
|
+
* Additional fetch options forwarded to the underlying `fetch()` call.
|
|
41
|
+
* (e.g. `{ next: { revalidate: 3600 } }` for Next.js ISR)
|
|
42
|
+
*/
|
|
43
|
+
fetchOptions?: RequestInit;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Fetch Smart Glide image data on the server.
|
|
48
|
+
* Use this inside Next.js Server Components or `generateStaticParams`.
|
|
49
|
+
*
|
|
50
|
+
* Supports Next.js ISR via `fetchOptions.next.revalidate`.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // app/products/[id]/page.tsx (Server Component)
|
|
54
|
+
* import { fetchSmartGlideData } from 'smart-glide-react/server';
|
|
55
|
+
*
|
|
56
|
+
* export default async function ProductPage({ params }) {
|
|
57
|
+
* const image = await fetchSmartGlideData({
|
|
58
|
+
* path: `products/${params.id}.jpg`,
|
|
59
|
+
* profile: 'hero',
|
|
60
|
+
* responsive: 'retina',
|
|
61
|
+
* blurPlaceholder: true,
|
|
62
|
+
* schema: true,
|
|
63
|
+
* baseUrl: process.env.SMART_GLIDE_URL, // server-side env (no NEXT_PUBLIC_ prefix)
|
|
64
|
+
* fetchOptions: { next: { revalidate: 3600 } }, // ISR: revalidate every hour
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* return (
|
|
68
|
+
* <img
|
|
69
|
+
* src={image.src}
|
|
70
|
+
* srcSet={image.srcset ?? undefined}
|
|
71
|
+
* sizes={image.sizes ?? undefined}
|
|
72
|
+
* alt="Product"
|
|
73
|
+
* {...(image.blurDataUrl
|
|
74
|
+
* ? { style: { backgroundImage: `url(${image.blurDataUrl})` } }
|
|
75
|
+
* : {})}
|
|
76
|
+
* />
|
|
77
|
+
* );
|
|
78
|
+
* }
|
|
79
|
+
*/
|
|
80
|
+
declare function fetchSmartGlideData(options: SmartGlideOptions): Promise<SmartGlideData>;
|
|
81
|
+
/**
|
|
82
|
+
* Fetch multiple images in parallel — useful for generating static params
|
|
83
|
+
* or pre-loading image data for a collection of items.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* const [hero, thumbnail, avatar] = await fetchSmartGlideMany([
|
|
87
|
+
* { path: 'home/hero.jpg', profile: 'hero', responsive: 'fhd' },
|
|
88
|
+
* { path: 'home/thumb.jpg', profile: 'thumbnail', responsive: 'thumbnails' },
|
|
89
|
+
* { path: 'avatars/user.jpg', profile: 'profile_photo' },
|
|
90
|
+
* ], { baseUrl: process.env.SMART_GLIDE_URL });
|
|
91
|
+
*/
|
|
92
|
+
declare function fetchSmartGlideMany(items: Omit<SmartGlideOptions, 'baseUrl'>[], shared: Pick<SmartGlideOptions, 'baseUrl' | 'fetchOptions'>): Promise<SmartGlideData[]>;
|
|
93
|
+
/**
|
|
94
|
+
* Fetch Smart Glide data and return a preload `<link>` hint header value.
|
|
95
|
+
* Use with `headers()` in Next.js route handlers or middleware.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* // app/api/page-data/route.ts
|
|
99
|
+
* import { headers } from 'next/headers';
|
|
100
|
+
* import { buildPreloadHeader } from 'smart-glide-react/server';
|
|
101
|
+
*
|
|
102
|
+
* export async function GET() {
|
|
103
|
+
* const preload = await buildPreloadHeader('home/hero.jpg', {
|
|
104
|
+
* baseUrl: process.env.SMART_GLIDE_URL,
|
|
105
|
+
* profile: 'hero',
|
|
106
|
+
* });
|
|
107
|
+
* return new Response(null, { headers: { Link: preload } });
|
|
108
|
+
* }
|
|
109
|
+
*/
|
|
110
|
+
declare function buildPreloadHeader(path: string, options: SmartGlideOptions): Promise<string>;
|
|
111
|
+
|
|
112
|
+
export { buildPreloadHeader, fetchSmartGlideData, fetchSmartGlideMany };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/url-builder.ts
|
|
4
|
+
function resolveBaseUrl(baseUrl) {
|
|
5
|
+
if (baseUrl) return baseUrl.replace(/\/$/, "");
|
|
6
|
+
if (typeof process !== "undefined") {
|
|
7
|
+
const env = process.env.NEXT_PUBLIC_SMART_GLIDE_URL ?? process.env.VITE_SMART_GLIDE_URL ?? process.env.SMART_GLIDE_URL ?? "";
|
|
8
|
+
if (env) return env.replace(/\/$/, "");
|
|
9
|
+
}
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
function resolveDataPath(dataPath) {
|
|
13
|
+
if (dataPath) return "/" + dataPath.replace(/^\/|\/$/g, "");
|
|
14
|
+
if (typeof process !== "undefined") {
|
|
15
|
+
const env = process.env.NEXT_PUBLIC_SMART_GLIDE_DATA_PATH ?? "/img-data";
|
|
16
|
+
return "/" + env.replace(/^\/|\/$/g, "");
|
|
17
|
+
}
|
|
18
|
+
return "/img-data";
|
|
19
|
+
}
|
|
20
|
+
function buildDataUrl(path, options) {
|
|
21
|
+
const base = resolveBaseUrl(options.baseUrl);
|
|
22
|
+
const prefix = resolveDataPath(options.dataPath);
|
|
23
|
+
const cleanPath = path.replace(/^\//, "");
|
|
24
|
+
const params = {};
|
|
25
|
+
if (options.profile) params.profile = options.profile;
|
|
26
|
+
if (options.blurPlaceholder) params.blur_placeholder = "1";
|
|
27
|
+
if (options.schema) params.schema = "1";
|
|
28
|
+
if (options.responsive === false) {
|
|
29
|
+
params.responsive = "0";
|
|
30
|
+
} else if (Array.isArray(options.responsive)) {
|
|
31
|
+
params.responsive = options.responsive.join(",");
|
|
32
|
+
} else if (options.responsive) {
|
|
33
|
+
params.responsive = options.responsive;
|
|
34
|
+
}
|
|
35
|
+
if (options.params) {
|
|
36
|
+
Object.entries(options.params).forEach(([k, v]) => {
|
|
37
|
+
params[k] = String(v);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const query = new URLSearchParams(params).toString();
|
|
41
|
+
return `${base}${prefix}/${cleanPath}${query ? "?" + query : ""}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/server.ts
|
|
45
|
+
async function fetchSmartGlideData(options) {
|
|
46
|
+
const url = buildDataUrl(options.path, options);
|
|
47
|
+
const response = await fetch(url, {
|
|
48
|
+
headers: {
|
|
49
|
+
Accept: "application/json",
|
|
50
|
+
// Forward server-side env Accept header for AVIF negotiation
|
|
51
|
+
"X-Smart-Glide-Client": "react-sdk"
|
|
52
|
+
},
|
|
53
|
+
...options.fetchOptions ?? {}
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Smart Glide API error: ${response.status} ${response.statusText} \u2014 ${url}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return response.json();
|
|
61
|
+
}
|
|
62
|
+
async function fetchSmartGlideMany(items, shared) {
|
|
63
|
+
return Promise.all(
|
|
64
|
+
items.map((item) => fetchSmartGlideData({ ...shared, ...item }))
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
async function buildPreloadHeader(path, options) {
|
|
68
|
+
const data = await fetchSmartGlideData({ ...options, path });
|
|
69
|
+
const parts = [`<${data.src}>; rel=preload; as=image`];
|
|
70
|
+
if (data.srcset) {
|
|
71
|
+
parts.push(`imagesrcset="${data.srcset}"`);
|
|
72
|
+
}
|
|
73
|
+
if (data.sizes) {
|
|
74
|
+
parts.push(`imagesizes="${data.sizes}"`);
|
|
75
|
+
}
|
|
76
|
+
return parts.join("; ");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
exports.buildPreloadHeader = buildPreloadHeader;
|
|
80
|
+
exports.fetchSmartGlideData = fetchSmartGlideData;
|
|
81
|
+
exports.fetchSmartGlideMany = fetchSmartGlideMany;
|
|
82
|
+
//# sourceMappingURL=server.js.map
|
|
83
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/url-builder.ts","../src/server.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// smart-glide-react — URL Builder\n// Builds Smart Glide delivery URLs client-side (no API call needed).\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { ImageProfile, ResponsivePreset, SmartGlideOptions } from './types';\n\n/** Resolve the base URL from options or environment variables. */\nexport function resolveBaseUrl(baseUrl?: string): string {\n if (baseUrl) return baseUrl.replace(/\\/$/, '');\n\n // Next.js public env\n if (typeof process !== 'undefined') {\n const env =\n process.env.NEXT_PUBLIC_SMART_GLIDE_URL ??\n process.env.VITE_SMART_GLIDE_URL ??\n process.env.SMART_GLIDE_URL ??\n '';\n if (env) return env.replace(/\\/$/, '');\n }\n\n return '';\n}\n\n/** Resolve the delivery path prefix (default: `/img`). */\nexport function resolveDeliveryPath(deliveryPath?: string): string {\n if (deliveryPath) return '/' + deliveryPath.replace(/^\\/|\\/$/g, '');\n\n if (typeof process !== 'undefined') {\n const env = process.env.NEXT_PUBLIC_SMART_GLIDE_DELIVERY_PATH ?? '/img';\n return '/' + env.replace(/^\\/|\\/$/g, '');\n }\n\n return '/img';\n}\n\n/** Resolve the data API path prefix (default: `/img-data`). */\nexport function resolveDataPath(dataPath?: string): string {\n if (dataPath) return '/' + dataPath.replace(/^\\/|\\/$/g, '');\n\n if (typeof process !== 'undefined') {\n const env = process.env.NEXT_PUBLIC_SMART_GLIDE_DATA_PATH ?? '/img-data';\n return '/' + env.replace(/^\\/|\\/$/g, '');\n }\n\n return '/img-data';\n}\n\n/** Build a single Smart Glide delivery URL (unsigned — for preview/dev). */\nexport function buildDeliveryUrl(\n path: string,\n params: Record<string, string | number> = {},\n options: { baseUrl?: string; deliveryPath?: string } = {}\n): string {\n const base = resolveBaseUrl(options.baseUrl);\n const prefix = resolveDeliveryPath(options.deliveryPath);\n const cleanPath = path.replace(/^\\//, '');\n\n const queryParams = Object.entries(params)\n .filter(([, v]) => v !== undefined && v !== '')\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join('&');\n\n return `${base}${prefix}/${cleanPath}${queryParams ? '?' + queryParams : ''}`;\n}\n\n/** Build the JSON data API URL for a given path. */\nexport function buildDataUrl(\n path: string,\n options: SmartGlideOptions & { deliveryPath?: string; dataPath?: string }\n): string {\n const base = resolveBaseUrl(options.baseUrl);\n const prefix = resolveDataPath(options.dataPath);\n const cleanPath = path.replace(/^\\//, '');\n\n const params: Record<string, string> = {};\n\n if (options.profile) params.profile = options.profile;\n if (options.blurPlaceholder) params.blur_placeholder = '1';\n if (options.schema) params.schema = '1';\n\n // Responsive\n if (options.responsive === false) {\n params.responsive = '0';\n } else if (Array.isArray(options.responsive)) {\n params.responsive = options.responsive.join(',');\n } else if (options.responsive) {\n params.responsive = options.responsive;\n }\n\n // Extra Glide params\n if (options.params) {\n Object.entries(options.params).forEach(([k, v]) => {\n params[k] = String(v);\n });\n }\n\n const query = new URLSearchParams(params).toString();\n return `${base}${prefix}/${cleanPath}${query ? '?' + query : ''}`;\n}\n\n/** Resolve a named or custom responsive set to an array of widths. */\nconst NAMED_SETS: Record<string, number[]> = {\n hero: [640, 960, 1280, 1600, 1920],\n thumbnails: [240, 320, 480],\n square: [320, 480, 640],\n portrait: [480, 768, 1024],\n hd: [960, 1280, 1600],\n fhd: [1280, 1600, 1920, 2560],\n retina: [640, 960, 1280, 1920, 2560],\n};\n\nexport function resolveWidths(\n responsive?: string | number[] | false | null\n): number[] {\n if (responsive === false) return [];\n\n if (responsive === null || responsive === undefined) {\n return NAMED_SETS.retina; // sensible default\n }\n\n if (Array.isArray(responsive)) return responsive;\n\n return NAMED_SETS[responsive] ?? NAMED_SETS.retina;\n}\n\n/**\n * Build srcset and sizes strings entirely client-side\n * (no API call — for use with Next.js custom loader or static generation).\n */\nexport function buildSrcSet(\n path: string,\n params: Record<string, string | number> = {},\n widths: number[] = NAMED_SETS.retina,\n options: { baseUrl?: string; deliveryPath?: string } = {}\n): { srcset: string; sizes: string } {\n const entries = widths.map((w) =>\n `${buildDeliveryUrl(path, { ...params, w }, options)} ${w}w`\n );\n\n const sizes = widths\n .map((w) => `(max-width: ${w}px) 100vw`)\n .concat('100vw')\n .join(', ');\n\n return {\n srcset: entries.join(', '),\n sizes,\n };\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// smart-glide-react — Server utilities (Next.js App Router / RSC)\n// These functions run on the server — do NOT import in client components.\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { SmartGlideData, SmartGlideOptions } from './types';\nimport { buildDataUrl } from './url-builder';\n\n/**\n * Fetch Smart Glide image data on the server.\n * Use this inside Next.js Server Components or `generateStaticParams`.\n *\n * Supports Next.js ISR via `fetchOptions.next.revalidate`.\n *\n * @example\n * // app/products/[id]/page.tsx (Server Component)\n * import { fetchSmartGlideData } from 'smart-glide-react/server';\n *\n * export default async function ProductPage({ params }) {\n * const image = await fetchSmartGlideData({\n * path: `products/${params.id}.jpg`,\n * profile: 'hero',\n * responsive: 'retina',\n * blurPlaceholder: true,\n * schema: true,\n * baseUrl: process.env.SMART_GLIDE_URL, // server-side env (no NEXT_PUBLIC_ prefix)\n * fetchOptions: { next: { revalidate: 3600 } }, // ISR: revalidate every hour\n * });\n *\n * return (\n * <img\n * src={image.src}\n * srcSet={image.srcset ?? undefined}\n * sizes={image.sizes ?? undefined}\n * alt=\"Product\"\n * {...(image.blurDataUrl\n * ? { style: { backgroundImage: `url(${image.blurDataUrl})` } }\n * : {})}\n * />\n * );\n * }\n */\nexport async function fetchSmartGlideData(\n options: SmartGlideOptions\n): Promise<SmartGlideData> {\n const url = buildDataUrl(options.path, options);\n\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n // Forward server-side env Accept header for AVIF negotiation\n 'X-Smart-Glide-Client': 'react-sdk',\n },\n ...(options.fetchOptions ?? {}),\n });\n\n if (!response.ok) {\n throw new Error(\n `Smart Glide API error: ${response.status} ${response.statusText} — ${url}`\n );\n }\n\n return response.json() as Promise<SmartGlideData>;\n}\n\n/**\n * Fetch multiple images in parallel — useful for generating static params\n * or pre-loading image data for a collection of items.\n *\n * @example\n * const [hero, thumbnail, avatar] = await fetchSmartGlideMany([\n * { path: 'home/hero.jpg', profile: 'hero', responsive: 'fhd' },\n * { path: 'home/thumb.jpg', profile: 'thumbnail', responsive: 'thumbnails' },\n * { path: 'avatars/user.jpg', profile: 'profile_photo' },\n * ], { baseUrl: process.env.SMART_GLIDE_URL });\n */\nexport async function fetchSmartGlideMany(\n items: Omit<SmartGlideOptions, 'baseUrl'>[],\n shared: Pick<SmartGlideOptions, 'baseUrl' | 'fetchOptions'>\n): Promise<SmartGlideData[]> {\n return Promise.all(\n items.map((item) => fetchSmartGlideData({ ...shared, ...item }))\n );\n}\n\n/**\n * Fetch Smart Glide data and return a preload `<link>` hint header value.\n * Use with `headers()` in Next.js route handlers or middleware.\n *\n * @example\n * // app/api/page-data/route.ts\n * import { headers } from 'next/headers';\n * import { buildPreloadHeader } from 'smart-glide-react/server';\n *\n * export async function GET() {\n * const preload = await buildPreloadHeader('home/hero.jpg', {\n * baseUrl: process.env.SMART_GLIDE_URL,\n * profile: 'hero',\n * });\n * return new Response(null, { headers: { Link: preload } });\n * }\n */\nexport async function buildPreloadHeader(\n path: string,\n options: SmartGlideOptions\n): Promise<string> {\n const data = await fetchSmartGlideData({ ...options, path });\n const parts = [`<${data.src}>; rel=preload; as=image`];\n\n if (data.srcset) {\n parts.push(`imagesrcset=\"${data.srcset}\"`);\n }\n\n if (data.sizes) {\n parts.push(`imagesizes=\"${data.sizes}\"`);\n }\n\n return parts.join('; ');\n}\n"]}
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/url-builder.ts
|
|
2
|
+
function resolveBaseUrl(baseUrl) {
|
|
3
|
+
if (baseUrl) return baseUrl.replace(/\/$/, "");
|
|
4
|
+
if (typeof process !== "undefined") {
|
|
5
|
+
const env = process.env.NEXT_PUBLIC_SMART_GLIDE_URL ?? process.env.VITE_SMART_GLIDE_URL ?? process.env.SMART_GLIDE_URL ?? "";
|
|
6
|
+
if (env) return env.replace(/\/$/, "");
|
|
7
|
+
}
|
|
8
|
+
return "";
|
|
9
|
+
}
|
|
10
|
+
function resolveDataPath(dataPath) {
|
|
11
|
+
if (dataPath) return "/" + dataPath.replace(/^\/|\/$/g, "");
|
|
12
|
+
if (typeof process !== "undefined") {
|
|
13
|
+
const env = process.env.NEXT_PUBLIC_SMART_GLIDE_DATA_PATH ?? "/img-data";
|
|
14
|
+
return "/" + env.replace(/^\/|\/$/g, "");
|
|
15
|
+
}
|
|
16
|
+
return "/img-data";
|
|
17
|
+
}
|
|
18
|
+
function buildDataUrl(path, options) {
|
|
19
|
+
const base = resolveBaseUrl(options.baseUrl);
|
|
20
|
+
const prefix = resolveDataPath(options.dataPath);
|
|
21
|
+
const cleanPath = path.replace(/^\//, "");
|
|
22
|
+
const params = {};
|
|
23
|
+
if (options.profile) params.profile = options.profile;
|
|
24
|
+
if (options.blurPlaceholder) params.blur_placeholder = "1";
|
|
25
|
+
if (options.schema) params.schema = "1";
|
|
26
|
+
if (options.responsive === false) {
|
|
27
|
+
params.responsive = "0";
|
|
28
|
+
} else if (Array.isArray(options.responsive)) {
|
|
29
|
+
params.responsive = options.responsive.join(",");
|
|
30
|
+
} else if (options.responsive) {
|
|
31
|
+
params.responsive = options.responsive;
|
|
32
|
+
}
|
|
33
|
+
if (options.params) {
|
|
34
|
+
Object.entries(options.params).forEach(([k, v]) => {
|
|
35
|
+
params[k] = String(v);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const query = new URLSearchParams(params).toString();
|
|
39
|
+
return `${base}${prefix}/${cleanPath}${query ? "?" + query : ""}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/server.ts
|
|
43
|
+
async function fetchSmartGlideData(options) {
|
|
44
|
+
const url = buildDataUrl(options.path, options);
|
|
45
|
+
const response = await fetch(url, {
|
|
46
|
+
headers: {
|
|
47
|
+
Accept: "application/json",
|
|
48
|
+
// Forward server-side env Accept header for AVIF negotiation
|
|
49
|
+
"X-Smart-Glide-Client": "react-sdk"
|
|
50
|
+
},
|
|
51
|
+
...options.fetchOptions ?? {}
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Smart Glide API error: ${response.status} ${response.statusText} \u2014 ${url}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return response.json();
|
|
59
|
+
}
|
|
60
|
+
async function fetchSmartGlideMany(items, shared) {
|
|
61
|
+
return Promise.all(
|
|
62
|
+
items.map((item) => fetchSmartGlideData({ ...shared, ...item }))
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
async function buildPreloadHeader(path, options) {
|
|
66
|
+
const data = await fetchSmartGlideData({ ...options, path });
|
|
67
|
+
const parts = [`<${data.src}>; rel=preload; as=image`];
|
|
68
|
+
if (data.srcset) {
|
|
69
|
+
parts.push(`imagesrcset="${data.srcset}"`);
|
|
70
|
+
}
|
|
71
|
+
if (data.sizes) {
|
|
72
|
+
parts.push(`imagesizes="${data.sizes}"`);
|
|
73
|
+
}
|
|
74
|
+
return parts.join("; ");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { buildPreloadHeader, fetchSmartGlideData, fetchSmartGlideMany };
|
|
78
|
+
//# sourceMappingURL=server.mjs.map
|
|
79
|
+
//# sourceMappingURL=server.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/url-builder.ts","../src/server.ts"],"names":[],"mappings":"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","file":"server.mjs","sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// smart-glide-react — URL Builder\n// Builds Smart Glide delivery URLs client-side (no API call needed).\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { ImageProfile, ResponsivePreset, SmartGlideOptions } from './types';\n\n/** Resolve the base URL from options or environment variables. */\nexport function resolveBaseUrl(baseUrl?: string): string {\n if (baseUrl) return baseUrl.replace(/\\/$/, '');\n\n // Next.js public env\n if (typeof process !== 'undefined') {\n const env =\n process.env.NEXT_PUBLIC_SMART_GLIDE_URL ??\n process.env.VITE_SMART_GLIDE_URL ??\n process.env.SMART_GLIDE_URL ??\n '';\n if (env) return env.replace(/\\/$/, '');\n }\n\n return '';\n}\n\n/** Resolve the delivery path prefix (default: `/img`). */\nexport function resolveDeliveryPath(deliveryPath?: string): string {\n if (deliveryPath) return '/' + deliveryPath.replace(/^\\/|\\/$/g, '');\n\n if (typeof process !== 'undefined') {\n const env = process.env.NEXT_PUBLIC_SMART_GLIDE_DELIVERY_PATH ?? '/img';\n return '/' + env.replace(/^\\/|\\/$/g, '');\n }\n\n return '/img';\n}\n\n/** Resolve the data API path prefix (default: `/img-data`). */\nexport function resolveDataPath(dataPath?: string): string {\n if (dataPath) return '/' + dataPath.replace(/^\\/|\\/$/g, '');\n\n if (typeof process !== 'undefined') {\n const env = process.env.NEXT_PUBLIC_SMART_GLIDE_DATA_PATH ?? '/img-data';\n return '/' + env.replace(/^\\/|\\/$/g, '');\n }\n\n return '/img-data';\n}\n\n/** Build a single Smart Glide delivery URL (unsigned — for preview/dev). */\nexport function buildDeliveryUrl(\n path: string,\n params: Record<string, string | number> = {},\n options: { baseUrl?: string; deliveryPath?: string } = {}\n): string {\n const base = resolveBaseUrl(options.baseUrl);\n const prefix = resolveDeliveryPath(options.deliveryPath);\n const cleanPath = path.replace(/^\\//, '');\n\n const queryParams = Object.entries(params)\n .filter(([, v]) => v !== undefined && v !== '')\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join('&');\n\n return `${base}${prefix}/${cleanPath}${queryParams ? '?' + queryParams : ''}`;\n}\n\n/** Build the JSON data API URL for a given path. */\nexport function buildDataUrl(\n path: string,\n options: SmartGlideOptions & { deliveryPath?: string; dataPath?: string }\n): string {\n const base = resolveBaseUrl(options.baseUrl);\n const prefix = resolveDataPath(options.dataPath);\n const cleanPath = path.replace(/^\\//, '');\n\n const params: Record<string, string> = {};\n\n if (options.profile) params.profile = options.profile;\n if (options.blurPlaceholder) params.blur_placeholder = '1';\n if (options.schema) params.schema = '1';\n\n // Responsive\n if (options.responsive === false) {\n params.responsive = '0';\n } else if (Array.isArray(options.responsive)) {\n params.responsive = options.responsive.join(',');\n } else if (options.responsive) {\n params.responsive = options.responsive;\n }\n\n // Extra Glide params\n if (options.params) {\n Object.entries(options.params).forEach(([k, v]) => {\n params[k] = String(v);\n });\n }\n\n const query = new URLSearchParams(params).toString();\n return `${base}${prefix}/${cleanPath}${query ? '?' + query : ''}`;\n}\n\n/** Resolve a named or custom responsive set to an array of widths. */\nconst NAMED_SETS: Record<string, number[]> = {\n hero: [640, 960, 1280, 1600, 1920],\n thumbnails: [240, 320, 480],\n square: [320, 480, 640],\n portrait: [480, 768, 1024],\n hd: [960, 1280, 1600],\n fhd: [1280, 1600, 1920, 2560],\n retina: [640, 960, 1280, 1920, 2560],\n};\n\nexport function resolveWidths(\n responsive?: string | number[] | false | null\n): number[] {\n if (responsive === false) return [];\n\n if (responsive === null || responsive === undefined) {\n return NAMED_SETS.retina; // sensible default\n }\n\n if (Array.isArray(responsive)) return responsive;\n\n return NAMED_SETS[responsive] ?? NAMED_SETS.retina;\n}\n\n/**\n * Build srcset and sizes strings entirely client-side\n * (no API call — for use with Next.js custom loader or static generation).\n */\nexport function buildSrcSet(\n path: string,\n params: Record<string, string | number> = {},\n widths: number[] = NAMED_SETS.retina,\n options: { baseUrl?: string; deliveryPath?: string } = {}\n): { srcset: string; sizes: string } {\n const entries = widths.map((w) =>\n `${buildDeliveryUrl(path, { ...params, w }, options)} ${w}w`\n );\n\n const sizes = widths\n .map((w) => `(max-width: ${w}px) 100vw`)\n .concat('100vw')\n .join(', ');\n\n return {\n srcset: entries.join(', '),\n sizes,\n };\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// smart-glide-react — Server utilities (Next.js App Router / RSC)\n// These functions run on the server — do NOT import in client components.\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { SmartGlideData, SmartGlideOptions } from './types';\nimport { buildDataUrl } from './url-builder';\n\n/**\n * Fetch Smart Glide image data on the server.\n * Use this inside Next.js Server Components or `generateStaticParams`.\n *\n * Supports Next.js ISR via `fetchOptions.next.revalidate`.\n *\n * @example\n * // app/products/[id]/page.tsx (Server Component)\n * import { fetchSmartGlideData } from 'smart-glide-react/server';\n *\n * export default async function ProductPage({ params }) {\n * const image = await fetchSmartGlideData({\n * path: `products/${params.id}.jpg`,\n * profile: 'hero',\n * responsive: 'retina',\n * blurPlaceholder: true,\n * schema: true,\n * baseUrl: process.env.SMART_GLIDE_URL, // server-side env (no NEXT_PUBLIC_ prefix)\n * fetchOptions: { next: { revalidate: 3600 } }, // ISR: revalidate every hour\n * });\n *\n * return (\n * <img\n * src={image.src}\n * srcSet={image.srcset ?? undefined}\n * sizes={image.sizes ?? undefined}\n * alt=\"Product\"\n * {...(image.blurDataUrl\n * ? { style: { backgroundImage: `url(${image.blurDataUrl})` } }\n * : {})}\n * />\n * );\n * }\n */\nexport async function fetchSmartGlideData(\n options: SmartGlideOptions\n): Promise<SmartGlideData> {\n const url = buildDataUrl(options.path, options);\n\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n // Forward server-side env Accept header for AVIF negotiation\n 'X-Smart-Glide-Client': 'react-sdk',\n },\n ...(options.fetchOptions ?? {}),\n });\n\n if (!response.ok) {\n throw new Error(\n `Smart Glide API error: ${response.status} ${response.statusText} — ${url}`\n );\n }\n\n return response.json() as Promise<SmartGlideData>;\n}\n\n/**\n * Fetch multiple images in parallel — useful for generating static params\n * or pre-loading image data for a collection of items.\n *\n * @example\n * const [hero, thumbnail, avatar] = await fetchSmartGlideMany([\n * { path: 'home/hero.jpg', profile: 'hero', responsive: 'fhd' },\n * { path: 'home/thumb.jpg', profile: 'thumbnail', responsive: 'thumbnails' },\n * { path: 'avatars/user.jpg', profile: 'profile_photo' },\n * ], { baseUrl: process.env.SMART_GLIDE_URL });\n */\nexport async function fetchSmartGlideMany(\n items: Omit<SmartGlideOptions, 'baseUrl'>[],\n shared: Pick<SmartGlideOptions, 'baseUrl' | 'fetchOptions'>\n): Promise<SmartGlideData[]> {\n return Promise.all(\n items.map((item) => fetchSmartGlideData({ ...shared, ...item }))\n );\n}\n\n/**\n * Fetch Smart Glide data and return a preload `<link>` hint header value.\n * Use with `headers()` in Next.js route handlers or middleware.\n *\n * @example\n * // app/api/page-data/route.ts\n * import { headers } from 'next/headers';\n * import { buildPreloadHeader } from 'smart-glide-react/server';\n *\n * export async function GET() {\n * const preload = await buildPreloadHeader('home/hero.jpg', {\n * baseUrl: process.env.SMART_GLIDE_URL,\n * profile: 'hero',\n * });\n * return new Response(null, { headers: { Link: preload } });\n * }\n */\nexport async function buildPreloadHeader(\n path: string,\n options: SmartGlideOptions\n): Promise<string> {\n const data = await fetchSmartGlideData({ ...options, path });\n const parts = [`<${data.src}>; rel=preload; as=image`];\n\n if (data.srcset) {\n parts.push(`imagesrcset=\"${data.srcset}\"`);\n }\n\n if (data.sizes) {\n parts.push(`imagesizes=\"${data.sizes}\"`);\n }\n\n return parts.join('; ');\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "smart-glide-react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React & Next.js SDK for Laravel Smart Glide — responsive images, blur placeholders, JSON-LD schema, and AVIF/WebP auto-negotiation.",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.cjs",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./next": {
|
|
15
|
+
"import": "./dist/next.mjs",
|
|
16
|
+
"require": "./dist/next.cjs",
|
|
17
|
+
"types": "./dist/next.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./server": {
|
|
20
|
+
"import": "./dist/server.mjs",
|
|
21
|
+
"require": "./dist/server.cjs",
|
|
22
|
+
"types": "./dist/server.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup",
|
|
32
|
+
"dev": "tsup --watch",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"prepublishOnly": "npm run build"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"laravel",
|
|
38
|
+
"smart-glide",
|
|
39
|
+
"react",
|
|
40
|
+
"nextjs",
|
|
41
|
+
"next-image",
|
|
42
|
+
"image",
|
|
43
|
+
"responsive-images",
|
|
44
|
+
"srcset",
|
|
45
|
+
"webp",
|
|
46
|
+
"avif",
|
|
47
|
+
"blur-placeholder",
|
|
48
|
+
"lqip",
|
|
49
|
+
"seo",
|
|
50
|
+
"json-ld",
|
|
51
|
+
"image-optimization"
|
|
52
|
+
],
|
|
53
|
+
"author": "Shadi Shammaa <shadi.shammaa@gmail.com>",
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"homepage": "https://github.com/shammaa/smart-glide-react",
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "git+https://github.com/shammaa/smart-glide-react.git"
|
|
59
|
+
},
|
|
60
|
+
"bugs": {
|
|
61
|
+
"url": "https://github.com/shammaa/smart-glide-react/issues"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"react": ">=18.0.0",
|
|
65
|
+
"react-dom": ">=18.0.0"
|
|
66
|
+
},
|
|
67
|
+
"peerDependenciesMeta": {
|
|
68
|
+
"next": {
|
|
69
|
+
"optional": true
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@types/node": "^25.5.0",
|
|
74
|
+
"@types/react": "^18.3.0",
|
|
75
|
+
"@types/react-dom": "^18.3.0",
|
|
76
|
+
"react": "^18.3.0",
|
|
77
|
+
"react-dom": "^18.3.0",
|
|
78
|
+
"tsup": "^8.0.0",
|
|
79
|
+
"typescript": "^5.4.0"
|
|
80
|
+
}
|
|
81
|
+
}
|