Skip to content

Commit

Permalink
feat: astro _image endpoint (#94)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt Kane <m@mk.gg>
  • Loading branch information
jlarmstrongiv and ascorbic committed Dec 5, 2023
1 parent 8d84f4d commit b015190
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 0 deletions.
1 change: 1 addition & 0 deletions data/paths.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"/_vercel/image": "vercel",
"/is/image": "scene7",
"/_ipx/": "ipx",
"/_image": "astro",
"/.netlify/images": "netlify"
}
2 changes: 2 additions & 0 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { parse as imageengine } from "./transformers/imageengine.ts";
import { parse as contentstack } from "./transformers/contentstack.ts";
import { parse as cloudflareImages } from "./transformers/cloudflare_images.ts";
import { parse as ipx } from "./transformers/ipx.ts";
import { parse as astro } from "./transformers/astro.ts";
import { parse as netlify } from "./transformers/netlify.ts";
import { parse as imagekit } from "./transformers/imagekit.ts";
import { ImageCdn, ParsedUrl, SupportedImageCdn, UrlParser } from "./types.ts";
Expand All @@ -42,6 +43,7 @@ export const parsers = {
contentstack,
"cloudflare_images": cloudflareImages,
ipx,
astro,
netlify,
imagekit,
};
Expand Down
2 changes: 2 additions & 0 deletions src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { transform as imageengine } from "./transformers/imageengine.ts";
import { transform as contentstack } from "./transformers/contentstack.ts";
import { transform as cloudflareImages } from "./transformers/cloudflare_images.ts";
import { transform as ipx } from "./transformers/ipx.ts";
import { transform as astro } from "./transformers/astro.ts";
import { transform as netlify } from "./transformers/netlify.ts";
import { transform as imagekit } from "./transformers/imagekit.ts";
import { ImageCdn, UrlTransformer } from "./types.ts";
Expand All @@ -43,6 +44,7 @@ export const getTransformer = (cdn: ImageCdn) => ({
contentstack,
"cloudflare_images": cloudflareImages,
ipx,
astro,
netlify,
imagekit,
}[cdn]);
Expand Down
87 changes: 87 additions & 0 deletions src/transformers/astro.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { assertEquals } from "https://deno.land/std@0.172.0/testing/asserts.ts";

import { ParsedUrl } from "../types.ts";
import { AstroParams, parse, transform } from "./astro.ts";

const img =
"https://images.ctfassets.net/aaaa/xxxx/yyyy/how-to-wow-a-customer.jpg";

Deno.test("astro parser", () => {
const parsed = parse(img);
const expected: ParsedUrl<AstroParams> = {
base: "/_image?href=https%3A%2F%2Fimages.ctfassets.net%2Faaaa%2Fxxxx%2Fyyyy%2Fhow-to-wow-a-customer.jpg",
params: {
"href": "https%3A%2F%2Fimages.ctfassets.net%2Faaaa%2Fxxxx%2Fyyyy%2Fhow-to-wow-a-customer.jpg"
},
cdn: "astro"
}
assertEquals(JSON.stringify(parsed), JSON.stringify(expected));
});

Deno.test("astro parser endpoint", () => {
const parsed = parse("/_image?href=https%3A%2F%2Fimages.ctfassets.net%2Faaaa%2Fxxxx%2Fyyyy%2Fhow-to-wow-a-customer.jpg");
const expected: ParsedUrl<AstroParams> = {
base: "/_image?href=https%3A%2F%2Fimages.ctfassets.net%2Faaaa%2Fxxxx%2Fyyyy%2Fhow-to-wow-a-customer.jpg",
params: {
"href": "https%3A%2F%2Fimages.ctfassets.net%2Faaaa%2Fxxxx%2Fyyyy%2Fhow-to-wow-a-customer.jpg"
},
cdn: "astro"
}
assertEquals(JSON.stringify(parsed), JSON.stringify(expected));
});


