Repository: kaliber5/ember-responsive-image
Pull request #310 (2021-07-23 10:24:18)

Add imgix provider


Patch:
diff --git a/README.md b/README.md
index 188218ad..96b6c858 100644
--- a/README.md
+++ b/README.md
@@ -242,6 +242,7 @@ an image CDN as a proxy to scale, optimize and deliver that image as needed, at
 
 The addon currently ships with these image providers (besides the local one) out of the box:
 * [Cloudinary](docs/providers/cloudinary.md)
+* [imgix](docs/providers/imgix.md)
 
 ## Configuration
 
diff --git a/addon/helpers/responsive-image-cloudinary-provider.ts b/addon/helpers/responsive-image-cloudinary-provider.ts
index 65fe62de..9c238884 100644
--- a/addon/helpers/responsive-image-cloudinary-provider.ts
+++ b/addon/helpers/responsive-image-cloudinary-provider.ts
@@ -3,6 +3,7 @@ import Helper from '@ember/component/helper';
 import ResponsiveImageService from 'ember-responsive-image/services/responsive-image';
 import { ImageType, ProviderResult } from 'ember-responsive-image/types';
 import { assert } from '@ember/debug';
+import { normalizeSrc } from 'ember-responsive-image/utils/utils';
 
 interface CloudinaryConfig {
   cloudName: string;
@@ -35,25 +36,23 @@ export const provider = (
     imageId = encodeURIComponent(image);
     deliveryType = 'fetch';
   } else {
-    imageId = image.replace(/\.[^/.]+$/, '');
+    imageId = normalizeSrc(image).replace(/\.[^/.]+$/, '');
     deliveryType = 'upload';
   }
 
   return {
     imageTypes: options.formats ?? ['png', 'jpeg', 'webp', 'avif'],
     imageUrlFor(width: number, type: ImageType = 'jpeg'): string {
-      let resizeParams = `w_${width},c_limit,q_auto`;
-      if (deliveryType !== 'upload') {
-        resizeParams += `,f_${type}`;
-      }
-
-      const params = options.transformations
-        ? `${options.transformations}/${resizeParams}`
-        : resizeParams;
-
-      return `https://res.cloudinary.com/${cloudName}/image/${deliveryType}/${params}/${imageId}${
-        deliveryType === 'upload' ? '.' + type : ''
-      }`;
+      const params = [
+        `w_${width}`,
+        'c_limit',
+        'q_auto',
+        deliveryType !== 'upload' ? `f_${type}` : undefined,
+        options.transformations,
+      ];
+      return `https://res.cloudinary.com/${cloudName}/image/${deliveryType}/${params
+        .filter(Boolean)
+        .join(',')}/${imageId}${deliveryType === 'upload' ? '.' + type : ''}`;
     },
   };
 };
diff --git a/addon/helpers/responsive-image-imgix-provider.ts b/addon/helpers/responsive-image-imgix-provider.ts
new file mode 100644
index 00000000..7a5f188e
--- /dev/null
+++ b/addon/helpers/responsive-image-imgix-provider.ts
@@ -0,0 +1,50 @@
+import { inject as service } from '@ember/service';
+import Helper from '@ember/component/helper';
+import ResponsiveImageService from 'ember-responsive-image/services/responsive-image';
+import { ImageType, ProviderResult } from 'ember-responsive-image/types';
+import { assert } from '@ember/debug';
+import { normalizeSrc } from 'ember-responsive-image/utils/utils';
+
+interface ImgixConfig {
+  domain: string;
+}
+
+interface ImgixOptions {
+  formats?: ImageType[];
+}
+
+const formatMap: Record<string, string> = {
+  jpeg: 'jpg',
+};
+
+export const provider = (
+  image: string,
+  service: ResponsiveImageService,
+  options: ImgixOptions
+): ProviderResult => {
+  const domain = service.getProviderConfig<ImgixConfig>('imgix').domain;
+  assert('domain must be set for imgix provider!', typeof domain === 'string');
+
+  return {
+    imageTypes: options.formats ?? ['png', 'jpeg', 'webp'],
+    imageUrlFor(width: number, type: ImageType = 'jpeg'): string {
+      const url = new URL(`https://${domain}/${normalizeSrc(image)}`);
+      const params = url.searchParams;
+
+      params.set('fm', formatMap[type] ?? type);
+      params.set('w', String(width));
+      params.set('fit', 'max');
+
+      return url.toString();
+    },
+  };
+};
+
+export default class ResponsiveImageCloudinaryProvider extends Helper {
+  @service
+  responsiveImage!: ResponsiveImageService;
+
+  compute([image]: [string], options: ImgixOptions): ProviderResult {
+    return provider(image, this.responsiveImage, options);
+  }
+}
diff --git a/addon/utils/utils.ts b/addon/utils/utils.ts
new file mode 100644
index 00000000..ae98fa16
--- /dev/null
+++ b/addon/utils/utils.ts
@@ -0,0 +1,3 @@
+export function normalizeSrc(src: string): string {
+  return src[0] === '/' ? src.slice(1) : src;
+}
diff --git a/app/helpers/responsive-image-imgix-provider.js b/app/helpers/responsive-image-imgix-provider.js
new file mode 100644
index 00000000..8feffbee
--- /dev/null
+++ b/app/helpers/responsive-image-imgix-provider.js
@@ -0,0 +1,1 @@
+export { default } from 'ember-responsive-image/helpers/responsive-image-imgix-provider';
diff --git a/docs/providers/imgix.md b/docs/providers/imgix.md
new file mode 100644
index 00000000..d88eae9c
--- /dev/null
+++ b/docs/providers/imgix.md
@@ -0,0 +1,17 @@
+# imgix Provider
+
+## Usage
+
+```hbs
+<ResponsiveImage @src={{responsive-image-imgix-provider "path/to/image.jpg"}}/>
+```
+
+### Image formats
+
+By default, all image formats supported by imgix (PNG, JPEG, WEBP, but no AVIF yet) are referenced in the generated `<source>` tags.
+You can tweak that using the `formats` argument:
+
+```hbs
+<ResponsiveImage @src={{responsive-image-imgix-provider "path/to/image.jpg" formats=(array "webp" "jpeg")}}/>
+```
+
diff --git a/ember-cli-build.js b/ember-cli-build.js
index e2a24c61..7aaa1aa5 100644
--- a/ember-cli-build.js
+++ b/ember-cli-build.js
@@ -15,6 +15,9 @@ module.exports = function (defaults) {
         cloudinary: {
           cloudName: 'kaliber5',
         },
+        imgix: {
+          domain: 'kaliber5.imgix.net',
+        },
       },
       deviceWidths: [640, 750, 1920],
       fingerprint:


