@smoothcdn/loader 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.
Files changed (110) hide show
  1. package/dist/astro.d.ts +5 -0
  2. package/dist/astro.d.ts.map +1 -0
  3. package/dist/astro.js +3 -0
  4. package/dist/core/config.d.ts +6 -0
  5. package/dist/core/config.d.ts.map +1 -0
  6. package/dist/core/config.js +78 -0
  7. package/dist/core/createLoader.d.ts +3 -0
  8. package/dist/core/createLoader.d.ts.map +1 -0
  9. package/dist/core/createLoader.js +24 -0
  10. package/dist/core/index.d.ts +5 -0
  11. package/dist/core/index.d.ts.map +1 -0
  12. package/dist/core/index.js +3 -0
  13. package/dist/core/loaders/fetch.d.ts +2 -0
  14. package/dist/core/loaders/fetch.d.ts.map +1 -0
  15. package/dist/core/loaders/fetch.js +10 -0
  16. package/dist/core/loaders/image.d.ts +2 -0
  17. package/dist/core/loaders/image.d.ts.map +1 -0
  18. package/dist/core/loaders/image.js +12 -0
  19. package/dist/core/loaders/script.d.ts +2 -0
  20. package/dist/core/loaders/script.d.ts.map +1 -0
  21. package/dist/core/loaders/script.js +14 -0
  22. package/dist/core/loaders/style.d.ts +2 -0
  23. package/dist/core/loaders/style.d.ts.map +1 -0
  24. package/dist/core/loaders/style.js +14 -0
  25. package/dist/core/runtime.d.ts +2 -0
  26. package/dist/core/runtime.d.ts.map +1 -0
  27. package/dist/core/runtime.js +7 -0
  28. package/dist/core/types.d.ts +72 -0
  29. package/dist/core/types.d.ts.map +1 -0
  30. package/dist/core/types.js +1 -0
  31. package/dist/core/utils.d.ts +7 -0
  32. package/dist/core/utils.d.ts.map +1 -0
  33. package/dist/core/utils.js +50 -0
  34. package/dist/framework/shared.d.ts +7 -0
  35. package/dist/framework/shared.d.ts.map +1 -0
  36. package/dist/framework/shared.js +8 -0
  37. package/dist/index.d.ts +5 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +3 -0
  40. package/dist/next/CDNImage.d.ts +8 -0
  41. package/dist/next/CDNImage.d.ts.map +1 -0
  42. package/dist/next/CDNImage.js +6 -0
  43. package/dist/next/Script.d.ts +7 -0
  44. package/dist/next/Script.d.ts.map +1 -0
  45. package/dist/next/Script.js +7 -0
  46. package/dist/next/Style.d.ts +6 -0
  47. package/dist/next/Style.d.ts.map +1 -0
  48. package/dist/next/Style.js +7 -0
  49. package/dist/next/index.d.ts +7 -0
  50. package/dist/next/index.d.ts.map +1 -0
  51. package/dist/next/index.js +6 -0
  52. package/dist/nuxt.d.ts +6 -0
  53. package/dist/nuxt.d.ts.map +1 -0
  54. package/dist/nuxt.js +4 -0
  55. package/dist/react/CDNAudio.d.ts +14 -0
  56. package/dist/react/CDNAudio.d.ts.map +1 -0
  57. package/dist/react/CDNAudio.js +5 -0
  58. package/dist/react/CDNImage.d.ts +11 -0
  59. package/dist/react/CDNImage.d.ts.map +1 -0
  60. package/dist/react/CDNImage.js +28 -0
  61. package/dist/react/CDNVideo.d.ts +12 -0
  62. package/dist/react/CDNVideo.d.ts.map +1 -0
  63. package/dist/react/CDNVideo.js +5 -0
  64. package/dist/react/index.d.ts +8 -0
  65. package/dist/react/index.d.ts.map +1 -0
  66. package/dist/react/index.js +7 -0
  67. package/dist/react/useFetch.d.ts +6 -0
  68. package/dist/react/useFetch.d.ts.map +1 -0
  69. package/dist/react/useFetch.js +14 -0
  70. package/dist/react/useImage.d.ts +6 -0
  71. package/dist/react/useImage.d.ts.map +1 -0
  72. package/dist/react/useImage.js +13 -0
  73. package/dist/react/useScript.d.ts +6 -0
  74. package/dist/react/useScript.d.ts.map +1 -0
  75. package/dist/react/useScript.js +13 -0
  76. package/dist/react/useStyle.d.ts +3 -0
  77. package/dist/react/useStyle.d.ts.map +1 -0
  78. package/dist/react/useStyle.js +7 -0
  79. package/dist/remix.d.ts +11 -0
  80. package/dist/remix.d.ts.map +1 -0
  81. package/dist/remix.js +8 -0
  82. package/dist/sveltekit.d.ts +19 -0
  83. package/dist/sveltekit.d.ts.map +1 -0
  84. package/dist/sveltekit.js +15 -0
  85. package/dist/types-generator.d.ts +20 -0
  86. package/dist/types-generator.d.ts.map +1 -0
  87. package/dist/types-generator.js +209 -0
  88. package/dist/vue/CDNImage.d.ts +39 -0
  89. package/dist/vue/CDNImage.d.ts.map +1 -0
  90. package/dist/vue/CDNImage.js +26 -0
  91. package/dist/vue/index.d.ts +7 -0
  92. package/dist/vue/index.d.ts.map +1 -0
  93. package/dist/vue/index.js +5 -0
  94. package/dist/vue/useCdnFetch.d.ts +6 -0
  95. package/dist/vue/useCdnFetch.d.ts.map +1 -0
  96. package/dist/vue/useCdnFetch.js +14 -0
  97. package/dist/vue/useCdnScript.d.ts +3 -0
  98. package/dist/vue/useCdnScript.d.ts.map +1 -0
  99. package/dist/vue/useCdnScript.js +7 -0
  100. package/dist/vue/useCdnStyle.d.ts +3 -0
  101. package/dist/vue/useCdnStyle.d.ts.map +1 -0
  102. package/dist/vue/useCdnStyle.js +7 -0
  103. package/dist/vue/useFetch.d.ts +6 -0
  104. package/dist/vue/useFetch.d.ts.map +1 -0
  105. package/dist/vue/useFetch.js +19 -0
  106. package/dist/vue/useImage.d.ts +6 -0
  107. package/dist/vue/useImage.d.ts.map +1 -0
  108. package/dist/vue/useImage.js +18 -0
  109. package/package.json +93 -0
  110. package/readme.md +356 -0