Deno.test("astro", async (t) => {
await t.step("should format a URL", () => {
const result = transform({
url: img,
width: 200,
height: 100,
});
assertEquals(
result?.toString(),
"/_image?href=https%3A%2F%2Fimages.ctfassets.net%2Faaaa%2Fxxxx%2Fyyyy%2Fhow-to-wow-a-customer.jpg&w=200&h=100",
);
});
await t.step("should not set height if not provided", () => {
const result = transform({ url: img, width: 200 });
assertEquals(
result?.toString(),
"/_image?href=https%3A%2F%2Fimages.ctfassets.net%2Faaaa%2Fxxxx%2Fyyyy%2Fhow-to-wow-a-customer.jpg&w=200",
);
});
await t.step("should delete height if not set", () => {
const url = new URL(img);
url.searchParams.set("h", "100");
const result = transform({ url, width: 200 });
assertEquals(
result?.toString(),
"/_image?href=https%3A%2F%2Fimages.ctfassets.net%2Faaaa%2Fxxxx%2Fyyyy%2Fhow-to-wow-a-customer.jpg&w=200",
);
});

await t.step("should round non-integer params", () => {
const result = transform({
url: img,
width: 200.6,
height: 100.2,
});
assertEquals(
result?.toString(),
"/_image?href=https%3A%2F%2Fimages.ctfassets.net%2Faaaa%2Fxxxx%2Fyyyy%2Fhow-to-wow-a-customer.jpg&w=201&h=100",
);
});

await t.step("should transform a local image with a relative base", () => {
const result = transform({
url: "/static/moose.png",
width: 100,
height: 200,
format: "webp",
});
assertEquals(
result?.toString(),
"/_image?href=%2Fstatic%2Fmoose.png&w=100&h=200&f=webp",
);
});
});
71 changes: 71 additions & 0 deletions src/transformers/astro.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { UrlParser, UrlTransformer, ShouldDelegateUrl } from "../types.ts";
import {
getNumericParam,
setParamIfDefined,
toUrl,
toCanonicalUrlString,
} from "../utils.ts";
import { getImageCdnForUrlByDomain } from "../detect.ts";

export interface AstroParams {
href: string;
quality?: string | number;
}

export const delegateUrl: ShouldDelegateUrl = (url) => {
const parsedUrl = toUrl(url);
const searchParamHref = parsedUrl.searchParams.get("href")
const decodedHref = typeof searchParamHref === "string"
? decodeURIComponent(searchParamHref)
: new URL(parsedUrl.pathname, parsedUrl.origin).toString()
const source = toCanonicalUrlString(toUrl(decodedHref));

if (!source || !source.startsWith("http")) {
return false;
}
const cdn = getImageCdnForUrlByDomain(source);
if (!cdn) {
return false;
}
return {
cdn,
url: source,
};
};

export const parse: UrlParser<AstroParams> = (url) => {
const parsedUrl = toUrl(url);
const searchParamHref = parsedUrl.searchParams.get("href")
const decodedHref = typeof searchParamHref === "string"
? decodeURIComponent(searchParamHref)
: new URL(parsedUrl.pathname, parsedUrl.origin).toString()
const encodedHref = encodeURIComponent(toCanonicalUrlString(toUrl(decodedHref)));
const width = getNumericParam(parsedUrl, "w") || undefined;
const height = getNumericParam(parsedUrl, "h") || undefined;
const format = parsedUrl.searchParams.get("f") || undefined;
const quality = parsedUrl.searchParams.get("q") || undefined;

return {
width,
height,
format,
base: `/_image?href=${encodedHref}`,
params: { quality, href: encodedHref },
cdn: "astro",
};
};

export const transform: UrlTransformer = (
{ url: originalUrl, width, height, format, },
) => {
const parsedUrl = toUrl(originalUrl);
const href = toCanonicalUrlString(new URL(parsedUrl.pathname, parsedUrl.origin))
const url = {searchParams: new URLSearchParams()} as URL

setParamIfDefined(url, "href", href, true, true);
setParamIfDefined(url, "w", width, true, true);
setParamIfDefined(url, "h", height, true, true);
setParamIfDefined(url, "f", format);

return `/_image?${url.searchParams.toString()}`;
};
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export type ImageCdn =
| "contentstack"
| "cloudflare_images"
| "ipx"
| "astro"
| "netlify"
| "imagekit";

Expand Down

0 comments on commit b015190

Please sign in to comment.