@nuxt/image
Version:
Nuxt Image Module
217 lines (216 loc) • 7.14 kB
JavaScript
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;
}
}