Skip to content

Commit

Permalink
refactor(common): request low quality placeholder images
Browse files Browse the repository at this point in the history
For every built-in load, this commit adds a parameter to load low quality placeholder images. Using 20/100 as base value.
  • Loading branch information
JeanMeche committed Mar 23, 2024
1 parent 1b19d2d commit 743cc35
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 2 deletions.
Expand Up @@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {PLACEHOLDER_QUALITY} from './constants';
import {createImageLoader, ImageLoaderConfig} from './image_loader';

/**
Expand All @@ -29,6 +30,12 @@ function createCloudflareUrl(path: string, config: ImageLoaderConfig) {
if (config.width) {
params += `,width=${config.width}`;
}

// When requesting a placeholder image we ask for a low quality image to reduce the load time.
if (config.isPlaceholder) {
params += `,quality=${PLACEHOLDER_QUALITY}`;
}

// Cloudflare image URLs format:
// https://developers.cloudflare.com/images/image-resizing/url-format/
return `${path}/cdn-cgi/image/${params}/${config.src}`;
Expand Down
Expand Up @@ -52,9 +52,15 @@ function createCloudinaryUrl(path: string, config: ImageLoaderConfig) {
// https://cloudinary.com/documentation/image_transformations#transformation_url_structure
// Example of a Cloudinary image URL:
// https://res.cloudinary.com/mysite/image/upload/c_scale,f_auto,q_auto,w_600/marketing/tile-topics-m.png
let params = `f_auto,q_auto`; // sets image format and quality to "auto"

// For a placeholder image, we use the lowest image setting available to reduce the load time
// else we use the auto size
const quality = config.isPlaceholder ? 'q_auto:low' : 'q_auto';

let params = `f_auto,${quality}`;
if (config.width) {
params += `,w_${config.width}`;
}

return `${path}/image/upload/${params}/${config.src}`;
}
@@ -0,0 +1,12 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

/**
* Value (out of 100) of the requested quality for placeholder images.
*/
export const PLACEHOLDER_QUALITY = '20';
Expand Up @@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {PLACEHOLDER_QUALITY} from './constants';
import {createImageLoader, ImageLoaderConfig, ImageLoaderInfo} from './image_loader';