Test patch:
diff --git a/tests/dummy/app/templates/index.hbs b/tests/dummy/app/templates/index.hbs
index da40e45c..1d44361d 100644
--- a/tests/dummy/app/templates/index.hbs
+++ b/tests/dummy/app/templates/index.hbs
@@ -7,6 +7,10 @@
 
 <ResponsiveImage @src={{responsive-image-cloudinary-provider "samples/animals/three-dogs" formats=(array "webp" "avif")}} @width={{160}}/>
 
+<h2>imgix</h2>
+
+<ResponsiveImage @src={{responsive-image-imgix-provider "pages/approach/agile_2000x1200.jpg"}} @width={{320}}/>
+
 <h2>Local</h2>
 
 <ResponsiveImage @src="/assets/images/docs/lqip-blurhash.jpg"/>
diff --git a/tests/integration/helpers/responsive-image-cloudinary-provider-test.ts b/tests/integration/helpers/responsive-image-cloudinary-provider-test.ts
index 59140dd4..cd155aad 100644
--- a/tests/integration/helpers/responsive-image-cloudinary-provider-test.ts
+++ b/tests/integration/helpers/responsive-image-cloudinary-provider-test.ts
@@ -53,20 +53,7 @@ module(
 
       assert.equal(
         data.imageUrlFor(100, 'jpeg'),
-        'https://res.cloudinary.com/kaliber5/image/upload/co_rgb:20a020,e_colorize:50/w_100,c_limit,q_auto/samples/animals/three-dogs.jpeg'
-      );
-    });
-
-    test('it supports custom chained transformations', async function (assert) {
-      await render(
-        hbs`{{dump (responsive-image-cloudinary-provider "samples/animals/three-dogs" transformations="co_rgb:20a020,e_colorize:50/ar_1.0,c_fill,w_150/r_max")}}`
-      );
-
-      const data = getData() as ProviderResult;
-
-      assert.equal(
-        data.imageUrlFor(100, 'jpeg'),
-        'https://res.cloudinary.com/kaliber5/image/upload/co_rgb:20a020,e_colorize:50/ar_1.0,c_fill,w_150/r_max/w_100,c_limit,q_auto/samples/animals/three-dogs.jpeg'
+        'https://res.cloudinary.com/kaliber5/image/upload/w_100,c_limit,q_auto,co_rgb:20a020,e_colorize:50/samples/animals/three-dogs.jpeg'
       );
     });
 
diff --git a/tests/integration/helpers/responsive-image-imgix-provider-test.ts b/tests/integration/helpers/responsive-image-imgix-provider-test.ts
new file mode 100644
index 00000000..1dfbd311
--- /dev/null
+++ b/tests/integration/helpers/responsive-image-imgix-provider-test.ts
@@ -0,0 +1,57 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render } from '@ember/test-helpers';
+import hbs from 'htmlbars-inline-precompile';
+import setupDataDumper from 'dummy/tests/helpers/dump';
+import { ProviderResult } from 'ember-responsive-image/types';
+
+module(
+  'Integration | Helper | responsive-image-imgix-provider',
+  function (hooks) {
+    setupRenderingTest(hooks);
+    const getData = setupDataDumper(hooks);
+
+    test('it supports jpg, png and webp image types', async function (assert) {
+      await render(
+        hbs`{{dump (responsive-image-imgix-provider "foo/bar.jpg")}}`
+      );
+
+      const data = getData() as ProviderResult;
+
+      assert.deepEqual(data.imageTypes, ['png', 'jpeg', 'webp']);
+    });
+
+    test('it returns correct image URLs', async function (assert) {
+      await render(
+        hbs`{{dump (responsive-image-imgix-provider "foo/bar.jpg")}}`
+      );
+
+      const data = getData() as ProviderResult;
+
+      assert.equal(
+        data.imageUrlFor(100, 'jpeg'),
+        'https://kaliber5.imgix.net/foo/bar.jpg?fm=jpg&w=100&fit=max'
+      );
+
+      assert.equal(
+        data.imageUrlFor(1000, 'jpeg'),
+        'https://kaliber5.imgix.net/foo/bar.jpg?fm=jpg&w=1000&fit=max'
+      );
+
+      assert.equal(
+        data.imageUrlFor(100, 'webp'),
+        'https://kaliber5.imgix.net/foo/bar.jpg?fm=webp&w=100&fit=max'
+      );
+    });
+
+    test('it supports custom image formats', async function (assert) {
+      await render(
+        hbs`{{dump (responsive-image-imgix-provider "foo/bar.jpg" formats=(array "webp" "jpeg"))}}`
+      );
+
+      const data = getData() as ProviderResult;
+
+      assert.deepEqual(data.imageTypes, ['webp', 'jpeg']);
+    });
+  }
+);