Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
fi

- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ steps.resolve-ref.outputs.ref }}
fetch-depth: 0
Expand All @@ -57,12 +57,10 @@ jobs:
echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT"

- name: Setup pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: 11.10.0
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0

- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: pnpm
Expand Down
8 changes: 3 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,15 @@ jobs:
id-token: write
steps:
- name: Checkout Repo
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- name: Setup pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: 11.10.0
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0

- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: pnpm
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ Framework defaults solve images inside one framework. DeSource Image handles ima
- **Built for fast product iterations.** Change image quality, format, crop, or responsive sizes in code. Source files and filenames stay unchanged.
- **One source instead of exported variants.** Start with one suitable image and generate the widths and formats each screen needs.
- **Deployment-aware provider selection.** Leave `provider` on `auto`. DeSource Image detects Vercel, Netlify, or AWS Amplify and uses IPX for local or other environments.
- **Simplest way to deal with image providers.** CMS URLs need aliases, validation, and provider-specific modifiers. DeSource Image handles provider selection, modifier translation, and URL generation for 46 built-in providers.
- **Simplest way to deal with image providers.** CMS URLs need aliases, validation, and provider-specific modifiers. DeSource Image handles provider selection, modifier translation, and URL generation for built-in providers.
- **Native framework APIs.** Use Angular components and directives, React components and hooks, or Svelte components, actions, and attachments. Output remains native `<img>` and `<picture>` markup.
- **Built-in local optimizer.** React/Vite, Next.js, Angular SSR, and SvelteKit adapters can serve IPX transformations without a separate image service.
- **46 provider modules.** Use Cloudinary, Imgix, ImageKit, Sanity, Contentful, Shopify, Vercel, Netlify, or another built-in provider. Provider subpath imports remain **tree-shakable**.
- **Dozens of built-in provider modules.** Use Cloudinary, Imgix, ImageKit, Sanity, Contentful, Shopify, Vercel, Netlify, or another built-in provider. Provider subpath imports remain **tree-shakable**.
- **Responsive images and modern formats.** Generate width or density candidates, breakpoint-aware `sizes`, ordered AVIF/WebP sources, and a fallback image from one input.
- **Placeholders and LCP controls.** Generate low-resolution placeholders, preload links, loading hints, and fetch priority without hand-maintained head tags.
- **SSR-friendly.** The same inputs generate the same URLs and attributes on the server and client, so hydration does not rewrite image markup.
Expand All @@ -66,7 +66,7 @@ After adopting the package for your framework, image components need no host-spe
| [`@sveltejs/enhanced-img`](https://svelte.dev/docs/kit/images) | Static local assets transformed during the Vite build | Images are processed at build time. The deployment target does not switch them to its runtime image service. | You want local images transformed on demand to save your time. Your images arrive from a CMS, database, API, object store, or CDN, or the same source should use IPX locally and the deployment platform’s optimizer in production. |
| [Unpic](https://unpic.pics/) | Cross-framework responsive images already hosted on recognizable CDN or CMS URLs | Detects the provider from each `src` URL. Local or unknown sources need a fallback or explicit provider; the deployment itself is not the selection signal. | Vercel, Netlify, or AWS Amplify should choose the optimizer for every source, including relative paths, and you also need presets, aliases, source rules, `<picture>`, or server adapters. |

DeSource Image combines deployment-aware selection and 46 provider modules under one configuration for React/Next.js, Angular, and Svelte/SvelteKit. Nuxt applications should continue to use [`@nuxt/image`](https://image.nuxt.com/).
DeSource Image combines deployment-aware selection and built-in provider modules under one configuration for React/Next.js, Angular, and Svelte/SvelteKit. Nuxt applications should continue to use [`@nuxt/image`](https://image.nuxt.com/).

## Packages

Expand Down Expand Up @@ -252,7 +252,7 @@ const picture = image.getPicture({
- Picture output: ordered AVIF/WebP/etc. `<source>` elements plus a fallback `<img>`.
- Placeholders: generated low-resolution provider URLs, custom URLs, custom `[width, height, quality, blur]` tuples, decode-before-swap behavior, and temporary classes.
- Head preloads: responsive `<link rel="preload" as="image">` generation with reference counting in framework packages.
- Providers: 46 built-in modules, a small default registry, tree-shakable subpath imports, and typed custom providers.
- Providers: built-in modules, a small default registry, tree-shakable subpath imports, and typed custom providers.
- Presets and aliases: reusable image defaults and clean source aliases for CMS or asset hosts.
- Source controls: `domains`, `localPatterns`, `remotePatterns`, and invalid-source policies.
- Server adapters: IPX middleware for Angular SSR, React/Vite, Next.js App Router, SvelteKit, Fetch API servers, and Connect/Express-style Node servers.
Expand Down Expand Up @@ -347,7 +347,7 @@ The default registry is intentionally small:
- `netlifyLargeMedia`
- `none`

Import all 46 provider modules only when needed:
Import all built-in provider modules only when needed:

```ts
import { BUILT_IN_PROVIDER_NAMES, createBuiltInProviders } from '@desource/image/providers';
Expand All @@ -363,7 +363,7 @@ import { sanityProvider } from '@desource/image/providers/sanity';

Supported provider modules:

`aliyun`, `awsAmplify`, `builderio`, `bunny`, `caisy`, `cloudflare`, `cloudflareimages`, `cloudimage`, `cloudinary`, `contentful`, `directus`, `edgeonePages`, `fastly`, `filerobot`, `flyimg`, `github`, `glide`, `gumlet`, `hygraph`, `imageengine`, `imagekit`, `imgix`, `imgproxy`, `ipx`, `ipxStatic`, `netlify`, `netlifyImageCdn`, `netlifyLargeMedia`, `none`, `picsum`, `prepr`, `prismic`, `sanity`, `shopify`, `sirv`, `storyblok`, `strapi`, `strapi5`, `supabase`, `twicpics`, `umbraco`, `unsplash`, `uploadcare`, `vercel`, `wagtail`, and `weserv`.
`aliyun`, `awsAmplify`, `builderio`, `bunny`, `caisy`, `cloudflare`, `cloudflareimages`, `cloudimage`, `cloudinary`, `contentful`, `directus`, `edgeonePages`, `fastly`, `filerobot`, `flyimg`, `github`, `glide`, `gumlet`, `hygraph`, `imageengine`, `imagekit`, `imgix`, `imgproxy`, `ipx`, `ipxStatic`, `keycdn`, `netlify`, `netlifyImageCdn`, `netlifyLargeMedia`, `none`, `picsum`, `prepr`, `prismic`, `sanity`, `shopify`, `sirv`, `storyblok`, `strapi`, `strapi5`, `supabase`, `twicpics`, `umbraco`, `unsplash`, `uploadcare`, `vercel`, `wagtail`, and `weserv`.

## Local optimization

Expand Down
8 changes: 4 additions & 4 deletions demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
},
"devDependencies": {
"@sveltejs/adapter-vercel": "^6.3.4",
"@sveltejs/kit": "^2.70.2",
"@sveltejs/kit": "^2.70.3",
"@sveltejs/vite-plugin-svelte": "^7.3.0",
"sass": "^1.102.0",
"svelte": "^5.56.9",
"svelte-check": "^4.7.5"
"sass": "^1.104.1",
"svelte": "^5.57.0",
"svelte-check": "^4.7.6"
}
}
1 change: 0 additions & 1 deletion demo/src/lib/Gallery.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,6 @@
alt={item.alt}
draggable={false}
loading="lazy"
fetchpriority="low"
format="webp"
width={60}
height={60}
Expand Down
3 changes: 2 additions & 1 deletion demo/src/lib/SiteFooter.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { resolve } from '$app/paths';
import { DsImage } from '@desource/image-svelte';
import { providerCount } from '$lib/provider-count';

const year = new Date().getUTCFullYear();
</script>
Expand All @@ -18,7 +19,7 @@
<small>Open image tooling</small>
</span>
</a>
<p>One image API for React, Angular, and Svelte, backed by 46 provider modules.</p>
<p>One image API for React, Angular, and Svelte, backed by {providerCount} provider modules.</p>
<span class="license"><i aria-hidden="true"></i> MIT licensed</span>
</div>

Expand Down
6 changes: 5 additions & 1 deletion demo/src/lib/home/BenefitsSection.svelte
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<script lang="ts">
import { providerCount } from '$lib/provider-count';
</script>

<section class="trust-strip" aria-label="Core capabilities">
<div class="shell">
<span>Angular 19-22</span>
Expand Down Expand Up @@ -26,7 +30,7 @@
<p>Leave provider on auto. DeSource Image detects Vercel, Netlify, or AWS Amplify and uses IPX elsewhere.</p>
</article>
<article>
<h3>46 provider modules</h3>
<h3>{providerCount} provider modules</h3>
<p>Use Cloudinary, Imgix, ImageKit, Sanity, Contentful, Shopify, or another built-in provider.</p>
</article>
<article>
Expand Down
3 changes: 2 additions & 1 deletion demo/src/lib/home/CtaSection.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import { providerCount } from '$lib/provider-count';
import RayField from '$lib/RayField.svelte';
</script>

Expand Down Expand Up @@ -46,7 +47,7 @@
<div><small>delivery</small><strong>CDN</strong></div>
</div>
<div class="output-foot">
<span>46 providers</span>
<span>{providerCount} providers</span>
<span>3 frameworks</span>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion demo/src/lib/home/HeroSection.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { DsPicture } from '@desource/image-svelte';
import RayField from '$lib/RayField.svelte';
import { providerCount } from '$lib/provider-count';

function updatePerspective(event: PointerEvent) {
if (event.pointerType === 'touch') return;
Expand Down Expand Up @@ -43,7 +44,7 @@
<dd>deployment provider</dd>
</div>
<div>
<dt>46</dt>
<dt>{providerCount}</dt>
<dd>provider modules</dd>
</div>
<div>
Expand Down
5 changes: 3 additions & 2 deletions demo/src/lib/home/ProvidersSection.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { resolve } from '$app/paths';
import Gallery from '$lib/Gallery.svelte';
import { providerCount } from '$lib/provider-count';
import { providers } from '$lib/providers';

const providerImages = providers.map((provider) => ({
Expand All @@ -13,7 +14,7 @@
<section class="providers shell" id="providers">
<div class="provider-heading">
<div>
<p class="eyebrow">46 built-in provider modules</p>
<p class="eyebrow">{providerCount} built-in provider modules</p>
<h2>Use the CDN, CMS, or image service already in your stack.</h2>
</div>
<p class="section-copy">
Expand All @@ -33,7 +34,7 @@
<span aria-hidden="true">Read guide →</span>
</a>
<details class="provider-directory animated-details">
<summary>Browse all {providers.length} providers</summary>
<summary>Browse all {providerCount} providers</summary>
<ul>
{#each providers as provider (provider.slug)}<li>
<a href={resolve('/providers/[provider]', { provider: provider.slug })}>{provider.name}</a>
Expand Down
3 changes: 3 additions & 0 deletions demo/src/lib/provider-count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { BUILT_IN_PROVIDER_NAMES } from '@desource/image/providers';

export const providerCount = BUILT_IN_PROVIDER_NAMES.length;
1 change: 1 addition & 0 deletions demo/src/lib/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const providerNames = {
imgproxy: 'imgproxy',
ipx: 'IPX',
ipxStatic: 'IPX Static',
keycdn: 'KeyCDN',
netlify: 'Netlify',
netlifyImageCdn: 'Netlify Image CDN',
netlifyLargeMedia: 'Netlify Large Media',
Expand Down
33 changes: 33 additions & 0 deletions demo/src/lib/server/provider-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,39 @@ export const providerDocs: Record<ProviderId, ProviderDoc> = {
},
extra: []
},
keycdn: {
description: 'Transform images served by a KeyCDN Zone with Image Processing enabled.',
options: [
{
name: 'baseURL',
value: 'https://your-zone.kxcdn.com',
description: 'Your Zone URL or custom delivery domain. Optional when src is an absolute KeyCDN delivery URL.'
}
],
src: '/photo.jpg',
source:
'Use a path served by the configured Zone, or a full delivery URL. Existing paths, query parameters, and fragments are preserved; explicit modifiers replace matching parameters.',
modifiers:
'Supports width, height, quality, format, fit, position, and background (mapped to bg). Fit values are cover, contain, fill, inside, and outside. Position supports top, right, bottom, and left; center clears the position parameter to use the service default. Native operations such as blur, crop, and enlarge can be passed as modifiers; boolean flags become 0 or 1.',
notes: [
'Enable Image Processing on the Pull or Push Zone. A Pull Zone must include query strings in its cache key; enabling Image Processing disables Ignore Query String automatically. Keep Cache Key Scheme disabled as documented by KeyCDN.',
'Supported explicit formats are jpeg, jpg, png, and webp; jpg is sent as jpeg. Unsupported formats, including avif and auto, throw an error. Configure picture formats to use webp with a jpeg or png fallback.',
'Quality applies to JPEG and WebP. Width and height must remain within the service limit of 10000 pixels, including responsive density variants.',
'Background accepts hex with or without #, RGB/RGBA comma-separated values, or transparent. Three-digit hex colors are expanded to six digits.',
'Secure Token signing is not generated by this adapter. Sign each final transformed URL on the server when the Zone requires it; an existing token does not automatically authorize new transformations.'
],
reference: 'https://www.keycdn.com/support/image-processing',
factory: 'keycdnProvider',
example: {
width: 800,
height: 500,
quality: 80,
format: 'webp',
fit: 'contain',
background: '#ffffff'
},
extra: []
},
gumlet: {
description: 'Generate transformations for a configured Gumlet image source.',
options: [
Expand Down
3 changes: 2 additions & 1 deletion demo/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import PlaygroundSection from '$lib/home/PlaygroundSection.svelte';
import ProvidersSection from '$lib/home/ProvidersSection.svelte';
import SiteHeader from '$lib/home/SiteHeader.svelte';
import { providerCount } from '$lib/provider-count';
import type { PageData } from './$types';

let { data }: { data: PageData } = $props();
Expand Down Expand Up @@ -37,7 +38,7 @@
<meta name="twitter:title" content="DeSource Image - Optimized images for React, Angular, and Svelte" />
<meta
name="twitter:description"
content="Responsive images, modern formats, deployment-aware provider selection, 46 providers, and local IPX."
content="Responsive images, modern formats, deployment-aware provider selection, {providerCount} providers, and local IPX."
/>
<meta name="twitter:image" content={`${data.origin}/og.jpg`} />
<meta name="twitter:image:alt" content="DeSource Image optimization for React, Angular, and Svelte" />
Expand Down
Binary file added demo/static/providers/keycdn.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 17 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"type": "module",
"packageManager": "pnpm@11.10.0",
"packageManager": "pnpm@12.4.1",
"engines": {
"node": ">=22.18.0"
},
Expand Down Expand Up @@ -43,32 +43,32 @@
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.5",
"@changesets/changelog-github": "^1.0.0",
"@changesets/cli": "^3.0.0",
"@changesets/changelog-github": "^1.0.1",
"@changesets/cli": "^3.0.2",
"@eslint/js": "^10.0.1",
"@nuxt/image": "^2.1.0",
"@playwright/test": "^1.62.1",
"@types/node": "^26.2.0",
"@typescript-eslint/eslint-plugin": "^8.67.0",
"@typescript-eslint/parser": "^8.67.0",
"@vitest/coverage-v8": "^4.1.10",
"angular-eslint": "^22.1.0",
"eslint": "^10.8.1",
"@playwright/test": "^1.63.0",
"@types/node": "^26.5.1",
"@typescript-eslint/eslint-plugin": "^8.70.0",
"@typescript-eslint/parser": "^8.70.0",
"@vitest/coverage-v8": "^4.1.11",
"angular-eslint": "^22.5.0",
"eslint": "^10.10.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-svelte": "^3.23.0",
"globals": "^17.11.0",
"globals": "^17.12.0",
"husky": "^9.1.7",
"jsdom": "^29.1.1",
"lint-staged": "^17.3.0",
"jsdom": "^30.0.1",
"lint-staged": "^17.5.1",
"prettier": "^3.9.6",
"prettier-plugin-svelte": "^4.1.1",
"publint": "^0.3.23",
"publint": "^0.3.24",
"rimraf": "^6.1.3",
"svelte-eslint-parser": "^1.8.0",
"svelte-eslint-parser": "^1.8.1",
"typescript": "~6.0.3",
"vite": "^8.2.1",
"vitest": "^4.1.10"
"vite": "^8.3.0",
"vitest": "^4.1.11"
},
"lint-staged": {
"*.{svelte,vue,js,jsx,cjs,mjs,ts,tsx,cts,mts}": [
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

- Initial release of DeSource Image:
- Generates responsive `srcset`, native `<picture>` output, placeholders, and preload metadata from one local or remote image.
- Shares typed configuration, presets, aliases, source validation, and 46 built-in providers with tree-shakable subpath imports.
- Shares typed configuration, presets, aliases, source validation, and dozens of built-in providers with tree-shakable subpath imports.
- Detects Vercel, Netlify, or AWS Amplify from the deployment environment, with explicit provider overrides and an IPX fallback.
- Exposes native framework APIs through Angular components, directives, a service, and an `NgOptimizedImage` loader; React components, hooks, and a `next/image` loader; and Svelte components, actions, and attachments.
- Generates the same URLs and attributes on the server and client so hydration does not rewrite image markup.
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Angular's `NgOptimizedImage` remains a strong built-in choice for performance ch
- **Deployment-aware provider selection.** Leave `provider` on `auto`. DeSource Image detects Vercel, Netlify, or AWS Amplify and uses IPX for local or other environments.
- **`<picture>` is a first-class API.** Components and directives generate ordered AVIF/WebP sources with fallback control.
- **Native framework APIs.** Use standalone components and native-element directives. Output remains native `<img>` and `<picture>` markup.
- **46 built-in provider modules.** Import one provider subpath, use the complete registry, or register a typed custom provider.
- **Dozens of built-in provider modules.** Import one provider subpath, use the complete registry, or register a typed custom provider.
- **Built-in local optimizer.** Angular SSR middleware can serve IPX transformations without a separate image service.
- **`NgOptimizedImage` still works.** `provideDsImage()` registers DeSource Image as Angular's `IMAGE_LOADER`.

Expand Down
Loading