gatsby-source-prismic
Version:
Gatsby source plugin for building websites using Prismic as a data source
216 lines (185 loc) • 5.31 kB
text/typescript
import { Buffer } from "buffer";
import type { GatsbyCache } from "gatsby";
import type {
IGatsbyImageData,
IGatsbyImageHelperArgs,
ImageFormat,
} from "gatsby-plugin-image";
import {
generateImageData,
getLowResolutionImageURL,
} from "gatsby-plugin-image";
import type { ImgixURLParams } from "imgix-url-builder";
import { buildURL } from "imgix-url-builder";
import PQueue from "p-queue";
import { extname } from "path";
import { name as packageName } from "../../package.json";
import {
DEFAULT_IMGIX_PARAMS,
GatsbyImageDataLayoutKind,
GatsbyImageDataPlaceholderKind,
} from "../constants";
const imgixRequestQueue = new PQueue({ concurrency: 5 });
export const generateImageSource: IGatsbyImageHelperArgs["generateImageSource"] =
(sourceUrl, width, height, format, _fit, options?: GatsbyImageDataArgs) => {
const imgixParams: ImgixURLParams = {
...DEFAULT_IMGIX_PARAMS,
...options?.imgixParams,
w: width,
h: height,
};
if (format && format !== "auto") {
imgixParams.fm = format;
}
return {
src: buildURL(sourceUrl, imgixParams),
width,
height,
format,
};
};
type FetchBase64ImageConfig = {
url: string;
cache: GatsbyCache;
};
const fetchBase64Image = async (
config: FetchBase64ImageConfig,
): Promise<string | undefined> => {
const cacheKey = `base64___${config.url}`;
const cacheValue: string | undefined = await config.cache.get(cacheKey);
if (cacheValue) {
return cacheValue;
} else {
const fetch = (await import("node-fetch")).default;
const res = await imgixRequestQueue.add(async () => {
return await fetch(config.url);
});
if (res) {
const arrayBuffer = await res.arrayBuffer();
const buffer = Buffer.from(new Uint8Array(arrayBuffer));
const contentType = res.headers.get("content-type");
const base64 = `data:${contentType};base64,${buffer.toString("base64")}`;
config.cache.set(cacheKey, base64);
return base64;
}
}
};
/**
* The minimal data used when querying an image's pallete data using Imgix's
* API.
*
* @see Imgix Color Pallete Extration: https://docs.imgix.com/apis/rendering/color-palette/palette
*/
export interface ImgixPalleteLike {
colors: { hex: string }[];
dominant_colors?: {
vibrant?: { hex: string };
muted?: { hex: string };
};
}
/**
* Metadata that defines an image. This data is used to resolve Gatsby image
* objects.
*/
export interface ImageSource {
/**
* The image's Imgix URL.
*/
url: string;
/**
* The width of the image.
*/
width: number;
/**
* The height of the image.
*/
height: number;
}
export type GatsbyImageDataArgs = {
placeholder?: GatsbyImageDataPlaceholderKind;
imgixParams?: ImgixURLParams;
placeholderImgixParams?: ImgixURLParams;
aspectRatio?: number;
backgroundColor?: string;
breakpoints?: number[];
formats?: ImageFormat[];
layout?: GatsbyImageDataLayoutKind;
width?: number;
height?: number;
sizes?: string;
};
type ResolveGatsbyImageDataConfig = {
cache: GatsbyCache;
pluginName?: string;
buildURL?: typeof buildURL;
};
export const resolveGatsbyImageData = async (
image: ImageSource,
options: GatsbyImageDataArgs = {},
config: ResolveGatsbyImageDataConfig,
): Promise<IGatsbyImageData | null> => {
const imageDataArgs: IGatsbyImageHelperArgs = {
pluginName: config.pluginName || packageName,
sourceMetadata: {
width: image.width,
height: image.height,
format: "auto",
},
filename: image.url,
generateImageSource,
options,
layout: options.layout,
width: options.width,
height: options.height,
aspectRatio: options.aspectRatio,
backgroundColor: options.backgroundColor,
breakpoints: options.breakpoints,
formats: options.formats,
sizes: options.sizes,
};
const resolvedBuildURL = config.buildURL || buildURL;
const placeholderURL = resolvedBuildURL(imageDataArgs.filename, {
...DEFAULT_IMGIX_PARAMS,
...options.imgixParams,
...options.placeholderImgixParams,
});
if (options.placeholder === GatsbyImageDataPlaceholderKind.Blurred) {
imageDataArgs.placeholderURL = await fetchBase64Image({
url: getLowResolutionImageURL({
...imageDataArgs,
filename: placeholderURL,
}),
cache: config.cache,
});
}
if (options.placeholder === GatsbyImageDataPlaceholderKind.DominantColor) {
const cacheKey = `${GatsbyImageDataPlaceholderKind.DominantColor}___${placeholderURL}`;
const cacheValue: string | undefined = await config.cache.get(cacheKey);
if (cacheValue) {
imageDataArgs.backgroundColor = cacheValue;
} else {
const fileExtension = extname(new URL(placeholderURL).pathname);
// Imgix does not support `palette=json` for SVGs.
if (fileExtension !== ".svg") {
const palleteUrl = resolvedBuildURL(placeholderURL, {
palette: "json",
colors: 1,
});
const fetch = (await import("node-fetch")).default;
const res = await imgixRequestQueue.add(async () => {
return await fetch(palleteUrl);
});
if (res) {
const json = (await res.json()) as ImgixPalleteLike;
const dominantColor =
json.dominant_colors?.muted?.hex ||
json.dominant_colors?.vibrant?.hex ||
json.colors[0].hex;
config.cache.set(cacheKey, dominantColor);
imageDataArgs.backgroundColor = dominantColor;
}
}
}
}
return generateImageData(imageDataArgs);
};