@@ -0,0 +1,6 @@
1
+ import type { LoaderAssetMap, LoaderDefaultAssetMap, LoaderEndpointPath, LoaderVariantSlug } from "../core/types.js";
2
+ export declare function useFetch<T = unknown, TAssets extends Partial<LoaderAssetMap> | undefined = LoaderDefaultAssetMap>(assetPath: LoaderEndpointPath<TAssets>, variantSlug?: LoaderVariantSlug<TAssets>, init?: RequestInit): {
3
+ data: [T | null] extends [import("vue").Ref<any, any>] ? import("@vue/shared").IfAny<import("vue").Ref<any, any> & T, import("vue").Ref<import("vue").Ref<any, any> & T, import("vue").Ref<any, any> & T>, import("vue").Ref<any, any> & T> : import("vue").Ref<import("vue").UnwrapRef<T> | null, T | import("vue").UnwrapRef<T> | null>;
4
+ error: import("vue").Ref<Error | null, Error | null>;
5
+ };
6
+ //# sourceMappingURL=useFetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFetch.d.ts","sourceRoot":"","sources":["../../src/vue/useFetch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,cAAc,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAEnH,wBAAgB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,SAAS,OAAO,CAAC,cAAc,CAAC,GAAG,SAAS,GAAG,qBAAqB,EAC7G,SAAS,EAAE,kBAAkB,CAAC,OAAO,CAAC,EACtC,WAAW,CAAC,EAAE,iBAAiB,CAAC,OAAO,CAAC,EACxC,IAAI,CAAC,EAAE,WAAW;;;EAmBrB"}
@@ -0,0 +1,19 @@
1
+ import { ref, watchEffect } from "vue";
2
+ import { createLoader } from "../core/createLoader.js";
3
+ export function useFetch(assetPath, variantSlug, init) {
4
+ const data = ref(null);
5
+ const error = ref(null);
6
+ watchEffect(() => {
7
+ createLoader()
8
+ .fetch(assetPath, variantSlug, init)
9
+ .then((response) => response.json())
10
+ .then((payload) => {
11
+ data.value = payload;
12
+ error.value = null;
13
+ })
14
+ .catch((currentError) => {
15
+ error.value = currentError;
16
+ });
17
+ });
18
+ return { data, error };
19
+ }
@@ -0,0 +1,6 @@
1
+ import type { LoaderAssetMap, LoaderDefaultAssetMap, LoaderImagePath, LoaderVariantSlug } from "../core/types.js";
2
+ export declare function useImage<TAssets extends Partial<LoaderAssetMap> | undefined = LoaderDefaultAssetMap>(assetPath: LoaderImagePath<TAssets>, variantSlug?: LoaderVariantSlug<TAssets>): {
3
+ image: import("vue").Ref<HTMLImageElement | null, HTMLImageElement | null>;
4
+ error: import("vue").Ref<Error | null, Error | null>;
5
+ };
6
+ //# sourceMappingURL=useImage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useImage.d.ts","sourceRoot":"","sources":["../../src/vue/useImage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,cAAc,EAAE,qBAAqB,EAAE,eAAe,EAAE,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAEhH,wBAAgB,QAAQ,CAAC,OAAO,SAAS,OAAO,CAAC,cAAc,CAAC,GAAG,SAAS,GAAG,qBAAqB,EAChG,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,EACnC,WAAW,CAAC,EAAE,iBAAiB,CAAC,OAAO,CAAC;;;EAkB3C"}
@@ -0,0 +1,18 @@
1
+ import { ref, watchEffect } from "vue";
2
+ import { createLoader } from "../core/createLoader.js";
3
+ export function useImage(assetPath, variantSlug) {
4
+ const image = ref(null);
5
+ const error = ref(null);
6
+ watchEffect(() => {
7
+ createLoader()
8
+ .image(assetPath, variantSlug)
9
+ .then((loadedImage) => {
10
+ image.value = loadedImage;
11
+ error.value = null;
12
+ })
13
+ .catch((currentError) => {
14
+ error.value = currentError;
15
+ });
16
+ });
17
+ return { image, error };
18
+ }
package/package.json ADDED
@@ -0,0 +1,93 @@
1
+ {
2
+ "name": "@smoothcdn/loader",
3
+ "version": "1.0.0",
4
+ "description": "Smooth CDN framework-aware asset loader",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ },
16
+ "./core": {
17
+ "types": "./dist/core/index.d.ts",
18
+ "import": "./dist/core/index.js"
19
+ },
20
+ "./react": {
21
+ "types": "./dist/react/index.d.ts",
22
+ "import": "./dist/react/index.js"
23
+ },
24
+ "./vue": {
25
+ "types": "./dist/vue/index.d.ts",
26
+ "import": "./dist/vue/index.js"
27
+ },
28
+ "./next": {
29
+ "types": "./dist/next/index.d.ts",
30
+ "import": "./dist/next/index.js"
31
+ },
32
+ "./nuxt": {
33
+ "types": "./dist/nuxt.d.ts",
34
+ "import": "./dist/nuxt.js"
35
+ },
36
+ "./astro": {
37
+ "types": "./dist/astro.d.ts",
38
+ "import": "./dist/astro.js"
39
+ },
40
+ "./sveltekit": {
41
+ "types": "./dist/sveltekit.d.ts",
42
+ "import": "./dist/sveltekit.js"
43
+ },
44
+ "./remix": {
45
+ "types": "./dist/remix.d.ts",
46
+ "import": "./dist/remix.js"
47
+ },
48
+ "./types-generator": {
49
+ "types": "./dist/types-generator.d.ts",
50
+ "import": "./dist/types-generator.js"
51
+ },
52
+ "./package.json": {
53
+ "default": "./package.json"
54
+ }
55
+ },
56
+
57
+ "files": ["dist"],
58
+ "sideEffects": false,
59
+
60
+ "scripts": {
61
+ "build": "tsc",
62
+ "dev": "tsc --watch"
63
+ },
64
+
65
+ "peerDependencies": {
66
+ "react": "^18.0.0 || ^19.0.0",
67
+ "vue": "^3.0.0",
68
+ "next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "react": {
72
+ "optional": true
73
+ },
74
+ "vue": {
75
+ "optional": true
76
+ },
77
+ "next": {
78
+ "optional": true
79
+ }
80
+ },
81
+
82
+ "devDependencies": {
83
+ "@types/node": "^25.6.0",
84
+ "typescript": "^5.3.0",
85
+
86
+ "react": "^18.2.0",
87
+ "@types/react": "^18.2.0",
88
+
89
+ "vue": "^3.4.0",
90
+
91
+ "next": "^13.5.0"
92
+ }
93
+ }
package/readme.md ADDED
@@ -0,0 +1,356 @@
1
+ # @smoothcdn/loader
2
+
3
+ Framework-aware loader for Smooth CDN assets.
4
+
5
+ `@smoothcdn/loader` reads the same `.scdn.json` project configuration used by the Smooth CDN CLI and gives your app typed helpers and ready-to-use components for loading images, scripts, styles and fetchable assets from Smooth CDN.
6
+
7
+ It includes:
8
+
9
+ - a framework-neutral core loader,
10
+ - React and Next.js components/hooks for images, audio, video, scripts and styles,
11
+ - Vue and Nuxt helpers,
12
+ - Astro, SvelteKit and Remix entrypoints,
13
+ - TypeScript asset typing generated from `.scdn.json`.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @smoothcdn/loader
19
+ ```
20
+
21
+ ## Configuration
22
+
23
+ Place `.scdn.json` in the root of your project, generate the loader module, then call its bootstrap once from your app entrypoint.
24
+
25
+ ```bash
26
+ scdn types
27
+ ```
28
+
29
+ ```ts
30
+ import { configureSmoothCDN } from "./smoothcdn-loader";
31
+
32
+ configureSmoothCDN();
33
+ ```
34
+
35
+ After that, import components and helpers directly from `@smoothcdn/loader`. The generated file adds project asset types globally, so `assetPath` suggestions work without importing a project-specific wrapper in every component.
36
+
37
+ The loader expects the standard Smooth CDN config shape:
38
+
39
+ ```json
40
+ {
41
+ "userSlug": "smoothcdn",
42
+ "projectSlug": "web",
43
+ "customSubdomain": "web",
44
+ "sources": ["./public/*", "./.next/static/**/*"],
45
+ "excludes": ["./public/*.txt", "./.next/static/**/*.map"],
46
+ "replacePath": {
47
+ "/.next/": "/_next/"
48
+ },
49
+ "imageVariants": {
50
+ "320": 320,
51
+ "480": 480,
52
+ "640": 640,
53
+ "768": 768,
54
+ "960": 960,
55
+ "1200": 1200
56
+ }
57
+ }
58
+ ```
59
+
60
+ Use `replacePath` when files are collected from one location but should be referenced from another public path. `replacePaths` is also accepted as an alias.
61
+
62
+ For projects with `customSubdomain`, URLs are built from the subdomain root:
63
+
64
+ ```ts
65
+ buildUrl("/public/logo.svg");
66
+ // https://web.smoothcdn.com/public/logo.svg
67
+ ```
68
+
69
+ Without `customSubdomain`, URLs use the standard Smooth CDN path:
70
+
71
+ ```ts
72
+ buildUrl("/public/logo.svg");
73
+ // https://cdn.smoothcdn.com/<userSlug>/<projectSlug>/<version>/<variant>/public/logo.svg
74
+ ```
75
+
76
+ ## TypeScript Asset Typing
77
+
78
+ The package exposes a generator that reads root `.scdn.json`, scans configured sources, applies `excludes` and `replacePath`, and writes a TypeScript module with project asset types.
79
+
80
+ Generate the types with the Smooth CDN CLI:
81
+
82
+ ```bash
83
+ scdn types
84
+ ```
85
+
86
+ The command should generate:
87
+
88
+ ```text
89
+ smoothcdn-loader.ts
90
+ ```
91
+
92
+ To write the file somewhere else:
93
+
94
+ ```bash
95
+ scdn types --output src/smoothcdn-loader.ts
96
+ ```
97
+
98
+ Once the generated module is included in your app bootstrap, helpers and components imported from `@smoothcdn/loader` use those generated asset types automatically:
99
+
100
+ ```ts
101
+ import { createLoader } from "@smoothcdn/loader";
102
+
103
+ const loader = createLoader();
104
+
105
+ loader.url("/public/logo.svg");
106
+ loader.image("/public/logo.svg");
107
+ ```
108
+
109
+ Commit `smoothcdn-loader.ts` with your app if you want asset-path autocomplete to work without regenerating it on every install. The `.scdn` directory can stay ignored.
110
+
111
+ ```gitignore
112
+ .scdn/*
113
+ !.scdn/
114
+ ```
115
+
116
+ ## Core
117
+
118
+ Use the core loader in any JavaScript or TypeScript runtime that has the required browser APIs.
119
+
120
+ ```ts
121
+ import { createLoader } from "@smoothcdn/loader";
122
+
123
+ const loader = createLoader();
124
+
125
+ const logoUrl = loader.url("/public/logo.svg");
126
+
127
+ await loader.script("/public/app.js");
128
+ await loader.style("/public/app.css");
129
+ await loader.image("/public/logo.svg");
130
+
131
+ const response = await loader.fetch("/public/data.json");
132
+ const data = await response.json();
133
+ ```
134
+
135
+ ## React
136
+
137
+ ```tsx
138
+ import { CDNImage, useFetch, useImage } from "@smoothcdn/loader/react";
139
+
140
+ export function Logo() {
141
+ return (
142
+ <CDNImage
143
+ assetPath="/public/logo.svg"
144
+ alt="Smooth CDN"
145
+ width={40}
146
+ height={40}
147
+ />
148
+ );
149
+ }
150
+
151
+ export function Preview() {
152
+ const { image, error } = useImage("/public/logo.svg");
153
+
154
+ if (error) return null;
155
+ return <span>{image ? "Loaded" : "Loading"}</span>;
156
+ }
157
+
158
+ export function DataBlock() {
159
+ const { data } = useFetch("/public/data.json");
160
+
161
+ return <pre>{JSON.stringify(data, null, 2)}</pre>;
162
+ }
163
+ ```
164
+
165
+ ## Next.js
166
+
167
+ ```tsx
168
+ import { Script, Style } from "@smoothcdn/loader/next";
169
+ import { CDNAudio, CDNImage, CDNVideo } from "@smoothcdn/loader/react";
170
+
171
+ export default function Page() {
172
+ return (
173
+ <>
174
+ <Style assetPath="/public/app.css" />
175
+ <Script assetPath="/public/app.js" />
176
+
177
+ <CDNImage
178
+ assetPath="/public/logo.svg"
179
+ alt="Smooth CDN"
180
+ width={80}
181
+ height={80}
182
+ />
183
+
184
+ <CDNAudio
185
+ assetPath="/public/intro.mp3"
186
+ controls
187
+ />
188
+
189
+ <CDNVideo
190
+ assetPath="/public/demo.mp4"
191
+ posterAssetPath="/public/demo-poster.jpg"
192
+ controls
193
+ width={1280}
194
+ height={720}
195
+ />
196
+ </>
197
+ );
198
+ }
199
+ ```
200
+
201
+ ## Vue
202
+
203
+ ```vue
204
+ <script setup lang="ts">
205
+ import { CDNImage, useImage } from "@smoothcdn/loader/vue";
206
+
207
+ const { image, error } = useImage("/public/logo.svg");
208
+ </script>
209
+
210
+ <template>
211
+ <CDNImage
212
+ asset-path="/public/logo.svg"
213
+ alt="Smooth CDN"
214
+ width="80"
215
+ height="80"
216
+ />
217
+
218
+ <span v-if="image">Loaded</span>
219
+ <span v-if="error">Failed</span>
220
+ </template>
221
+ ```
222
+
223
+ ## Nuxt
224
+
225
+ Create a plugin that configures the loader once.
226
+
227
+ ```ts
228
+ // plugins/smoothcdn.ts
229
+ import { configureSmoothCDN } from "../smoothcdn-loader";
230
+
231
+ export default defineNuxtPlugin(() => {
232
+ configureSmoothCDN();
233
+ });
234
+ ```
235
+
236
+ Use the Nuxt entrypoint in components or composables:
237
+
238
+ ```vue
239
+ <script setup lang="ts">
240
+ import { createSmoothLoader, CDNImage } from "@smoothcdn/loader/nuxt";
241
+
242
+ const { url } = createSmoothLoader();
243
+ const logoUrl = url("/public/logo.svg");
244
+ </script>
245
+
246
+ <template>
247
+ <CDNImage
248
+ asset-path="/public/logo.svg"
249
+ alt="Smooth CDN"
250
+ width="80"
251
+ height="80"
252
+ />
253
+
254
+ <a :href="logoUrl">Open logo</a>
255
+ </template>
256
+ ```
257
+
258
+ ## Astro
259
+
260
+ ```astro
261
+ ---
262
+ import { createSmoothLoader } from "@smoothcdn/loader/astro";
263
+
264
+ const { url } = createSmoothLoader();
265
+ const logoUrl = url("/public/logo.svg");
266
+ ---
267
+
268
+ <img src={logoUrl} alt="Smooth CDN" width="80" height="80" />
269
+ ```
270
+
271
+ ## SvelteKit
272
+
273
+ Initialize the loader in a shared module:
274
+
275
+ ```ts
276
+ // src/lib/smoothcdn.ts
277
+ import { createSmoothLoader } from "@smoothcdn/loader/sveltekit";
278
+
279
+ export const smoothcdn = createSmoothLoader();
280
+ ```
281
+
282
+ Use it in a Svelte component:
283
+
284
+ ```svelte
285
+ <script lang="ts">
286
+ import { smoothcdn } from "$lib/smoothcdn";
287
+
288
+ const logoUrl = smoothcdn.url("/public/logo.svg");
289
+ </script>
290
+
291
+ <img src={logoUrl} alt="Smooth CDN" width="80" height="80" />
292
+ ```
293
+
294
+ Or create a typed `load` helper:
295
+
296
+ ```ts
297
+ import { createAssetLoad } from "@smoothcdn/loader/sveltekit";
298
+
299
+ export const load = createAssetLoad({
300
+ assetPath: "/public/logo.svg",
301
+ depends: ["smoothcdn:logo"],
302
+ cacheControl: "public, max-age=3600"
303
+ });
304
+ ```
305
+
306
+ ## Remix
307
+
308
+ ```tsx
309
+ import { createSmoothLoader, createStyleLink } from "@smoothcdn/loader/remix";
310
+
311
+ const { url } = createSmoothLoader();
312
+
313
+ export const links = () => [
314
+ createStyleLink("/public/app.css")
315
+ ];
316
+
317
+ export default function Route() {
318
+ return (
319
+ <img
320
+ src={url("/public/logo.svg")}
321
+ alt="Smooth CDN"
322
+ width={80}
323
+ height={80}
324
+ />
325
+ );
326
+ }
327
+ ```
328
+
329
+ ## Generated Image Variants
330
+
331
+ If your `.scdn.json` contains `imageVariants`, generated type output also includes those labels as valid variants.
332
+
333
+ ```ts
334
+ const loader = createLoader();
335
+
336
+ loader.image("/public/logo.png", "640");
337
+ ```
338
+
339
+ For responsive images, use the package component with `responsive`. It builds `srcSet` from `imageVariants` in `.scdn.json` unless you pass `variants` explicitly.
340
+
341
+ ```tsx
342
+ import { CDNImage } from "@smoothcdn/loader/react";
343
+
344
+ export function HeroImage() {
345
+ return (
346
+ <CDNImage
347
+ responsive
348
+ assetPath="/public/hero.png"
349
+ alt="Hero"
350
+ width={1536}
351
+ height={1024}
352
+ sizes="(min-width: 1024px) 50vw, 100vw"
353
+ />
354
+ );
355
+ }
356
+ ```