UNPKG

@supernaut/next-image-storyblok-loader

Version:
1 lines 22.1 kB
{"version":3,"sources":["../src/lib/constants.ts","../src/lib/get-storyblok-src-host.ts","../src/lib/parse-storyblok-src.ts","../src/lib/get-storyblok-image-filename.ts","../src/lib/get-storyblok-image-quality.ts","../src/lib/get-storyblok-image-filters.ts","../src/lib/storyblok-image-size-limit.ts","../src/lib/get-storyblok-image-size.ts","../src/lib/img-src-is-storyblok.ts","../src/lib/get-storyblok-src.ts","../src/lib/get-storyblok-image-loader.ts","../src/lib/storyblok-image-loader.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export const IMAGE_SIZE_LIMIT = 4000 as const;\nexport const DEFAULT_HOST = \"a.storyblok.com\" as const;\n","import { DEFAULT_HOST } from \"./constants\";\n\n/**\n * Gets the host for Storyblok images. Defaults to \"a.storyblok.com\"\n * if no override is provided or STORYBLOK_IMAGE_LOADER_HOST environment\n * variable isn't set.\n * @param {string} override Manual override for the host\n * @returns\n */\nexport function getStoryblokSrcHost(override?: string): string {\n // Regex to match a hostname with an optional port\n const pattern = /^\\w+(\\.\\w+)*(:\\d+)?$/;\n\n // Return override if set and valid\n if (override && pattern.test(override)) {\n return override;\n }\n\n // Return environment variable if set and valid\n if (\n process.env.STORYBLOK_IMAGE_LOADER_HOST &&\n pattern.test(process.env.STORYBLOK_IMAGE_LOADER_HOST)\n ) {\n return process.env.STORYBLOK_IMAGE_LOADER_HOST;\n }\n\n // If no valid override or environment variable is provided, return default\n return DEFAULT_HOST;\n}\n","import type { ParsedStoryblokSrc } from \"../types/parsed-storyblok-src\";\nimport type { StoryblokImageFormat } from \"../types/storyblok-image-format\";\nimport type { StoryblokImageLoaderOptions } from \"../types/storyblok-image-loader-options\";\nimport type { StoryblokImageResize } from \"../types/storyblok-image-resize\";\n\nimport { StoryblokImageFocus } from \"../types/storyblok-image-focus\";\nimport { getStoryblokSrcHost } from \"./get-storyblok-src-host\";\n\n// Precompiled regex patterns (compiled once per module load)\nconst SIZE_PATTERN = /^(\\d+)x(\\d+)$/;\nconst FOCAL_PATTERN = /focal\\((\\d+)x(\\d+):(\\d+)x(\\d+)\\)/;\n\n// Allow only explicitly supported formats to avoid unsafe casting\nconst ALLOWED_FORMATS: readonly StoryblokImageFormat[] = [\n \"avif\",\n \"jpeg\",\n \"png\",\n \"webp\",\n];\n\ntype ParsedFilters = {\n focus?: StoryblokImageFocus;\n format?: StoryblokImageFormat;\n quality?: number;\n};\n\nexport function parseStoryblokSrc(\n src: string,\n options?: StoryblokImageLoaderOptions,\n): ParsedStoryblokSrc {\n let url: URL;\n try {\n url = new URL(src);\n } catch (error) {\n console.error(error);\n throw new TypeError(`Invalid Storyblok image URL: ${src}`);\n }\n\n // Normalize host (retain original protocol)\n url.host = getStoryblokSrcHost(options?.host);\n\n const pathSegments = url.pathname.split(\"/\").filter(Boolean); // drop empty leading segment\n const mIndex = pathSegments.indexOf(\"m\");\n\n // Base filename should point to the original asset root, i.e., everything before /m/... (if present)\n // Examples:\n // /f/123456/image.jpg -> /f/123456/image.jpg (no transformation)\n // /f/123456/m/800x600/filters:.../image.jpg -> /f/123456\n const baseSegments =\n mIndex >= 0 ? pathSegments.slice(0, mIndex) : pathSegments;\n // If we have a direct asset (no m) keep full path; if transformed, only keep first 2 segments after /f\n // Previous implementation sliced first 6 raw splits; replicating intent by reconstructing then trimming '/m'\n // Build a preliminary filename URL\n const basePath = `/${baseSegments.join(\"/\")}`;\n const filename = new URL(basePath, url).href;\n\n const result: ParsedStoryblokSrc = { filename };\n\n // Size parsing (only when 'm' segment is present and followed by \\d+x\\d+)\n if (mIndex >= 0) {\n const sizeCandidate = pathSegments[mIndex + 1];\n const sizeMatch = SIZE_PATTERN.exec(sizeCandidate || \"\");\n if (sizeMatch) {\n const width = Number.parseInt(sizeMatch[1], 10);\n const height = Number.parseInt(sizeMatch[2], 10);\n if (!Number.isNaN(width) && !Number.isNaN(height)) {\n const resize: StoryblokImageResize = { height, width };\n result.resize = resize;\n }\n }\n }\n\n // Filters parsing (look for the first segment that starts with filters:)\n const filtersSegment = pathSegments.find((segment) =>\n segment.startsWith(\"filters:\"),\n );\n const { focus, format, quality } = parseFilters(filtersSegment);\n if (typeof quality !== \"undefined\") {\n result.quality = quality;\n }\n if (typeof format !== \"undefined\") {\n result.format = format;\n }\n if (typeof focus !== \"undefined\") {\n result.focus = focus;\n }\n\n return result;\n}\n\nfunction parseFilters(segment?: string): ParsedFilters {\n if (!segment || !segment.startsWith(\"filters:\")) return {};\n // Remove the leading \"filters:\" then split into individual filters preserving order\n const rawFilters = segment\n .substring(\"filters:\".length)\n // Convert :x to __x so we can split safely while keeping order\n .replace(/:([a-z])/g, \"__$1\")\n .split(\"__\");\n\n const parsed: ParsedFilters = {};\n for (const filter of rawFilters) {\n if (filter.startsWith(\"quality\")) {\n const value = Number.parseInt(\n filter.replace(/quality\\((.+)\\)/, \"$1\"),\n 10,\n );\n if (!Number.isNaN(value) && value >= 1 && value <= 100) {\n parsed.quality = value;\n }\n } else if (filter.startsWith(\"format\")) {\n const value = filter.replace(\n /format\\((.+)\\)/,\n \"$1\",\n ) as StoryblokImageFormat;\n if (ALLOWED_FORMATS.includes(value)) {\n parsed.format = value;\n }\n } else if (filter.startsWith(\"focal\") && FOCAL_PATTERN.test(filter)) {\n parsed.focus = filter.replace(\n FOCAL_PATTERN,\n \"$1x$2:$3x$4\",\n ) as StoryblokImageFocus;\n }\n }\n return parsed;\n}\n","import type { AssetStoryblok } from \"../types/asset-storyblok\";\nimport type { StoryblokImageLoaderOptions } from \"../types/storyblok-image-loader-options\";\n\nimport { parseStoryblokSrc } from \"./parse-storyblok-src\";\n\nexport function getStoryblokImageFilename(\n image: AssetStoryblok | string | undefined,\n imageOptions?: null | StoryblokImageLoaderOptions,\n) {\n let filename = \"\";\n const options: StoryblokImageLoaderOptions = imageOptions || {};\n if (image && typeof image !== \"string\" && !!image?.focus) {\n options.focus = image?.focus;\n }\n if (typeof image === \"string\") {\n const parsed = parseStoryblokSrc(image, options);\n filename = parsed.filename;\n if (typeof parsed.focus !== \"undefined\") {\n options.focus = parsed.focus;\n }\n if (typeof parsed.format !== \"undefined\") {\n options.format = parsed.format;\n }\n if (typeof parsed.quality !== \"undefined\") {\n options.quality = parsed.quality;\n }\n if (typeof parsed.resize !== \"undefined\") {\n options.resize = parsed.resize;\n }\n }\n if (typeof image !== \"string\" && image?.filename) {\n filename = image?.filename;\n }\n\n return {\n filename,\n options,\n };\n}\n","export function getStoryblokImageQuality(input: number): string {\n const quality = Math.floor(Math.max(0, Math.min(100, input)));\n return `quality(${quality})`;\n}\n","import type { StoryblokImageLoaderOptions } from \"../types/storyblok-image-loader-options\";\n\nimport { getStoryblokImageQuality } from \"./get-storyblok-image-quality\";\n\nexport function getStoryblokImageFilters(\n options?: StoryblokImageLoaderOptions,\n): string | undefined {\n const filters: string[] = [];\n\n if (options?.format) {\n filters.push(`format(${options.format})`);\n }\n if (typeof options?.quality === \"number\") {\n filters.push(getStoryblokImageQuality(options.quality));\n }\n if (options?.resize && options?.focus) {\n filters.push(`focal(${options.focus})`);\n }\n const filtersString = filters.filter(Boolean).join(\":\");\n return filtersString ? `filters:${filtersString}` : undefined;\n}\n","export const STORYBLOK_IMAGE_SIZE_LIMIT = 4000 as const;\n","import type { StoryblokImageResize } from \"../types/storyblok-image-resize\";\n\nimport { STORYBLOK_IMAGE_SIZE_LIMIT } from \"./storyblok-image-size-limit\";\n\nexport function getStoryblokImageSize(\n resize: StoryblokImageResize,\n): string | undefined {\n if (!resize.width) {\n return;\n }\n\n let { height = 0, width } = resize;\n\n if (width > height && width > STORYBLOK_IMAGE_SIZE_LIMIT) {\n width = STORYBLOK_IMAGE_SIZE_LIMIT;\n height = resize.height\n ? Math.floor((resize.height / resize.width) * width)\n : 0;\n }\n if (resize.height && height > width && height > STORYBLOK_IMAGE_SIZE_LIMIT) {\n height = STORYBLOK_IMAGE_SIZE_LIMIT;\n width = Math.floor((resize.width / resize.height) * height);\n }\n return `${width}x${height}`;\n}\n","import { DEFAULT_HOST } from \"./constants\";\n\nexport function imgSrcIsStoryblok(\n src: string,\n host: string = DEFAULT_HOST,\n): boolean {\n const url = new URL(src);\n return url.host.includes(host);\n}\n","import type { AssetStoryblok } from \"../types/asset-storyblok\";\nimport type { StoryblokImageLoaderOptions } from \"../types/storyblok-image-loader-options\";\n\nimport { getStoryblokImageFilename } from \"./get-storyblok-image-filename\";\nimport { getStoryblokImageFilters } from \"./get-storyblok-image-filters\";\nimport { getStoryblokImageSize } from \"./get-storyblok-image-size\";\nimport { imgSrcIsStoryblok } from \"./img-src-is-storyblok\";\n\n/**\n * Get Storyblok image src URL from given asset and options\n * @param image Storyblok asset or image URL\n * @param imageOptions Options for constructing the image URL\n * @returns URL string or undefined if no URL could be constructed\n */\nexport function getStoryblokSrc(\n image: AssetStoryblok | string | undefined,\n imageOptions?: null | StoryblokImageLoaderOptions,\n): string | undefined {\n // Exit if image is a string and not a Storyblok asset URL\n if (\n typeof image === \"string\" &&\n !imgSrcIsStoryblok(image, imageOptions?.host)\n ) {\n return image;\n }\n\n // Get filename\n const { filename, options } = getStoryblokImageFilename(image, imageOptions);\n\n // Exit if no filename could be constructed\n if (!filename) {\n return;\n }\n\n // Create an array of path based options\n const parts: (string | undefined)[] = [filename, \"m\"];\n\n // Add resize options if set\n if (options?.resize) {\n parts.push(getStoryblokImageSize(options.resize));\n }\n\n // Add smart format option if not explicitly disabled\n if ((options?.resize || options?.focus) && options?.smart !== false) {\n parts.push(\"smart\");\n }\n\n // Add filters if set\n if (typeof options !== \"undefined\") {\n parts.push(getStoryblokImageFilters(options));\n }\n\n // Join parts into a path string\n const src = parts.filter(Boolean).join(\"/\");\n\n // If a prefix is set, remove host and prepend prefix\n if (imageOptions?.prefix || process.env.STORYBLOK_IMAGE_LOADER_PREFIX) {\n const prefix =\n imageOptions?.prefix || process.env.STORYBLOK_IMAGE_LOADER_PREFIX;\n const url = new URL(src);\n return `${prefix}${url.pathname}`.replace(\"//\", \"/\");\n }\n\n // Return the constructed URL\n return src;\n}\n","import type { ImageLoader } from \"next/image\";\n\nimport type { StoryblokImageLoaderOptions } from \"../types/storyblok-image-loader-options\";\n\nimport { getStoryblokSrc } from \"./get-storyblok-src\";\nimport { imgSrcIsStoryblok } from \"./img-src-is-storyblok\";\nimport { parseStoryblokSrc } from \"./parse-storyblok-src\";\n\n/**\n * Get a Storyblok image loader for Next.js Image component.\n * @param options Options for the loader\n * @returns Image loader function\n */\nexport function getStoryblokImageLoader(\n options: StoryblokImageLoaderOptions = {},\n): ImageLoader {\n return ({ quality, src, width: inputWidth }) => {\n // Check if it's a Storyblok URL first\n if (!imgSrcIsStoryblok(src, options.host)) {\n return src;\n }\n\n const url = new URL(src);\n\n const parsed = parseStoryblokSrc(src);\n\n const parts = url.pathname.split(\"/\");\n const size = parts[3]?.split(\"x\");\n\n if (!size || size.length < 2) {\n // If URL doesn't have the expected format, use getStoryblokSrc directly\n const loaderOptions: StoryblokImageLoaderOptions = {\n ...options,\n resize: { width: inputWidth },\n };\n if (typeof quality === \"number\") {\n loaderOptions.quality = quality;\n }\n return getStoryblokSrc(src, loaderOptions) || src;\n }\n\n const originalWidth = Number.parseInt(size[0], 10);\n const originalHeight = Number.parseInt(size[1], 10);\n\n const originalAspectRatio = originalWidth / originalHeight;\n let resizedAspectRatio = originalAspectRatio;\n\n if (parsed.resize?.height) {\n resizedAspectRatio = parsed.resize.width / parsed.resize.height;\n }\n\n const resize = {\n height: resizedAspectRatio\n ? Math.floor(inputWidth / resizedAspectRatio)\n : 0,\n width: inputWidth,\n };\n\n if (typeof quality !== \"undefined\") {\n options.quality = quality;\n }\n if (inputWidth !== undefined && typeof resize !== \"undefined\") {\n options.resize = resize;\n }\n\n return getStoryblokSrc(src, options) || src;\n };\n}\n","import type { ImageLoader } from \"next/image\";\n\nimport { getStoryblokImageLoader } from \"./get-storyblok-image-loader\";\n\n/**\n * Storyblok image loader for Next.js Image component with default options.\n */\nexport const storyblokImageLoader: ImageLoader = getStoryblokImageLoader();\n"]}