/**
Expand Down Expand Up @@ -53,5 +54,11 @@ export function createImagekitUrl(path: string, config: ImageLoaderConfig): stri
urlSegments = [path, src];
}

return urlSegments.join('/');
const url = new URL(urlSegments.join('/'));

// When requesting a placeholder image we ask for a low quality image to reduce the load time.
if (config.isPlaceholder) {
url.searchParams.set('q', PLACEHOLDER_QUALITY);
}
return url.href;
}
Expand Up @@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {PLACEHOLDER_QUALITY} from './constants';
import {createImageLoader, ImageLoaderConfig, ImageLoaderInfo} from './image_loader';

/**
Expand Down Expand Up @@ -45,5 +46,10 @@ function createImgixUrl(path: string, config: ImageLoaderConfig) {
if (config.width) {
url.searchParams.set('w', config.width.toString());
}

// When requesting a placeholder image we ask a low quality image to reduce the load time.
if (config.isPlaceholder) {
url.searchParams.set('q', PLACEHOLDER_QUALITY);
}
return url.href;
}
Expand Up @@ -16,6 +16,7 @@ import {RuntimeErrorCode} from '../../../errors';
import {isAbsoluteUrl, isValidPath} from '../url';

import {IMAGE_LOADER, ImageLoaderConfig, ImageLoaderInfo} from './image_loader';
import {PLACEHOLDER_QUALITY} from './constants';

/**
* Name and URL tester for Netlify.
Expand Down Expand Up @@ -90,6 +91,13 @@ function createNetlifyUrl(config: ImageLoaderConfig, path?: string) {
url.searchParams.set('w', config.width.toString());
}

// When requesting a placeholder image we ask for a low quality image to reduce the load time.
// If the quality is specified in the loader config - always use provided value.
const configQuality = config.loaderParams?.['quality'] ?? config.loaderParams?.['q'];
if (config.isPlaceholder && !configQuality) {
url.searchParams.set('q', PLACEHOLDER_QUALITY);
}

for (const [param, value] of Object.entries(config.loaderParams ?? {})) {
if (validParams.has(param)) {
url.searchParams.set(validParams.get(param)!, value.toString());
Expand Down
44 changes: 44 additions & 0 deletions packages/common/test/image_loaders/image_loader_spec.ts
Expand Up @@ -75,6 +75,13 @@ describe('Built-in image directive loaders', () => {
const loader = createImgixLoader(path);
expect(() => loader({src})).toThrowError(absoluteUrlError(src, path));
});

it('should load a low quality image when a placeholder is requested', () => {
const path = 'https://somesite.imgix.net';
const loader = createImgixLoader(path);
const config = {src: 'img.png', isPlaceholder: true};
expect(loader(config)).toBe(`${path}/img.png?auto=format&q=20`);
});
});

describe('Cloudinary loader', () => {
Expand All @@ -97,6 +104,13 @@ describe('Built-in image directive loaders', () => {
).toBe(`${path}/image/upload/f_auto,q_auto/marketing/img-2.png`);
});

it('should load a low quality image when a placeholder is requested', () => {
const path = 'https://res.cloudinary.com/mysite';
const loader = createCloudinaryLoader(path);
const config = {src: 'img.png', isPlaceholder: true};
expect(loader(config)).toBe(`${path}/image/upload/f_auto,q_auto:low/img.png`);
});

describe('input validation', () => {
it('should throw if an absolute URL is provided as a loader input', () => {
const path = 'https://res.cloudinary.com/mysite';
Expand Down Expand Up @@ -154,6 +168,13 @@ describe('Built-in image directive loaders', () => {
);
});

it('should load a low quality image when a placeholder is requested', () => {
const path = 'https://ik.imageengine.io/imagetest';
const loader = createImageKitLoader(path);
const config = {src: 'img.png', isPlaceholder: true};
expect(loader(config)).toBe(`${path}/img.png?q=20`);
});

describe('input validation', () => {
it('should throw if an absolute URL is provided as a loader input', () => {
const path = 'https://ik.imageengine.io/imagetest';
Expand Down Expand Up @@ -210,6 +231,15 @@ describe('Built-in image directive loaders', () => {
const loader = createCloudflareLoader(path);
expect(() => loader({src})).toThrowError(absoluteUrlError(src, path));
});

it('should load a low quality image when a placeholder is requested', () => {
const path = 'https://mysite.com';
const loader = createCloudflareLoader(path);
const config = {src: 'img.png', isPlaceholder: true};
expect(loader(config)).toBe(
'https://mysite.com/cdn-cgi/image/format=auto,quality=20/img.png',
);
});
});

describe('Netlify loader', () => {
Expand Down Expand Up @@ -261,6 +291,20 @@ describe('Built-in image directive loaders', () => {
`NG0${RuntimeErrorCode.INVALID_LOADER_ARGUMENTS}: The Netlify image loader has detected an \`<img>\` tag with the unsupported attribute "\`unknown\`".`,
);
});

it('should load a low quality image when a placeholder is requested', () => {
const path = 'https://mysite.com';
const loader = createNetlifyLoader(path);
const config = {src: 'img.png', isPlaceholder: true};
expect(loader(config)).toBe('https://mysite.com/.netlify/images?url=%2Fimg.png&q=20');
});

it('should not load a low quality image when a placeholder is requested with a quality param', () => {
const path = 'https://mysite.com';
const loader = createNetlifyLoader(path);
const config = {src: 'img.png', isPlaceholder: true, loaderParams: {quality: 50}};
expect(loader(config)).toBe('https://mysite.com/.netlify/images?url=%2Fimg.png&q=50');
});
});

describe('loader utils', () => {
Expand Down

0 comments on commit 743cc35

Please sign in to comment.