@sandquist-group/svelte-imgproxy 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sandquist Group
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # svelte-imgproxy
2
+
3
+ Small Svelte 5 image component + helpers for imgproxy.
4
+
5
+ ```sh
6
+ pnpm add @sandquist-group/svelte-imgproxy
7
+ ```
8
+
9
+ Requires Svelte 5 and Node.js 20 or newer.
10
+
11
+ Designed for a setup like:
12
+
13
+ ```txt
14
+ Svelte app -> https://images.example.com -> imgproxy -> private object storage
15
+ ```
16
+
17
+ The default URL shape preserves aspect ratio and fits within width:
18
+
19
+ ```txt
20
+ https://images.example.com/unsafe/rs:fit:640:0/plain/blog/hero.jpg
21
+ ```
22
+
23
+ `unsafe` is the default imgproxy signature segment. Use a signed imgproxy
24
+ endpoint and configure `signature` when the endpoint is exposed publicly.
25
+
26
+ ## Install locally
27
+
28
+ ```sh
29
+ pnpm add ../svelte-imgproxy
30
+ ```
31
+
32
+ For the published package, install `@sandquist-group/svelte-imgproxy`.
33
+
34
+ ## Configure once
35
+
36
+ In your app entry/layout, configure the singleton:
37
+
38
+ ```ts
39
+ import { configureImgproxy } from 'svelte-imgproxy';
40
+
41
+ configureImgproxy({
42
+ baseUrl: 'https://images.example.com',
43
+ basePath: 'blog',
44
+ widths: [320, 640, 960, 1280, 1920],
45
+ sizes: '(max-width: 1280px) 100vw, 1280px'
46
+ });
47
+ ```
48
+
49
+ ## Project-wide SvelteKit configuration (recommended)
50
+
51
+ Put an `ImgproxyProvider` in the root layout. Every nested `Image` inherits
52
+ the configuration, so components need only receive their image key. Unlike a
53
+ module-level singleton, this also stays isolated per SSR request.
54
+
55
+ ```svelte
56
+ <!-- src/routes/+layout.svelte -->
57
+ <script lang="ts">
58
+ import { ImgproxyProvider } from 'svelte-imgproxy';
59
+ import { PUBLIC_IMGPROXY_URL } from '$env/static/public';
60
+
61
+ let { children } = $props();
62
+ </script>
63
+
64
+ <ImgproxyProvider
65
+ config={{
66
+ baseUrl: PUBLIC_IMGPROXY_URL,
67
+ basePath: 'blog',
68
+ widths: [320, 640, 960, 1280, 1920]
69
+ }}
70
+ >
71
+ {@render children()}
72
+ </ImgproxyProvider>
73
+ ```
74
+
75
+ Use `configureImgproxy(...)` only for a static, app-wide configuration. It is
76
+ module-level state: it may be called from any eagerly imported module, but it
77
+ must run before `Image` renders and must never be changed per server request.
78
+
79
+ ## Use the component
80
+
81
+ ```svelte
82
+ <script lang="ts">
83
+ import { Image } from 'svelte-imgproxy';
84
+ </script>
85
+
86
+ <Image
87
+ image="hero.jpg"
88
+ alt="Hero"
89
+ class="hero-image"
90
+ />
91
+ ```
92
+
93
+ `Image` is the primary ergonomic export. `ImgproxyImage` and `ImgOptim` remain
94
+ available as aliases.
95
+
96
+ With `basePath: 'blog'`, `image="hero.jpg"` resolves to object key:
97
+
98
+ ```txt
99
+ blog/hero.jpg
100
+ ```
101
+
102
+ ### Per-image base path and opt-out
103
+
104
+ ```svelte
105
+ <Image basePath="portfolio" image="headshot.jpg" alt="Headshot" />
106
+ ```
107
+
108
+ To use an image without the configured base path:
109
+
110
+ ```svelte
111
+ <Image image="shared/banner.jpg" useBasePath={false} alt="Banner" />
112
+ ```
113
+
114
+ ### Full object key
115
+
116
+ Use `objectKey` when you do **not** want the configured base path added:
117
+
118
+ ```svelte
119
+ <ImgproxyImage objectKey="customer-x/banner.jpg" alt="Banner" />
120
+ ```
121
+
122
+ ### Dynamic URL/key
123
+
124
+ Use `url` for dynamic values:
125
+
126
+ ```svelte
127
+ <ImgproxyImage url={post.imageKey} alt={post.title} />
128
+ ```
129
+
130
+ If `url` is relative and a base path is configured, the base path is applied unless it already starts with that base path. Set `useBasePath={false}` to bypass it.
131
+
132
+ ### Custom widths
133
+
134
+ ```svelte
135
+ <ImgproxyImage
136
+ image="gallery/photo.jpg"
137
+ alt="Gallery photo"
138
+ widths={[400, 800, 1200, 1600]}
139
+ sizes="(max-width: 900px) 100vw, 900px"
140
+ />
141
+ ```
142
+
143
+ ### Rest props and classes
144
+
145
+ Most normal `<img>` props are forwarded:
146
+
147
+ ```svelte
148
+ <ImgproxyImage
149
+ image="logo.png"
150
+ alt="Logo"
151
+ class="logo"
152
+ style="height: auto; width: 12rem;"
153
+ fetchpriority="high"
154
+ />
155
+ ```
156
+
157
+ `loading` defaults to `lazy`; `decoding` defaults to `async`.
158
+
159
+ ### SvelteKit / SSR configuration
160
+
161
+ `configureImgproxy(...)` is convenient for a browser-only app or a deployment
162
+ with one shared image endpoint. In a multi-tenant SvelteKit app, avoid changing
163
+ that module-level configuration during a request: pass the configuration to the
164
+ component instead.
165
+
166
+ ```svelte
167
+ <Image
168
+ image="hero.jpg"
169
+ alt="Hero"
170
+ config={{ baseUrl: 'https://images.example.com', basePath: tenant.slug }}
171
+ />
172
+ ```
173
+
174
+ ## Helper-only usage
175
+
176
+ ```ts
177
+ import { buildImgproxyUrl, configureImgproxy } from 'svelte-imgproxy';
178
+
179
+ configureImgproxy({
180
+ baseUrl: 'https://images.example.com',
181
+ basePath: 'blog'
182
+ });
183
+
184
+ const url = buildImgproxyUrl({
185
+ source: 'blog/hero.jpg',
186
+ width: 960
187
+ });
188
+ ```
189
+
190
+ ## API exports
191
+
192
+ - `Image` (primary component export)
193
+ - `ImgproxyProvider` (project-wide configuration)
194
+ - `ImgproxyImage`
195
+ - `ImgOptim` alias
196
+ - `configureImgproxy(config)`
197
+ - `getImgproxyConfig()`
198
+ - `resetImgproxyConfig()`
199
+ - `buildImgproxyUrl(options)`
200
+ - `buildImgproxySrcset(options)`
201
+ - `resolveImgproxySource(input)`
202
+ - `DEFAULT_WIDTHS`
203
+ - `DEFAULT_SIZES`
204
+
205
+ ## Developing
206
+
207
+ ```sh
208
+ pnpm install
209
+ pnpm run dev
210
+ pnpm run check
211
+ pnpm run build
212
+ ```
213
+
214
+ This repo was created from the official Svelte library template via `sv create --template library`.
@@ -0,0 +1,129 @@
1
+ <script lang="ts">
2
+ import type { HTMLImgAttributes } from 'svelte/elements';
3
+ import {
4
+ buildImgproxySrcset,
5
+ buildImgproxyUrl,
6
+ getImgproxyConfigContext,
7
+ pickFallbackWidth,
8
+ resolveImgproxyConfig,
9
+ resolveImgproxySource
10
+ } from './imgproxy.js';
11
+ import type { ImgproxyConfigOverride } from './imgproxy.js';
12
+
13
+ type Props = Omit<HTMLImgAttributes, 'src' | 'srcset' | 'sizes'> & {
14
+ /** Image filename/key. Uses configured basePath by default, e.g. `hero.jpg` -> `blog/hero.jpg`. */
15
+ image?: string;
16
+
17
+ /** Dynamic source/key. Relative values can use configured basePath; absolute URLs are encoded for /plain/. */
18
+ url?: string;
19
+
20
+ /** Full object key, used as-is without adding the configured basePath. */
21
+ objectKey?: string;
22
+
23
+ /** Per-image base path override. */
24
+ basePath?: string;
25
+
26
+ /** Set false to bypass the configured or supplied basePath. */
27
+ useBasePath?: boolean;
28
+
29
+ /** Required accessible alt text. */
30
+ alt: string;
31
+
32
+ /** Responsive widths used to generate srcset. */
33
+ widths?: number[];
34
+
35
+ /** Width used for the fallback src. Defaults to the middle srcset width. */
36
+ fallbackWidth?: number;
37
+
38
+ /** Optional quality override, encoded as `q:<quality>`. */
39
+ quality?: number;
40
+
41
+ /** Optional output format, encoded as `@<format>`. */
42
+ format?: string;
43
+
44
+ /** Optional cache-buster processing option, encoded as `cb:<value>`. */
45
+ cacheBuster?: string;
46
+
47
+ /** Optional extra imgproxy processing options, appended after resize/quality/cachebuster. */
48
+ processingOptions?: string[];
49
+
50
+ /** Override configured imgproxy base URL. */
51
+ baseUrl?: string;
52
+
53
+ /** Per-component configuration; recommended when server-rendering. */
54
+ config?: ImgproxyConfigOverride;
55
+
56
+ /** Override configured signature segment. */
57
+ signature?: string;
58
+
59
+ /** Responsive image sizes attribute. */
60
+ sizes?: string;
61
+ };
62
+
63
+ let {
64
+ image,
65
+ url,
66
+ objectKey,
67
+ basePath,
68
+ useBasePath,
69
+ alt,
70
+ widths,
71
+ fallbackWidth,
72
+ quality,
73
+ format,
74
+ cacheBuster,
75
+ processingOptions,
76
+ baseUrl,
77
+ config: configOverride,
78
+ signature,
79
+ sizes,
80
+ class: className,
81
+ loading = 'lazy',
82
+ decoding = 'async',
83
+ ...rest
84
+ }: Props = $props();
85
+
86
+ const getProviderConfig = getImgproxyConfigContext();
87
+ const cfg = $derived(resolveImgproxyConfig({ ...getProviderConfig?.(), ...configOverride }));
88
+ const resolvedWidths = $derived(widths ?? cfg.widths ?? [320, 640, 960, 1280, 1920]);
89
+ const resolvedSizes = $derived(sizes ?? cfg.sizes ?? '(max-width: 1280px) 100vw, 1280px');
90
+ const source = $derived(resolveImgproxySource({ image, url, objectKey, basePath, useBasePath }, cfg));
91
+ const resolvedFallbackWidth = $derived(pickFallbackWidth(resolvedWidths, fallbackWidth));
92
+
93
+ const src = $derived(
94
+ buildImgproxyUrl({
95
+ source,
96
+ width: resolvedFallbackWidth,
97
+ baseUrl: baseUrl ?? cfg.baseUrl,
98
+ signature: signature ?? cfg.signature,
99
+ quality: quality ?? cfg.quality,
100
+ format: format ?? cfg.format,
101
+ cacheBuster,
102
+ processingOptions
103
+ })
104
+ );
105
+
106
+ const srcset = $derived(
107
+ buildImgproxySrcset({
108
+ source,
109
+ widths: resolvedWidths,
110
+ baseUrl: baseUrl ?? cfg.baseUrl,
111
+ signature: signature ?? cfg.signature,
112
+ quality: quality ?? cfg.quality,
113
+ format: format ?? cfg.format,
114
+ cacheBuster,
115
+ processingOptions
116
+ })
117
+ );
118
+ </script>
119
+
120
+ <img
121
+ {...rest}
122
+ class={className}
123
+ {src}
124
+ {srcset}
125
+ {alt}
126
+ {loading}
127
+ {decoding}
128
+ sizes={resolvedSizes}
129
+ />
@@ -0,0 +1,39 @@
1
+ import type { HTMLImgAttributes } from 'svelte/elements';
2
+ import type { ImgproxyConfigOverride } from './imgproxy.js';
3
+ type Props = Omit<HTMLImgAttributes, 'src' | 'srcset' | 'sizes'> & {
4
+ /** Image filename/key. Uses configured basePath by default, e.g. `hero.jpg` -> `blog/hero.jpg`. */
5
+ image?: string;
6
+ /** Dynamic source/key. Relative values can use configured basePath; absolute URLs are encoded for /plain/. */
7
+ url?: string;
8
+ /** Full object key, used as-is without adding the configured basePath. */
9
+ objectKey?: string;
10
+ /** Per-image base path override. */
11
+ basePath?: string;
12
+ /** Set false to bypass the configured or supplied basePath. */
13
+ useBasePath?: boolean;
14
+ /** Required accessible alt text. */
15
+ alt: string;
16
+ /** Responsive widths used to generate srcset. */
17
+ widths?: number[];
18
+ /** Width used for the fallback src. Defaults to the middle srcset width. */
19
+ fallbackWidth?: number;
20
+ /** Optional quality override, encoded as `q:<quality>`. */
21
+ quality?: number;
22
+ /** Optional output format, encoded as `@<format>`. */
23
+ format?: string;
24
+ /** Optional cache-buster processing option, encoded as `cb:<value>`. */
25
+ cacheBuster?: string;
26
+ /** Optional extra imgproxy processing options, appended after resize/quality/cachebuster. */
27
+ processingOptions?: string[];
28
+ /** Override configured imgproxy base URL. */
29
+ baseUrl?: string;
30
+ /** Per-component configuration; recommended when server-rendering. */
31
+ config?: ImgproxyConfigOverride;
32
+ /** Override configured signature segment. */
33
+ signature?: string;
34
+ /** Responsive image sizes attribute. */
35
+ sizes?: string;
36
+ };
37
+ declare const ImgproxyImage: import("svelte").Component<Props, {}, "">;
38
+ type ImgproxyImage = ReturnType<typeof ImgproxyImage>;
39
+ export default ImgproxyImage;
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { setImgproxyConfigContext } from './imgproxy.js';
4
+ import type { ImgproxyConfigOverride } from './imgproxy.js';
5
+
6
+ type Props = {
7
+ /** Configuration inherited by all nested Image components. */
8
+ config: ImgproxyConfigOverride;
9
+ children: Snippet;
10
+ };
11
+
12
+ let { config, children }: Props = $props();
13
+
14
+ setImgproxyConfigContext(() => config);
15
+ </script>
16
+
17
+ {@render children()}
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { ImgproxyConfigOverride } from './imgproxy.js';
3
+ type Props = {
4
+ /** Configuration inherited by all nested Image components. */
5
+ config: ImgproxyConfigOverride;
6
+ children: Snippet;
7
+ };
8
+ declare const ImgproxyProvider: import("svelte").Component<Props, {}, "">;
9
+ type ImgproxyProvider = ReturnType<typeof ImgproxyProvider>;
10
+ export default ImgproxyProvider;
@@ -0,0 +1,60 @@
1
+ export declare const DEFAULT_WIDTHS: readonly [320, 640, 960, 1280, 1920];
2
+ export declare const DEFAULT_SIZES = "(max-width: 1280px) 100vw, 1280px";
3
+ export type ImgproxyConfig = {
4
+ /** Public imgproxy endpoint, e.g. https://images.example.com */
5
+ baseUrl: string;
6
+ /** Default source-key prefix, e.g. blog, uploads, customer-x. */
7
+ basePath?: string;
8
+ /** imgproxy signature segment. Defaults to unsafe. */
9
+ signature?: string;
10
+ /** Default responsive widths for srcset. */
11
+ widths?: number[];
12
+ /** Default sizes attribute for responsive images. */
13
+ sizes?: string;
14
+ /** Default quality processing option. */
15
+ quality?: number;
16
+ /** Optional default output format, e.g. webp, avif, jpg, png. */
17
+ format?: string;
18
+ };
19
+ export type ImgproxySourceInput = {
20
+ /** Image filename/key. If a basePath is configured, this becomes `${basePath}/${image}`. */
21
+ image?: string;
22
+ /** Dynamic source string. Relative values can still use the configured basePath. */
23
+ url?: string;
24
+ /** Full object key, used as-is without adding the configured basePath. */
25
+ objectKey?: string;
26
+ /** Per-image base path override. */
27
+ basePath?: string;
28
+ /** Set false to bypass the configured or supplied basePath. */
29
+ useBasePath?: boolean;
30
+ };
31
+ /** Configuration accepted by an individual component or helper call. */
32
+ export type ImgproxyConfigOverride = Partial<ImgproxyConfig>;
33
+ /** Sets a reactive configuration getter for descendant Image components. */
34
+ export declare function setImgproxyConfigContext(getConfig: () => ImgproxyConfigOverride): void;
35
+ /** Reads the nearest ImgproxyProvider configuration, if there is one. */
36
+ export declare function getImgproxyConfigContext(): (() => ImgproxyConfigOverride) | undefined;
37
+ export type BuildImgproxyUrlOptions = {
38
+ source: string;
39
+ width: number;
40
+ baseUrl?: string;
41
+ signature?: string;
42
+ quality?: number;
43
+ format?: string;
44
+ cacheBuster?: string;
45
+ processingOptions?: string[];
46
+ };
47
+ export declare function configureImgproxy(nextConfig: ImgproxyConfig): void;
48
+ export declare function getImgproxyConfig(): Readonly<ImgproxyConfig>;
49
+ export declare function resetImgproxyConfig(): void;
50
+ export declare function resolveImgproxySource(input: ImgproxySourceInput, cfg?: ImgproxyConfig): string;
51
+ /**
52
+ * Combines a per-call override with configured defaults. This is useful for
53
+ * SSR, where module-level configuration could otherwise be shared by requests.
54
+ */
55
+ export declare function resolveImgproxyConfig(override?: ImgproxyConfigOverride): ImgproxyConfig;
56
+ export declare function buildImgproxyUrl(options: BuildImgproxyUrlOptions): string;
57
+ export declare function buildImgproxySrcset(options: Omit<BuildImgproxyUrlOptions, 'width'> & {
58
+ widths: readonly number[];
59
+ }): string;
60
+ export declare function pickFallbackWidth(widths: readonly number[], fallbackWidth?: number): number;
@@ -0,0 +1,126 @@
1
+ import { getContext, hasContext, setContext } from 'svelte';
2
+ export const DEFAULT_WIDTHS = [320, 640, 960, 1280, 1920];
3
+ export const DEFAULT_SIZES = '(max-width: 1280px) 100vw, 1280px';
4
+ const IMGPROXY_CONFIG_CONTEXT = Symbol('svelte-imgproxy.config');
5
+ /** Sets a reactive configuration getter for descendant Image components. */
6
+ export function setImgproxyConfigContext(getConfig) {
7
+ setContext(IMGPROXY_CONFIG_CONTEXT, getConfig);
8
+ }
9
+ /** Reads the nearest ImgproxyProvider configuration, if there is one. */
10
+ export function getImgproxyConfigContext() {
11
+ return hasContext(IMGPROXY_CONFIG_CONTEXT)
12
+ ? getContext(IMGPROXY_CONFIG_CONTEXT)
13
+ : undefined;
14
+ }
15
+ const defaultConfig = {
16
+ baseUrl: '',
17
+ signature: 'unsafe',
18
+ widths: [...DEFAULT_WIDTHS],
19
+ sizes: DEFAULT_SIZES
20
+ };
21
+ let config = { ...defaultConfig };
22
+ export function configureImgproxy(nextConfig) {
23
+ config = {
24
+ ...config,
25
+ ...nextConfig,
26
+ baseUrl: stripTrailingSlash(nextConfig.baseUrl ?? config.baseUrl)
27
+ };
28
+ }
29
+ export function getImgproxyConfig() {
30
+ return config;
31
+ }
32
+ export function resetImgproxyConfig() {
33
+ config = { ...defaultConfig, widths: [...DEFAULT_WIDTHS] };
34
+ }
35
+ export function resolveImgproxySource(input, cfg = config) {
36
+ const suppliedSources = [input.image, input.url, input.objectKey].filter((source) => Boolean(source));
37
+ if (suppliedSources.length !== 1) {
38
+ throw new Error('svelte-imgproxy: provide exactly one of `image`, `url`, or `objectKey`.');
39
+ }
40
+ const source = suppliedSources[0];
41
+ const cleanSource = trimLeadingSlash(source);
42
+ if (input.objectKey || isAbsoluteSource(cleanSource)) {
43
+ return cleanSource;
44
+ }
45
+ if (input.useBasePath === false) {
46
+ return cleanSource;
47
+ }
48
+ const basePath = input.basePath ?? cfg.basePath;
49
+ if (!basePath) {
50
+ return cleanSource;
51
+ }
52
+ const cleanBasePath = trimSlashes(basePath);
53
+ // If the caller already passed a key with this base path, do not double-prefix.
54
+ if (cleanSource === cleanBasePath || cleanSource.startsWith(`${cleanBasePath}/`)) {
55
+ return cleanSource;
56
+ }
57
+ return `${cleanBasePath}/${cleanSource}`;
58
+ }
59
+ /**
60
+ * Combines a per-call override with configured defaults. This is useful for
61
+ * SSR, where module-level configuration could otherwise be shared by requests.
62
+ */
63
+ export function resolveImgproxyConfig(override = {}) {
64
+ return {
65
+ ...config,
66
+ ...override,
67
+ baseUrl: stripTrailingSlash(override.baseUrl ?? config.baseUrl),
68
+ widths: override.widths ?? config.widths,
69
+ sizes: override.sizes ?? config.sizes
70
+ };
71
+ }
72
+ export function buildImgproxyUrl(options) {
73
+ const cfg = getImgproxyConfig();
74
+ const baseUrl = stripTrailingSlash(options.baseUrl ?? cfg.baseUrl);
75
+ const signature = options.signature ?? cfg.signature ?? 'unsafe';
76
+ const quality = options.quality ?? cfg.quality;
77
+ const format = options.format ?? cfg.format;
78
+ if (!baseUrl) {
79
+ throw new Error('svelte-imgproxy: missing imgproxy `baseUrl`. Call configureImgproxy({ baseUrl }) first, or pass `baseUrl` to the component.');
80
+ }
81
+ const processingOptions = [
82
+ `rs:fit:${options.width}:0`,
83
+ ...(quality ? [`q:${quality}`] : []),
84
+ ...(options.cacheBuster ? [`cb:${encodeURIComponent(options.cacheBuster)}`] : []),
85
+ ...(options.processingOptions ?? [])
86
+ ];
87
+ const source = encodePlainSource(options.source);
88
+ const extension = format ? `@${encodeURIComponent(format)}` : '';
89
+ return `${baseUrl}/${signature}/${processingOptions.join('/')}/plain/${source}${extension}`;
90
+ }
91
+ export function buildImgproxySrcset(options) {
92
+ return options.widths
93
+ .filter(isValidWidth)
94
+ .map((width) => `${buildImgproxyUrl({ ...options, width })} ${width}w`)
95
+ .join(', ');
96
+ }
97
+ export function pickFallbackWidth(widths, fallbackWidth) {
98
+ if (fallbackWidth && isValidWidth(fallbackWidth))
99
+ return fallbackWidth;
100
+ const validWidths = widths.filter(isValidWidth);
101
+ return validWidths[Math.floor(validWidths.length / 2)] ?? 640;
102
+ }
103
+ function encodePlainSource(source) {
104
+ // For object keys with server-side IMGPROXY_BASE_URL, keep slashes readable:
105
+ // /plain/blog/my%20image.jpg
106
+ // For full URLs, encode the whole URL so ?, # and @ cannot break imgproxy parsing.
107
+ if (isAbsoluteSource(source)) {
108
+ return encodeURIComponent(source);
109
+ }
110
+ return trimLeadingSlash(source).split('/').map(encodeURIComponent).join('/');
111
+ }
112
+ function isAbsoluteSource(source) {
113
+ return /^[a-z][a-z\d+.-]*:\/\//i.test(source);
114
+ }
115
+ function isValidWidth(width) {
116
+ return Number.isFinite(width) && width > 0;
117
+ }
118
+ function stripTrailingSlash(value) {
119
+ return value.replace(/\/+$/, '');
120
+ }
121
+ function trimLeadingSlash(value) {
122
+ return value.replace(/^\/+/, '');
123
+ }
124
+ function trimSlashes(value) {
125
+ return value.replace(/^\/+|\/+$/g, '');
126
+ }
@@ -0,0 +1,6 @@
1
+ export { default as ImgproxyImage } from './ImgproxyImage.svelte';
2
+ export { default as ImgOptim } from './ImgproxyImage.svelte';
3
+ export { default as Image } from './ImgproxyImage.svelte';
4
+ export { default as ImgproxyProvider } from './ImgproxyProvider.svelte';
5
+ export { DEFAULT_SIZES, DEFAULT_WIDTHS, buildImgproxySrcset, buildImgproxyUrl, configureImgproxy, getImgproxyConfig, getImgproxyConfigContext, pickFallbackWidth, resetImgproxyConfig, resolveImgproxyConfig, resolveImgproxySource, setImgproxyConfigContext } from './imgproxy.js';
6
+ export type { BuildImgproxyUrlOptions, ImgproxyConfig, ImgproxyConfigOverride, ImgproxySourceInput } from './imgproxy.js';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { default as ImgproxyImage } from './ImgproxyImage.svelte';
2
+ export { default as ImgOptim } from './ImgproxyImage.svelte';
3
+ export { default as Image } from './ImgproxyImage.svelte';
4
+ export { default as ImgproxyProvider } from './ImgproxyProvider.svelte';
5
+ export { DEFAULT_SIZES, DEFAULT_WIDTHS, buildImgproxySrcset, buildImgproxyUrl, configureImgproxy, getImgproxyConfig, getImgproxyConfigContext, pickFallbackWidth, resetImgproxyConfig, resolveImgproxyConfig, resolveImgproxySource, setImgproxyConfigContext } from './imgproxy.js';
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@sandquist-group/svelte-imgproxy",
3
+ "version": "0.1.0",
4
+ "description": "Responsive Svelte 5 image component and URL helpers for imgproxy.",
5
+ "author": "Sandquist Group",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "engines": {
11
+ "node": ">=20"
12
+ },
13
+ "scripts": {
14
+ "dev": "vite dev",
15
+ "build": "vite build && npm run prepack",
16
+ "preview": "vite preview",
17
+ "prepare": "svelte-kit sync || echo ''",
18
+ "prepack": "svelte-kit sync && svelte-package && publint",
19
+ "prepublishOnly": "pnpm run check && pnpm run build",
20
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
21
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "!dist/**/*.test.*",
26
+ "!dist/**/*.spec.*"
27
+ ],
28
+ "sideEffects": [
29
+ "**/*.css"
30
+ ],
31
+ "svelte": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "type": "module",
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.ts",
37
+ "svelte": "./dist/index.js"
38
+ }
39
+ },
40
+ "peerDependencies": {
41
+ "svelte": "^5.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@sveltejs/adapter-auto": "^7.0.1",
45
+ "@sveltejs/kit": "^2.63.0",
46
+ "@sveltejs/package": "^2.5.8",
47
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
48
+ "publint": "^0.3.21",
49
+ "svelte": "^5.56.1",
50
+ "svelte-check": "^4.6.0",
51
+ "typescript": "^6.0.3",
52
+ "vite": "^8.0.16"
53
+ },
54
+ "keywords": [
55
+ "svelte",
56
+ "sveltekit",
57
+ "imgproxy",
58
+ "image-optimization",
59
+ "responsive-images",
60
+ "image-component"
61
+ ]
62
+ }