@supernaut/next-image-storyblok-loader
Version:
Storyblok image loader for the Next.js Image component
258 lines (245 loc) • 8.04 kB
JavaScript
;
// src/lib/constants.ts
var DEFAULT_HOST = "a.storyblok.com";
// src/lib/get-storyblok-src-host.ts
function getStoryblokSrcHost(override) {
const pattern = /^\w+(\.\w+)*(:\d+)?$/;
if (override && pattern.test(override)) {
return override;
}
if (process.env.STORYBLOK_IMAGE_LOADER_HOST && pattern.test(process.env.STORYBLOK_IMAGE_LOADER_HOST)) {
return process.env.STORYBLOK_IMAGE_LOADER_HOST;
}
return DEFAULT_HOST;
}
// src/lib/parse-storyblok-src.ts
var SIZE_PATTERN = /^(\d+)x(\d+)$/;
var FOCAL_PATTERN = /focal\((\d+)x(\d+):(\d+)x(\d+)\)/;
var ALLOWED_FORMATS = [
"avif",
"jpeg",
"png",
"webp"
];
function parseStoryblokSrc(src, options) {
let url;
try {
url = new URL(src);
} catch (error) {
console.error(error);
throw new TypeError(`Invalid Storyblok image URL: ${src}`);
}
url.host = getStoryblokSrcHost(options?.host);
const pathSegments = url.pathname.split("/").filter(Boolean);
const mIndex = pathSegments.indexOf("m");
const baseSegments = mIndex >= 0 ? pathSegments.slice(0, mIndex) : pathSegments;
const basePath = `/${baseSegments.join("/")}`;
const filename = new URL(basePath, url).href;
const result = { filename };
if (mIndex >= 0) {
const sizeCandidate = pathSegments[mIndex + 1];
const sizeMatch = SIZE_PATTERN.exec(sizeCandidate || "");
if (sizeMatch) {
const width = Number.parseInt(sizeMatch[1], 10);
const height = Number.parseInt(sizeMatch[2], 10);
if (!Number.isNaN(width) && !Number.isNaN(height)) {
const resize = { height, width };
result.resize = resize;
}
}
}
const filtersSegment = pathSegments.find(
(segment) => segment.startsWith("filters:")
);
const { focus, format, quality } = parseFilters(filtersSegment);
if (typeof quality !== "undefined") {
result.quality = quality;
}
if (typeof format !== "undefined") {
result.format = format;
}
if (typeof focus !== "undefined") {
result.focus = focus;
}
return result;
}
function parseFilters(segment) {
if (!segment || !segment.startsWith("filters:")) return {};
const rawFilters = segment.substring("filters:".length).replace(/:([a-z])/g, "__$1").split("__");
const parsed = {};
for (const filter of rawFilters) {
if (filter.startsWith("quality")) {
const value = Number.parseInt(
filter.replace(/quality\((.+)\)/, "$1"),
10
);
if (!Number.isNaN(value) && value >= 1 && value <= 100) {
parsed.quality = value;
}
} else if (filter.startsWith("format")) {
const value = filter.replace(
/format\((.+)\)/,
"$1"
);
if (ALLOWED_FORMATS.includes(value)) {
parsed.format = value;
}
} else if (filter.startsWith("focal") && FOCAL_PATTERN.test(filter)) {
parsed.focus = filter.replace(
FOCAL_PATTERN,
"$1x$2:$3x$4"
);
}
}
return parsed;
}
// src/lib/get-storyblok-image-filename.ts
function getStoryblokImageFilename(image, imageOptions) {
let filename = "";
const options = imageOptions || {};
if (image && typeof image !== "string" && !!image?.focus) {
options.focus = image?.focus;
}
if (typeof image === "string") {
const parsed = parseStoryblokSrc(image, options);
filename = parsed.filename;
if (typeof parsed.focus !== "undefined") {
options.focus = parsed.focus;
}
if (typeof parsed.format !== "undefined") {
options.format = parsed.format;
}
if (typeof parsed.quality !== "undefined") {
options.quality = parsed.quality;
}
if (typeof parsed.resize !== "undefined") {
options.resize = parsed.resize;
}
}
if (typeof image !== "string" && image?.filename) {
filename = image?.filename;
}
return {
filename,
options
};
}
// src/lib/get-storyblok-image-quality.ts
function getStoryblokImageQuality(input) {
const quality = Math.floor(Math.max(0, Math.min(100, input)));
return `quality(${quality})`;
}
// src/lib/get-storyblok-image-filters.ts
function getStoryblokImageFilters(options) {
const filters = [];
if (options?.format) {
filters.push(`format(${options.format})`);
}
if (typeof options?.quality === "number") {
filters.push(getStoryblokImageQuality(options.quality));
}
if (options?.resize && options?.focus) {
filters.push(`focal(${options.focus})`);
}
const filtersString = filters.filter(Boolean).join(":");
return filtersString ? `filters:${filtersString}` : void 0;
}
// src/lib/storyblok-image-size-limit.ts
var STORYBLOK_IMAGE_SIZE_LIMIT = 4e3;
// src/lib/get-storyblok-image-size.ts
function getStoryblokImageSize(resize) {
if (!resize.width) {
return;
}
let { height = 0, width } = resize;
if (width > height && width > STORYBLOK_IMAGE_SIZE_LIMIT) {
width = STORYBLOK_IMAGE_SIZE_LIMIT;
height = resize.height ? Math.floor(resize.height / resize.width * width) : 0;
}
if (resize.height && height > width && height > STORYBLOK_IMAGE_SIZE_LIMIT) {
height = STORYBLOK_IMAGE_SIZE_LIMIT;
width = Math.floor(resize.width / resize.height * height);
}
return `${width}x${height}`;
}
// src/lib/img-src-is-storyblok.ts
function imgSrcIsStoryblok(src, host = DEFAULT_HOST) {
const url = new URL(src);
return url.host.includes(host);
}
// src/lib/get-storyblok-src.ts
function getStoryblokSrc(image, imageOptions) {
if (typeof image === "string" && !imgSrcIsStoryblok(image, imageOptions?.host)) {
return image;
}
const { filename, options } = getStoryblokImageFilename(image, imageOptions);
if (!filename) {
return;
}
const parts = [filename, "m"];
if (options?.resize) {
parts.push(getStoryblokImageSize(options.resize));
}
if ((options?.resize || options?.focus) && options?.smart !== false) {
parts.push("smart");
}
if (typeof options !== "undefined") {
parts.push(getStoryblokImageFilters(options));
}
const src = parts.filter(Boolean).join("/");
if (imageOptions?.prefix || process.env.STORYBLOK_IMAGE_LOADER_PREFIX) {
const prefix = imageOptions?.prefix || process.env.STORYBLOK_IMAGE_LOADER_PREFIX;
const url = new URL(src);
return `${prefix}${url.pathname}`.replace("//", "/");
}
return src;
}
// src/lib/get-storyblok-image-loader.ts
function getStoryblokImageLoader(options = {}) {
return ({ quality, src, width: inputWidth }) => {
if (!imgSrcIsStoryblok(src, options.host)) {
return src;
}
const url = new URL(src);
const parsed = parseStoryblokSrc(src);
const parts = url.pathname.split("/");
const size = parts[3]?.split("x");
if (!size || size.length < 2) {
const loaderOptions = {
...options,
resize: { width: inputWidth }
};
if (typeof quality === "number") {
loaderOptions.quality = quality;
}
return getStoryblokSrc(src, loaderOptions) || src;
}
const originalWidth = Number.parseInt(size[0], 10);
const originalHeight = Number.parseInt(size[1], 10);
const originalAspectRatio = originalWidth / originalHeight;
let resizedAspectRatio = originalAspectRatio;
if (parsed.resize?.height) {
resizedAspectRatio = parsed.resize.width / parsed.resize.height;
}
const resize = {
height: resizedAspectRatio ? Math.floor(inputWidth / resizedAspectRatio) : 0,
width: inputWidth
};
if (typeof quality !== "undefined") {
options.quality = quality;
}
if (inputWidth !== void 0 && typeof resize !== "undefined") {
options.resize = resize;
}
return getStoryblokSrc(src, options) || src;
};
}
// src/lib/storyblok-image-loader.ts
var storyblokImageLoader = getStoryblokImageLoader();
exports.getStoryblokImageLoader = getStoryblokImageLoader;
exports.getStoryblokSrc = getStoryblokSrc;
exports.imgSrcIsStoryblok = imgSrcIsStoryblok;
exports.parseStoryblokSrc = parseStoryblokSrc;
exports.storyblokImageLoader = storyblokImageLoader;
//# sourceMappingURL=index.cjs.map
//# sourceMappingURL=index.cjs.map