UNPKG

@nuxt/image

Version:

Nuxt Image Module

217 lines (216 loc) 7.14 kB
import { defu } from "defu"; import { hasProtocol, parseURL, joinURL, withLeadingSlash } from "ufo"; import { imageMeta } from "./utils/meta.js"; import { checkDensities, parseDensities, parseSize, parseSizes } from "./utils/index.js"; import { prerenderStaticImages } from "./utils/prerender.js"; export function createImage(globalOptions) { const ctx = { options: globalOptions }; const getImage = (input, options = {}) => { const image = resolveImage(ctx, input, options); if (import.meta.server && import.meta.prerender) { prerenderStaticImages(image.url); } return image; }; const $img = (input, modifiers = {}, options = {}) => { return getImage(input, { ...options, modifiers: defu(modifiers, options.modifiers || {}) }).url; }; for (const presetName in globalOptions.presets) { $img[presetName] = (source, modifiers, options) => $img(source, modifiers, { ...globalOptions.presets[presetName], ...options }); } $img.options = globalOptions; $img.getImage = getImage; $img.getMeta = (input, options) => getMeta(ctx, input, options); $img.getSizes = (input, options) => getSizes(ctx, input, options); ctx.$img = $img; return $img; } async function getMeta(ctx, input, options) { const image = resolveImage(ctx, input, { ...options }); if (typeof image.getMeta === "function") { return await image.getMeta(); } else { return await imageMeta(ctx, image.url); } } function resolveImage(ctx, input, options) { if (input && typeof input !== "string") { throw new TypeError(`input must be a string (received ${typeof input}: ${JSON.stringify(input)})`); } if (!input || input.startsWith("data:")) { return { url: input }; } const { provider, defaults } = getProvider(ctx, options.provider || ctx.options.provider); const preset = getPreset(ctx, options.preset); input = hasProtocol(input) ? input : withLeadingSlash(input); if (!provider.supportsAlias) { for (const base in ctx.options.alias) { if (input.startsWith(base)) { const alias = ctx.options.alias[base]; if (alias) { input = joinURL(alias, input.slice(base.length)); } } } } if (provider.validateDomains && hasProtocol(input)) { const inputHost = parseURL(input).host; if (!ctx.options.domains.find((d) => d === inputHost)) { return { url: input }; } } const _options = defu(options, preset, defaults); _options.modifiers = { ..._options.modifiers }; const expectedFormat = _options.modifiers.format; if (_options.modifiers?.width) { _options.modifiers.width = parseSize(_options.modifiers.width); } if (_options.modifiers?.height) { _options.modifiers.height = parseSize(_options.modifiers.height); } const image = provider.getImage(input, _options, ctx); image.format = image.format || expectedFormat || ""; return image; } function getProvider(ctx, name) { const provider = ctx.options.providers[name]; if (!provider) { throw new Error("Unknown provider: " + name); } return provider; } function getPreset(ctx, name) { if (!name) { return {}; } if (!ctx.options.presets[name]) { throw new Error("Unknown preset: " + name); } return ctx.options.presets[name]; } function getSizes(ctx, input, opts) { const width = parseSize(opts.modifiers?.width); const height = parseSize(opts.modifiers?.height); const sizes = parseSizes(opts.sizes); const densities = opts.densities?.trim() ? parseDensities(opts.densities.trim()) : ctx.options.densities; checkDensities(densities); const hwRatio = width && height ? height / width : 0; const sizeVariants = []; const srcsetVariants = []; if (Object.keys(sizes).length >= 1) { for (const key in sizes) { const variant = getSizesVariant(key, String(sizes[key]), height, hwRatio, ctx); if (variant === void 0) { continue; } sizeVariants.push({ size: variant.size, screenMaxWidth: variant.screenMaxWidth, media: `(max-width: ${variant.screenMaxWidth}px)` }); for (const density of densities) { srcsetVariants.push({ width: variant._cWidth * density, src: getVariantSrc(ctx, input, opts, variant, density) }); } } finaliseSizeVariants(sizeVariants); } else { for (const density of densities) { const key = Object.keys(sizes)[0]; let variant = key ? getSizesVariant(key, String(sizes[key]), height, hwRatio, ctx) : void 0; if (variant === void 0) { variant = { size: "", screenMaxWidth: 0, _cWidth: opts.modifiers?.width, _cHeight: opts.modifiers?.height }; } srcsetVariants.push({ width: density, src: getVariantSrc(ctx, input, opts, variant, density) }); } } finaliseSrcsetVariants(srcsetVariants); const defaultVariant = srcsetVariants[srcsetVariants.length - 1]; const sizesVal = sizeVariants.length ? sizeVariants.map((v) => `${v.media ? v.media + " " : ""}${v.size}`).join(", ") : void 0; const suffix = sizesVal ? "w" : "x"; const srcsetVal = srcsetVariants.map((v) => `${v.src} ${v.width}${suffix}`).join(", "); return { sizes: sizesVal, srcset: srcsetVal, src: defaultVariant?.src }; } function getSizesVariant(key, size, height, hwRatio, ctx) { const screenMaxWidth = ctx.options.screens && ctx.options.screens[key] || Number.parseInt(key); const isFluid = size.endsWith("vw"); if (!isFluid && /^\d+$/.test(size)) { size = size + "px"; } if (!isFluid && !size.endsWith("px")) { return void 0; } let _cWidth = Number.parseInt(size); if (!screenMaxWidth || !_cWidth) { return void 0; } if (isFluid) { _cWidth = Math.round(_cWidth / 100 * screenMaxWidth); } const _cHeight = hwRatio ? Math.round(_cWidth * hwRatio) : height; return { size, screenMaxWidth, _cWidth, _cHeight }; } function getVariantSrc(ctx, input, opts, variant, density) { return ctx.$img( input, { ...opts.modifiers, width: variant._cWidth ? variant._cWidth * density : void 0, height: variant._cHeight ? variant._cHeight * density : void 0 }, opts ); } function finaliseSizeVariants(sizeVariants) { sizeVariants.sort((v1, v2) => v1.screenMaxWidth - v2.screenMaxWidth); let previousMedia = null; for (let i = sizeVariants.length - 1; i >= 0; i--) { const sizeVariant = sizeVariants[i]; if (sizeVariant.media === previousMedia) { sizeVariants.splice(i, 1); } previousMedia = sizeVariant.media; } for (let i = 0; i < sizeVariants.length; i++) { sizeVariants[i].media = sizeVariants[i + 1]?.media || ""; } } function finaliseSrcsetVariants(srcsetVariants) { srcsetVariants.sort((v1, v2) => v1.width - v2.width); let previousWidth = null; for (let i = srcsetVariants.length - 1; i >= 0; i--) { const sizeVariant = srcsetVariants[i]; if (sizeVariant.width === previousWidth) { srcsetVariants.splice(i, 1); } previousWidth = sizeVariant.width; } }