From 8ca389f1303976a83bff5e2cba32ab40ac52b168 Mon Sep 17 00:00:00 2001 From: Vasanthakumar RB Date: Mon, 3 Aug 2026 00:26:21 +0530 Subject: [PATCH 1/2] feat(cdn): add Autorender image CDN provider Add an Autorender (autorender.io) provider to @responsive-image/cdn, mirroring the existing cloudinary provider. Autorender is a URL-based image CDN with path transforms (/{workspace}/w_1600,f_webp/path.jpg), so it maps onto the existing ImageData contract with no core changes. - Config: { domain, workspace } - Options: formats, quality, aspectRatio, and a transforms[] escape hatch - Width -> w_, format -> f_, quality -> q_ - Remote http(s) sources become a fetch_ token (Cloudinary fetch parity) - Default imageTypes 'auto', matching the other providers - Docs page + sidebar entry, vitest suite with exact-URL assertions, changeset --- .changeset/autorender-cdn-provider.md | 5 + apps/docs/.vitepress/config.ts | 1 + apps/docs/src/cdn/autorender.md | 170 ++++++++++++++++++++++++++ apps/docs/src/cdn/index.md | 1 + packages/cdn/src/autorender.ts | 101 +++++++++++++++ packages/cdn/src/index.ts | 1 + packages/cdn/src/types.ts | 2 + packages/cdn/tests/autorender.test.ts | 108 ++++++++++++++++ 8 files changed, 389 insertions(+) create mode 100644 .changeset/autorender-cdn-provider.md create mode 100644 apps/docs/src/cdn/autorender.md create mode 100644 packages/cdn/src/autorender.ts create mode 100644 packages/cdn/tests/autorender.test.ts diff --git a/.changeset/autorender-cdn-provider.md b/.changeset/autorender-cdn-provider.md new file mode 100644 index 000000000..8734d7c0f --- /dev/null +++ b/.changeset/autorender-cdn-provider.md @@ -0,0 +1,5 @@ +--- +'@responsive-image/cdn': minor +--- + +Add Autorender image CDN provider diff --git a/apps/docs/.vitepress/config.ts b/apps/docs/.vitepress/config.ts index d730c925b..94f74d53a 100644 --- a/apps/docs/.vitepress/config.ts +++ b/apps/docs/.vitepress/config.ts @@ -114,6 +114,7 @@ export default defineConfig({ link: '/', base: '/cdn', items: [ + { text: 'Autorender', link: '/autorender' }, { text: 'Cloudinary', link: '/cloudinary' }, { text: 'Fastly', link: '/fastly' }, { text: 'Imgix', link: '/imgix' }, diff --git a/apps/docs/src/cdn/autorender.md b/apps/docs/src/cdn/autorender.md new file mode 100644 index 000000000..5a5a7959a --- /dev/null +++ b/apps/docs/src/cdn/autorender.md @@ -0,0 +1,170 @@ +--- +outline: [2, 3] +--- + +# Autorender + +The image processing capabilities of the [Autorender](https://autorender.io) image CDN are supported by a helper function provided to you by this library. + +## Setup + +Make sure you have the `@responsive-image/cdn` package installed: + +::: code-group + +```bash [npm] +npm install @responsive-image/cdn +``` + +```bash [yarn] +yarn add @responsive-image/cdn +``` + +```bash [pnpm] +pnpm add @responsive-image/cdn +``` + +::: + +You need to specify your Autorender `domain` and `workspace` in your configuration, which you can set up in your application (e.g. `app.js`). The workspace ID is the public routing identifier in your delivery URLs and is not a secret: + +```js +import { setConfig } from '@responsive-image/core'; + +setConfig('cdn', { + autorender: { + domain: 'assets.autorender.io', + workspace: 'LOKVTtKVGb', + }, +}); +``` + +## Usage + +> [!IMPORTANT] +> Please make sure you have read the section on [remote images](../usage/remote-images.md) first. + +Use the autorender provider function passing the source path of the image inside your workspace, and pass the return value to the [image component](../usage/component.md): + +::: code-group + +```gjs [Ember .gjs] +import { ResponsiveImage } from '@responsive-image/ember'; +import { autorender } from '@responsive-image/cdn'; + + +``` + +```hbs [Ember .hbs] + +``` + +```ts [Lit] +import { LitElement, html } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import { autorender } from '@responsive-image/cdn'; +import '@responsive-image/wc'; + +@customElement('my-app') +export class MyApp extends LitElement { + render() { + return html``; + } +} +``` + +```tsx [React] +import { ResponsiveImage } from '@responsive-image/react'; +import { autorender } from '@responsive-image/cdn'; + +export default function MyApp() { + return ; +} +``` + +```tsx [Solid] +import { ResponsiveImage } from '@responsive-image/solid'; +import { autorender } from '@responsive-image/cdn'; + +export default function MyApp() { + return ; +} +``` + +```svelte [Svelte] + + + +``` + +```vue [Vue] + + + +``` + +::: + +### Aspect Ratio + +For the image component to be able to render `width` and `height` attributes to prevent layout shifts after loading has completed, it needs to know the aspect ratio of the source image. Unlike [local images](../usage/local-images.md) it cannot know this upfront for remote images, that's why it is recommended to supply the `aspectRatio` parameter if possible: + +```ts [Lit] +autorender('products/chair.jpg', { + aspectRatio: 1.5, +}); +``` + +### Quality + +Use the `quality` parameter to pass a custom quality setting (`1`–`100`) instead of Autorender's per-format default: + +```ts [Lit] +autorender('products/chair.jpg', { + quality: 50, +}); +``` + +### Image formats + +By default the component lets Autorender select the format from the request `Accept` header. + +If you want a `picture` tag with one or more specific formats as `source` tags you can specify them using the `formats` argument. Autorender supports `avif`, `webp`, `jpeg`, `png`, `gif`, and `tiff`: + +```ts [Lit] +autorender('products/chair.jpg', { + formats: ['avif', 'webp'], +}); +``` + +### Remote images + +Besides source paths inside your workspace, you can pass a full `http(s)` URL. Autorender fetches the remote image, then optimizes and delivers it through the CDN — useful when your originals live elsewhere and aren't uploaded to the workspace: + +```ts [Lit] +autorender('https://images.example.com/products/chair.jpg', { + formats: ['avif', 'webp'], +}); +``` + +### Custom transforms + +Besides the resizing and format tokens the library adds implicitly, you can append any additional [Autorender transform tokens](https://autorender.io/docs/transformations/introduction) verbatim by passing a `transforms` array: + +```ts [Lit] +autorender('products/chair.jpg', { + transforms: ['e_sharpen', 'r_16'], +}); +``` diff --git a/apps/docs/src/cdn/index.md b/apps/docs/src/cdn/index.md index 2daa8a2e8..de026062e 100644 --- a/apps/docs/src/cdn/index.md +++ b/apps/docs/src/cdn/index.md @@ -6,6 +6,7 @@ With image CDNs the image processing is offloaded to the Cloud. This allows for The following image CDNs are supported by the `@responsive-image/cdn` package out of the box: +- [Autorender](./autorender.md) - [Cloudinary](./cloudinary.md) - [Fastly](./fastly.md) - [Imgix](./imgix.md) diff --git a/packages/cdn/src/autorender.ts b/packages/cdn/src/autorender.ts new file mode 100644 index 000000000..a0db48d53 --- /dev/null +++ b/packages/cdn/src/autorender.ts @@ -0,0 +1,101 @@ +import { assert, getConfig } from '@responsive-image/core'; + +import type { Config, CoreOptions } from './types'; +import type { ImageData, ImageUrlForType } from '@responsive-image/core'; + +export interface AutorenderConfig { + /** + * Delivery domain that serves your transformed images, + * e.g. `assets.autorender.io`. + */ + domain: string; + /** + * Public workspace ID that routes the request, e.g. `LOKVTtKVGb`. + * It is part of the delivery URL and is not a secret. + */ + workspace: string; +} + +export interface AutorenderOptions extends CoreOptions { + /** + * Extra Autorender transform tokens applied verbatim, e.g. + * `['e_sharpen', 'r_16']`. See the transformation reference for the + * full token vocabulary. + */ + transforms?: string[]; +} + +const ABSOLUTE_URL_RE = /^https?:\/\//i; + +function normalizeSrc(src: string): string { + return src[0] === '/' ? src.slice(1) : src; +} + +/** + * Escape the structural characters of a remote fetch URL so it survives as a + * single transform token: `%` (round-trip safety), `,` (token delimiter), + * `?` and `#` (which would otherwise terminate the path). Slashes and colons + * stay literal; the delivery backend percent-decodes path segments. + */ +function escapeFetchPayload(url: string): string { + return url + .replace(/%/g, '%25') + .replace(/,/g, '%2C') + .replace(/\?/g, '%3F') + .replace(/#/g, '%23'); +} + +export function autorender( + image: string, + options: AutorenderOptions = {}, +): ImageData { + const config = getConfig('cdn')?.autorender; + const domain = config?.domain; + const workspace = config?.workspace; + assert( + 'domain must be set for the autorender provider!', + typeof domain === 'string', + ); + assert( + 'workspace must be set for the autorender provider!', + typeof workspace === 'string', + ); + + const isRemote = ABSOLUTE_URL_RE.test(image.trim()); + const src = isRemote ? image.trim() : normalizeSrc(image); + + const imageData: ImageData = { + imageTypes: options.formats ?? 'auto', + imageUrlFor(width: number, type: ImageUrlForType = 'jpeg'): string { + const tokens = [`w_${width}`]; + + // Autorender accepts `f_jpeg` directly, so the format name maps 1:1. + // `auto` emits `f_auto`, letting Autorender negotiate from the + // Accept header instead of pinning a single format. + tokens.push(`f_${type}`); + + if (options.quality) { + tokens.push(`q_${options.quality}`); + } + + if (options.transforms) { + tokens.push(...options.transforms); + } + + // Remote sources become a `fetch_` transform token (not a path + // segment); the workspace-relative path otherwise trails the tokens. + if (isRemote) { + tokens.push(`fetch_${escapeFetchPayload(src)}`); + return `https://${domain}/${workspace}/${tokens.join(',')}`; + } + + return `https://${domain}/${workspace}/${tokens.join(',')}/${src}`; + }, + }; + + if (options.aspectRatio) { + imageData.aspectRatio = options.aspectRatio; + } + + return imageData; +} diff --git a/packages/cdn/src/index.ts b/packages/cdn/src/index.ts index 61e3a6e48..4134bc7fa 100644 --- a/packages/cdn/src/index.ts +++ b/packages/cdn/src/index.ts @@ -1,3 +1,4 @@ +export * from './autorender.ts'; export * from './cloudinary.ts'; export * from './fastly.ts'; export * from './imgix.ts'; diff --git a/packages/cdn/src/types.ts b/packages/cdn/src/types.ts index 5c63c32c9..7e02a73f4 100644 --- a/packages/cdn/src/types.ts +++ b/packages/cdn/src/types.ts @@ -1,3 +1,4 @@ +import type { AutorenderConfig } from './autorender'; import type { CloudinaryConfig } from './cloudinary'; import type { FastlyConfig } from './fastly'; import type { ImgixConfig } from './imgix'; @@ -5,6 +6,7 @@ import type { NetlifyConfig } from './netlify'; import type { ImageTypeAuto, ImageType } from '@responsive-image/core'; export interface Config { + autorender?: AutorenderConfig; imgix?: ImgixConfig; fastly?: FastlyConfig; cloudinary?: CloudinaryConfig; diff --git a/packages/cdn/tests/autorender.test.ts b/packages/cdn/tests/autorender.test.ts new file mode 100644 index 000000000..d68ed66f3 --- /dev/null +++ b/packages/cdn/tests/autorender.test.ts @@ -0,0 +1,108 @@ +import { setConfig } from '@responsive-image/core'; +import { beforeAll, describe, expect, test } from 'vitest'; + +import { autorender } from '../src'; + +import type { Config } from '../src'; + +describe('autorender', function () { + beforeAll(() => { + setConfig('cdn', { + autorender: { domain: 'assets.autorender.io', workspace: 'LOKVTtKVGb' }, + }); + }); + + test('it lets the CDN choose image type by default', function () { + const result = autorender('products/chair.jpg'); + + expect(result?.imageTypes).toEqual('auto'); + }); + + test('it supports custom image types', function () { + const result = autorender('products/chair.jpg', { + formats: ['jpeg', 'webp'], + }); + + expect(result?.imageTypes).toEqual(['jpeg', 'webp']); + }); + + test('it returns correct image URLs', function () { + const result = autorender('products/chair.jpg'); + + expect(result.imageUrlFor(100, 'jpeg')).toBe( + 'https://assets.autorender.io/LOKVTtKVGb/w_100,f_jpeg/products/chair.jpg', + ); + + expect(result.imageUrlFor(1000, 'webp')).toBe( + 'https://assets.autorender.io/LOKVTtKVGb/w_1000,f_webp/products/chair.jpg', + ); + }); + + test('it normalizes a leading slash in the source path', function () { + const result = autorender('/products/chair.jpg'); + + expect(result.imageUrlFor(100, 'jpeg')).toBe( + 'https://assets.autorender.io/LOKVTtKVGb/w_100,f_jpeg/products/chair.jpg', + ); + }); + + test('it emits f_auto for the auto type', function () { + const result = autorender('products/chair.jpg', { formats: 'auto' }); + + expect(result.imageUrlFor(100, 'auto')).toBe( + 'https://assets.autorender.io/LOKVTtKVGb/w_100,f_auto/products/chair.jpg', + ); + }); + + test('it supports custom quality setting', function () { + const result = autorender('products/chair.jpg', { quality: 50 }); + + expect(result.imageUrlFor(100, 'jpeg')).toBe( + 'https://assets.autorender.io/LOKVTtKVGb/w_100,f_jpeg,q_50/products/chair.jpg', + ); + }); + + test('it appends custom transform tokens', function () { + const result = autorender('products/chair.jpg', { + transforms: ['e_sharpen', 'r_16'], + }); + + expect(result.imageUrlFor(100, 'jpeg')).toBe( + 'https://assets.autorender.io/LOKVTtKVGb/w_100,f_jpeg,e_sharpen,r_16/products/chair.jpg', + ); + }); + + test('it supports custom aspectRatio', function () { + const result = autorender('products/chair.jpg', { aspectRatio: 2 }); + + expect(result.aspectRatio).toBe(2); + }); + + test('it fetches a remote URL as a fetch_ transform token', function () { + const result = autorender('https://images.example.com/chair.jpg'); + + expect(result.imageUrlFor(100, 'webp')).toBe( + 'https://assets.autorender.io/LOKVTtKVGb/w_100,f_webp,fetch_https://images.example.com/chair.jpg', + ); + }); + + test('it escapes structural characters in a remote fetch URL', function () { + const result = autorender( + 'https://images.example.com/chair.jpg?v=2&size=xl', + ); + + expect(result.imageUrlFor(100, 'webp')).toBe( + 'https://assets.autorender.io/LOKVTtKVGb/w_100,f_webp,fetch_https://images.example.com/chair.jpg%3Fv=2&size=xl', + ); + }); + + test('it appends custom transforms before the fetch token', function () { + const result = autorender('https://images.example.com/chair.jpg', { + transforms: ['e_sharpen'], + }); + + expect(result.imageUrlFor(100, 'webp')).toBe( + 'https://assets.autorender.io/LOKVTtKVGb/w_100,f_webp,e_sharpen,fetch_https://images.example.com/chair.jpg', + ); + }); +}); From 27686d69d2ee290e3cafa06e572d1ac738874389 Mon Sep 17 00:00:00 2001 From: Vasanthakumar RB Date: Sun, 9 Aug 2026 20:05:39 +0530 Subject: [PATCH 2/2] fix(cdn): harden Autorender integration --- .changeset/autorender-cdn-provider.md | 1 + apps/docs/src/cdn/autorender.md | 21 ++-------- apps/ember-vite/app/app.js | 4 ++ apps/ember-vite/app/templates/index.hbs | 7 ++++ apps/ember-webpack/app/app.ts | 4 ++ apps/ember-webpack/app/templates/index.hbs | 7 ++++ apps/nuxt/app/app.vue | 10 +++++ apps/react-vite/src/main.tsx | 11 ++++- apps/solid-start/src/app.tsx | 4 ++ apps/solid-start/src/routes/index.tsx | 13 +++++- apps/svelte-kit/src/routes/+page.svelte | 10 +++++ apps/wc-lit/src/app.ts | 19 ++++++++- packages/cdn/src/autorender.ts | 39 ++++-------------- packages/cdn/tests/autorender.test.ts | 40 +++++-------------- packages/ember/package.json | 1 + .../helpers/responsive-image-autorender.ts | 1 + packages/ember/src/template-registry.ts | 2 + .../responsive-image-autorender-test.gts | 10 +++++ 18 files changed, 123 insertions(+), 81 deletions(-) create mode 100644 packages/ember/src/helpers/responsive-image-autorender.ts create mode 100644 packages/ember/tests/integration/helpers/responsive-image-autorender-test.gts diff --git a/.changeset/autorender-cdn-provider.md b/.changeset/autorender-cdn-provider.md index 8734d7c0f..87a236005 100644 --- a/.changeset/autorender-cdn-provider.md +++ b/.changeset/autorender-cdn-provider.md @@ -1,5 +1,6 @@ --- '@responsive-image/cdn': minor +'@responsive-image/ember': minor --- Add Autorender image CDN provider diff --git a/apps/docs/src/cdn/autorender.md b/apps/docs/src/cdn/autorender.md index 5a5a7959a..e5ff746d2 100644 --- a/apps/docs/src/cdn/autorender.md +++ b/apps/docs/src/cdn/autorender.md @@ -41,9 +41,6 @@ setConfig('cdn', { ## Usage -> [!IMPORTANT] -> Please make sure you have read the section on [remote images](../usage/remote-images.md) first. - Use the autorender provider function passing the source path of the image inside your workspace, and pass the return value to the [image component](../usage/component.md): ::: code-group @@ -119,7 +116,7 @@ import { autorender } from '@responsive-image/cdn'; ### Aspect Ratio -For the image component to be able to render `width` and `height` attributes to prevent layout shifts after loading has completed, it needs to know the aspect ratio of the source image. Unlike [local images](../usage/local-images.md) it cannot know this upfront for remote images, that's why it is recommended to supply the `aspectRatio` parameter if possible: +For the image component to be able to render `width` and `height` attributes to prevent layout shifts after loading has completed, it needs to know the aspect ratio of the source image. Unlike [local images](../usage/local-images.md) it cannot know this upfront, that's why it is recommended to supply the `aspectRatio` parameter if possible: ```ts [Lit] autorender('products/chair.jpg', { @@ -141,7 +138,7 @@ autorender('products/chair.jpg', { By default the component lets Autorender select the format from the request `Accept` header. -If you want a `picture` tag with one or more specific formats as `source` tags you can specify them using the `formats` argument. Autorender supports `avif`, `webp`, `jpeg`, `png`, `gif`, and `tiff`: +If you want a `picture` tag with one or more specific formats as `source` tags you can specify them using the `formats` argument. Autorender supports `avif`, `webp`, `jpeg`, and `png`: ```ts [Lit] autorender('products/chair.jpg', { @@ -149,22 +146,12 @@ autorender('products/chair.jpg', { }); ``` -### Remote images - -Besides source paths inside your workspace, you can pass a full `http(s)` URL. Autorender fetches the remote image, then optimizes and delivers it through the CDN — useful when your originals live elsewhere and aren't uploaded to the workspace: - -```ts [Lit] -autorender('https://images.example.com/products/chair.jpg', { - formats: ['avif', 'webp'], -}); -``` - ### Custom transforms -Besides the resizing and format tokens the library adds implicitly, you can append any additional [Autorender transform tokens](https://autorender.io/docs/transformations/introduction) verbatim by passing a `transforms` array: +Besides the resizing and format tokens the library adds implicitly, you can append any additional [Autorender transform tokens](https://www.autorender.io/docs/transformations/introduction) verbatim by passing a `transforms` array: ```ts [Lit] autorender('products/chair.jpg', { - transforms: ['e_sharpen', 'r_16'], + transforms: ['e_sharpen', 'br_16'], }); ``` diff --git a/apps/ember-vite/app/app.js b/apps/ember-vite/app/app.js index 1951fe253..1c31faa7a 100644 --- a/apps/ember-vite/app/app.js +++ b/apps/ember-vite/app/app.js @@ -12,6 +12,10 @@ export default class App extends Application { } setConfig('cdn', { + autorender: { + domain: 'assets.autorender.io', + workspace: 'LOKVTtKVGb', + }, cloudinary: { cloudName: 'responsive-image', }, diff --git a/apps/ember-vite/app/templates/index.hbs b/apps/ember-vite/app/templates/index.hbs index 1cf5270e3..b33f767df 100644 --- a/apps/ember-vite/app/templates/index.hbs +++ b/apps/ember-vite/app/templates/index.hbs @@ -20,6 +20,13 @@ data-test-cloudinary-image /> +

Autorender

+ + +

Fastly

diff --git a/apps/ember-webpack/app/app.ts b/apps/ember-webpack/app/app.ts index 675c58f29..cceb28f55 100644 --- a/apps/ember-webpack/app/app.ts +++ b/apps/ember-webpack/app/app.ts @@ -12,6 +12,10 @@ export default class App extends Application { } setConfig('cdn', { + autorender: { + domain: 'assets.autorender.io', + workspace: 'LOKVTtKVGb', + }, cloudinary: { cloudName: 'responsive-image', }, diff --git a/apps/ember-webpack/app/templates/index.hbs b/apps/ember-webpack/app/templates/index.hbs index 1cf5270e3..b33f767df 100644 --- a/apps/ember-webpack/app/templates/index.hbs +++ b/apps/ember-webpack/app/templates/index.hbs @@ -20,6 +20,13 @@ data-test-cloudinary-image /> +

Autorender

+ + +

Fastly

diff --git a/apps/nuxt/app/app.vue b/apps/nuxt/app/app.vue index f032dd918..3cdfe7d8a 100644 --- a/apps/nuxt/app/app.vue +++ b/apps/nuxt/app/app.vue @@ -2,6 +2,7 @@ import { ResponsiveImage } from '@responsive-image/vue'; import { cloudinary, + autorender, fastly, imgix, netlify, @@ -17,6 +18,10 @@ import imageGray from './images/aurora.jpg?grayscale&responsive'; import { setConfig } from '@responsive-image/core'; setConfig('cdn', { + autorender: { + domain: 'assets.autorender.io', + workspace: 'LOKVTtKVGb', + }, cloudinary: { cloudName: 'responsive-image', }, @@ -52,6 +57,11 @@ setConfig('cdn', { " data-test-cloudinary-image /> +

Autorender

+

Fastly

+

Autorender

+

imgix

('cdn', { + autorender: { + domain: 'assets.autorender.io', + workspace: 'LOKVTtKVGb', + }, cloudinary: { cloudName: 'responsive-image', }, diff --git a/apps/solid-start/src/routes/index.tsx b/apps/solid-start/src/routes/index.tsx index e21e48aad..374d984b5 100644 --- a/apps/solid-start/src/routes/index.tsx +++ b/apps/solid-start/src/routes/index.tsx @@ -1,4 +1,10 @@ -import { cloudinary, fastly, imgix, netlify } from '@responsive-image/cdn'; +import { + autorender, + cloudinary, + fastly, + imgix, + netlify, +} from '@responsive-image/cdn'; import { ResponsiveImage } from '@responsive-image/solid'; import image from '../images/aurora.jpg?responsive'; import imageLqipColor from '../images/aurora.jpg?lqip=color&responsive'; @@ -23,6 +29,11 @@ export default function Home() { })} data-test-cloudinary-image /> +

Autorender

+

Fastly

('cdn', { + autorender: { + domain: 'assets.autorender.io', + workspace: 'LOKVTtKVGb', + }, cloudinary: { cloudName: 'responsive-image', }, @@ -46,6 +51,11 @@ })} data-test-cloudinary-image /> +

Autorender

+

Fastly

('cdn', { + autorender: { + domain: 'assets.autorender.io', + workspace: 'LOKVTtKVGb', + }, cloudinary: { cloudName: 'responsive-image', }, @@ -48,6 +58,13 @@ export class MyApp extends LitElement { data-test-cloudinary-image > +

Autorender

+ + +

Fastly

` transform token (not a path - // segment); the workspace-relative path otherwise trails the tokens. - if (isRemote) { - tokens.push(`fetch_${escapeFetchPayload(src)}`); - return `https://${domain}/${workspace}/${tokens.join(',')}`; - } - return `https://${domain}/${workspace}/${tokens.join(',')}/${src}`; }, }; diff --git a/packages/cdn/tests/autorender.test.ts b/packages/cdn/tests/autorender.test.ts index d68ed66f3..396b8370c 100644 --- a/packages/cdn/tests/autorender.test.ts +++ b/packages/cdn/tests/autorender.test.ts @@ -64,45 +64,27 @@ describe('autorender', function () { test('it appends custom transform tokens', function () { const result = autorender('products/chair.jpg', { - transforms: ['e_sharpen', 'r_16'], + transforms: ['e_sharpen', 'br_16'], }); expect(result.imageUrlFor(100, 'jpeg')).toBe( - 'https://assets.autorender.io/LOKVTtKVGb/w_100,f_jpeg,e_sharpen,r_16/products/chair.jpg', + 'https://assets.autorender.io/LOKVTtKVGb/e_sharpen,br_16,w_100,f_jpeg/products/chair.jpg', ); }); - test('it supports custom aspectRatio', function () { - const result = autorender('products/chair.jpg', { aspectRatio: 2 }); - - expect(result.aspectRatio).toBe(2); - }); - - test('it fetches a remote URL as a fetch_ transform token', function () { - const result = autorender('https://images.example.com/chair.jpg'); - - expect(result.imageUrlFor(100, 'webp')).toBe( - 'https://assets.autorender.io/LOKVTtKVGb/w_100,f_webp,fetch_https://images.example.com/chair.jpg', - ); - }); - - test('it escapes structural characters in a remote fetch URL', function () { - const result = autorender( - 'https://images.example.com/chair.jpg?v=2&size=xl', - ); + test('it applies the requested width after custom transforms', function () { + const result = autorender('products/chair.jpg', { + transforms: ['w_50'], + }); - expect(result.imageUrlFor(100, 'webp')).toBe( - 'https://assets.autorender.io/LOKVTtKVGb/w_100,f_webp,fetch_https://images.example.com/chair.jpg%3Fv=2&size=xl', + expect(result.imageUrlFor(100, 'jpeg')).toBe( + 'https://assets.autorender.io/LOKVTtKVGb/w_50,w_100,f_jpeg/products/chair.jpg', ); }); - test('it appends custom transforms before the fetch token', function () { - const result = autorender('https://images.example.com/chair.jpg', { - transforms: ['e_sharpen'], - }); + test('it supports custom aspectRatio', function () { + const result = autorender('products/chair.jpg', { aspectRatio: 2 }); - expect(result.imageUrlFor(100, 'webp')).toBe( - 'https://assets.autorender.io/LOKVTtKVGb/w_100,f_webp,e_sharpen,fetch_https://images.example.com/chair.jpg', - ); + expect(result.aspectRatio).toBe(2); }); }); diff --git a/packages/ember/package.json b/packages/ember/package.json index cbe3f0304..673999986 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -88,6 +88,7 @@ "main": "addon-main.cjs", "app-js": { "./components/responsive-image.js": "./dist/_app_/components/responsive-image.js", + "./helpers/responsive-image-autorender.js": "./dist/_app_/helpers/responsive-image-autorender.js", "./helpers/responsive-image-cloudinary.js": "./dist/_app_/helpers/responsive-image-cloudinary.js", "./helpers/responsive-image-fastly.js": "./dist/_app_/helpers/responsive-image-fastly.js", "./helpers/responsive-image-imgix.js": "./dist/_app_/helpers/responsive-image-imgix.js", diff --git a/packages/ember/src/helpers/responsive-image-autorender.ts b/packages/ember/src/helpers/responsive-image-autorender.ts new file mode 100644 index 000000000..ee586b928 --- /dev/null +++ b/packages/ember/src/helpers/responsive-image-autorender.ts @@ -0,0 +1 @@ +export { autorender as default } from '@responsive-image/cdn'; diff --git a/packages/ember/src/template-registry.ts b/packages/ember/src/template-registry.ts index 30fb41ec3..29420ed0c 100644 --- a/packages/ember/src/template-registry.ts +++ b/packages/ember/src/template-registry.ts @@ -1,4 +1,5 @@ import type ResponsiveImageComponent from './components/responsive-image.gts'; +import type AutorenderProvider from './helpers/responsive-image-autorender'; import type CloudinaryProvider from './helpers/responsive-image-cloudinary'; import type FastlyProvider from './helpers/responsive-image-fastly'; import type ImgixProvider from './helpers/responsive-image-imgix.ts'; @@ -8,6 +9,7 @@ import type ResponsiveImageResolve from './helpers/responsive-image-resolve.ts'; export default interface Registry { ResponsiveImage: typeof ResponsiveImageComponent; 'responsive-image-resolve': typeof ResponsiveImageResolve; + 'responsive-image-autorender': typeof AutorenderProvider; 'responsive-image-cloudinary': typeof CloudinaryProvider; 'responsive-image-fastly': typeof FastlyProvider; 'responsive-image-netlify': typeof NetlifyProvider; diff --git a/packages/ember/tests/integration/helpers/responsive-image-autorender-test.gts b/packages/ember/tests/integration/helpers/responsive-image-autorender-test.gts new file mode 100644 index 000000000..e2f42bfcd --- /dev/null +++ b/packages/ember/tests/integration/helpers/responsive-image-autorender-test.gts @@ -0,0 +1,10 @@ +import { autorender } from '@responsive-image/cdn'; +import { module, test } from 'qunit'; + +import responsiveImageAutorender from '../../../src/helpers/responsive-image-autorender.ts'; + +module('Integration | Helper | responsive-image-autorender', function () { + test('it re-exports autorender provider from cdn package', function (assert) { + assert.strictEqual(responsiveImageAutorender, autorender); + }); +});