Skip to content

Commit 788633e

Browse files
feat: add support for imageengine cdn (#66)
* feat: add support for imageengine cdn * feat: add support for ImageEngine cdn * updated transform method to remove unnecessary directive * Adding fit method --------- Co-authored-by: Matt Kane <[email protected]>
1 parent d635b5c commit 788633e

File tree

7 files changed

+270
-2
lines changed

7 files changed

+270
-2
lines changed

data/subdomains.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
"storyblok.com": "storyblok",
66
"kc-usercontent.com": "kontent.ai",
77
"cloudinary.com": "cloudinary",
8-
"kxcdn.com": "keycdn"
8+
"kxcdn.com": "keycdn",
9+
"imgeng.in": "imageengine"
910
}

demo/src/examples.json

+4
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,9 @@
5151
"directus": [
5252
"Directus",
5353
"https://apollo.kazel.academy/assets/6d910d38-0659-49bf-80b8-fa6e0b257975"
54+
],
55+
"imageengine": [
56+
"ImageEngine",
57+
"https://blazing-fast-pics.cdn.imgeng.in/images/pic_1.jpg?imgeng=/w_400"
5458
]
5559
}

src/parse.ts

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { parse as nextjs } from "./transformers/nextjs.ts";
1414
import { parse as scene7 } from "./transformers/scene7.ts";
1515
import { parse as keycdn } from "./transformers/keycdn.ts";
1616
import { parse as directus } from "./transformers/directus.ts";
17+
import { parse as imageengine } from "./transformers/imageengine.ts";
1718
import { ImageCdn, ParsedUrl, SupportedImageCdn, UrlParser } from "./types.ts";
1819

1920
export const parsers = {
@@ -32,6 +33,7 @@ export const parsers = {
3233
scene7,
3334
keycdn,
3435
directus,
36+
imageengine
3537
};
3638

3739
export const cdnIsSupportedForParse = (

src/transform.ts

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { transform as nextjs } from "./transformers/nextjs.ts";
1414
import { transform as scene7 } from "./transformers/scene7.ts";
1515
import { transform as keycdn } from "./transformers/keycdn.ts";
1616
import { transform as directus } from "./transformers/directus.ts";
17+
import { transform as imageengine } from "./transformers/imageengine.ts";
1718
import { ImageCdn, UrlTransformer } from "./types.ts";
1819
import { getCanonicalCdnForUrl } from "./canonical.ts";
1920

@@ -33,6 +34,7 @@ export const getTransformer = (cdn: ImageCdn) => ({
3334
"kontent.ai": kontentai,
3435
keycdn,
3536
directus,
37+
imageengine,
3638
}[cdn]);
3739

3840
/**

src/transformers/imageengine.test.ts

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
2+
import { ParsedUrl } from "../types.ts";
3+
import { parse, transform, ImageEngineParams } from "./imageengine.ts";
4+
5+
const img =
6+
"https://blazing-fast-pics.cdn.imgeng.in/images/pic_1.jpg";
7+
const parseImg =
8+
"https://blazing-fast-pics.cdn.imgeng.in/images/pic_1.jpg?imgeng=/w_200/h_100/f_webp/m_box"
9+
const transformImage =
10+
"https://blazing-fast-pics.cdn.imgeng.in/images/pic_1.jpg?imgeng=/m_outside/f_png"
11+
12+
Deno.test("ImageEngine parser", async (t) => {
13+
await t.step("parses a URL", () => {
14+
const parsed = parse(parseImg);
15+
const expected: ParsedUrl<ImageEngineParams> = {
16+
base: "https://blazing-fast-pics.cdn.imgeng.in/images/pic_1.jpg",
17+
cdn: "imageengine",
18+
format: "webp",
19+
width: 200,
20+
height: 100,
21+
params: {
22+
fit: "box",
23+
},
24+
};
25+
assertEquals(parsed, expected);
26+
});
27+
28+
await t.step("parses a URL without transforms", () => {
29+
const parsed = parse(img);
30+
const expected: ParsedUrl<ImageEngineParams> = {
31+
base: "https://blazing-fast-pics.cdn.imgeng.in/images/pic_1.jpg",
32+
cdn: "imageengine",
33+
format: undefined,
34+
width: undefined,
35+
height: undefined,
36+
params: {},
37+
};
38+
assertEquals(parsed, expected);
39+
});
40+
});
41+
42+
Deno.test("ImageEngine transformer", async (t) => {
43+
await t.step("should format a URL", () => {
44+
const result = transform({
45+
url: img,
46+
width: 200,
47+
height: 100,
48+
format: "webp"
49+
});
50+
assertEquals(
51+
result?.toString(),
52+
"https://blazing-fast-pics.cdn.imgeng.in/images/pic_1.jpg?imgeng=/w_200/h_100/f_webp/m_cropbox",
53+
);
54+
});
55+
await t.step("should not set height if not provided", () => {
56+
const result = transform({ url: img, width: 200, format: "jpg" });
57+
assertEquals(
58+
result?.toString(),
59+
"https://blazing-fast-pics.cdn.imgeng.in/images/pic_1.jpg?imgeng=/w_200/f_jpg/m_cropbox",
60+
);
61+
});
62+
await t.step("should not set fit=cropbox if another value exists", () => {
63+
const url = new URL(transformImage);
64+
const result = transform({ url, width: 200 });
65+
assertEquals(
66+
result?.toString(),
67+
"https://blazing-fast-pics.cdn.imgeng.in/images/pic_1.jpg?imgeng=/m_outside/f_png/w_200",
68+
);
69+
});
70+
});

src/transformers/imageengine.ts

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import { UrlParser, UrlTransformer } from "../types.ts";
2+
import { toUrl } from "../utils.ts";
3+
4+
export interface ImageEngineParams {
5+
host?: number;
6+
width?: number;
7+
height?: number;
8+
autoWidthWithFallback?: number;
9+
auto_width_fallback?: number;
10+
scaleToScreenWidth?: number;
11+
scale_to_screen_width?: number;
12+
crop?: number;
13+
outputFormat?: string;
14+
format?: string;
15+
fitMethod?: string;
16+
fit?: string;
17+
compression?: number;
18+
sharpness?: number;
19+
rotate?: number;
20+
keepMeta?: boolean;
21+
keep_meta?: boolean;
22+
noOptimization?: boolean;
23+
no_optimization?: boolean;
24+
force_download?: boolean;
25+
max_device_pixel_ratio?: number;
26+
maxDevicePixelRatio?: number;
27+
}
28+
29+
export const OBJECT_TO_DIRECTIVES_MAP: { [key: string]: string } = {
30+
width: "w",
31+
height: "h",
32+
autoWidthWithFallback: "w_auto",
33+
auto_width_fallback: "w_auto",
34+
scaleToScreenWidth: "pc",
35+
scale_to_screen_width: "pc",
36+
crop: "cr",
37+
outputFormat: "f",
38+
format: "f",
39+
fit: "m",
40+
fitMethod: "m",
41+
compression: "cmpr",
42+
sharpness: "s",
43+
rotate: "r",
44+
inline: "in",
45+
keepMeta: "meta",
46+
keep_meta: "meta",
47+
noOptimization: "pass",
48+
no_optimization: "pass",
49+
force_download: "dl",
50+
max_device_pixel_ratio: "maxdpr",
51+
maxDevicePixelRatio: "maxdpr"
52+
};
53+
54+
export const parse: UrlParser<ImageEngineParams> = (
55+
imageUrl,
56+
) => {
57+
const parsedUrl = toUrl(imageUrl);
58+
const paramArray = getParameterArray(parsedUrl);
59+
const baseUrl = getBaseUrl(parsedUrl);
60+
let width = undefined, height = undefined,format = undefined;
61+
const params: Record<string, string> = {};
62+
if(paramArray.length>0){
63+
paramArray.forEach((para:string) => {
64+
let key_value = para.split("_")
65+
if(key_value.length>1){
66+
switch(key_value[0]){
67+
case 'w':
68+
width = Number(key_value[1]);
69+
break;
70+
case 'h':
71+
height = Number(key_value[1]);
72+
break;
73+
case 'f':
74+
format = key_value[1];
75+
break;
76+
default:
77+
if( Object.values(OBJECT_TO_DIRECTIVES_MAP).includes(key_value[0])){
78+
let directive: string = getDirective(key_value[0])
79+
params[directive] = key_value[1];
80+
}
81+
}
82+
}
83+
});
84+
}
85+
return {
86+
base: baseUrl,
87+
width,
88+
height,
89+
format,
90+
params,
91+
cdn: "imageengine",
92+
};
93+
};
94+
95+
export function getDirective(key: string):string{
96+
let keyArray = Object.keys(OBJECT_TO_DIRECTIVES_MAP)
97+
let directive = keyArray.find(k => OBJECT_TO_DIRECTIVES_MAP[k] === key) || ""
98+
return directive;
99+
};
100+
101+
export function getParameterArray(url: URL){
102+
let url_string = url.toString();
103+
let paramArray:any = []
104+
if(url_string){
105+
let splitURL: string[] = url_string.split("imgeng=");
106+
if(splitURL.length>1){
107+
paramArray = splitURL[1].split("/")
108+
}
109+
}
110+
return paramArray;
111+
};
112+
113+
export function getBaseUrl(url: URL){
114+
let url_string = url.toString();
115+
let baseUrl:string = ""
116+
if(url_string){
117+
let splitURL: string[] = url_string.split("imgeng=");
118+
if(splitURL.length>1){
119+
baseUrl = splitURL[0].slice(0,-1)
120+
}
121+
else
122+
baseUrl = url_string;
123+
}
124+
return baseUrl;
125+
};
126+
127+
export const transform: UrlTransformer = (
128+
{ url: originalUrl, width, height, format},
129+
) => {
130+
const url = toUrl(originalUrl);
131+
const src = getBaseUrl(url);
132+
let directives: Record<string, any> = {};
133+
const param: [] = url.toString() === src ? [] : getParameterArray(url);
134+
if(param.length){
135+
directives = getDirectives(param)
136+
}
137+
if(width)
138+
directives["width"] = width;
139+
if(height)
140+
directives["height"] = height;
141+
if(format)
142+
directives["format"] = format;
143+
if(!directives.hasOwnProperty('fit')){
144+
directives = {...directives,"fit": "cropbox"};
145+
}
146+
let directives_string = build_IE_directives(directives);
147+
let query_string = build_IE_query_string(directives_string);
148+
let query_prefix = query_string === "" ? "" : (src.includes("?") ? "&" : "?");
149+
return `${src}${query_prefix}${query_string}`;
150+
};
151+
152+
export function build_IE_directives(directives:any): string {
153+
return Object.entries(directives).reduce((acc, [k, v]) => {
154+
return acc + maybe_create_directive(k, v)
155+
}, "");
156+
};
157+
158+
export function build_IE_query_string(directives_string: string): string {
159+
if (directives_string && directives_string !== "") {
160+
return `imgeng=${directives_string}`;
161+
}
162+
return ""
163+
};
164+
165+
export function maybe_create_directive(directive: string, value: any): string {
166+
let translated_directive = OBJECT_TO_DIRECTIVES_MAP[directive];
167+
168+
if (translated_directive && (value || value === 0)) {
169+
return `/${translated_directive}_${value}`;
170+
}
171+
return "";
172+
};
173+
174+
export function getDirectives(paramArray: []): {}{
175+
let directives: Record<string, any> = {};
176+
paramArray.forEach((para:string)=>{
177+
let keyValue = para.split("_");
178+
if(keyValue.length>1){
179+
let key = keyValue[0];
180+
let value = keyValue[1];
181+
let directiveKey = getDirective(key);
182+
if(directiveKey){
183+
directives[directiveKey] = value;
184+
}
185+
}
186+
})
187+
return directives
188+
}

src/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export type ImageCdn =
9090
| "nextjs"
9191
| "scene7"
9292
| "keycdn"
93-
| "directus";
93+
| "directus"
94+
| "imageengine";
9495

9596
export type SupportedImageCdn = ImageCdn;

0 commit comments

Comments
 (0)