UNPKG

gatsby-plugin-image

Version:

Adding responsive images to your site while maintaining high performance scores can be difficult to do manually. The Gatsby Image plugin handles the hard parts of producing images in multiple sizes and formats for you!

450 lines (449 loc) 20.3 kB
"use strict"; var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; }; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; exports.__esModule = true; exports.getDimensionsAndAspectRatio = exports.responsiveImageSizes = exports.fixedImageSizes = exports.calculateImageSizes = exports.generateImageData = exports.getLowResolutionImageURL = exports.setDefaultDimensions = exports.formatFromFilename = exports.getSrcSet = exports.getSizes = exports.EVERY_BREAKPOINT = exports.DEFAULT_BREAKPOINTS = void 0; /* eslint-disable no-unused-expressions */ var common_tags_1 = require("common-tags"); var camelcase_1 = __importDefault(require("camelcase")); var DEFAULT_PIXEL_DENSITIES = [0.25, 0.5, 1, 2]; exports.DEFAULT_BREAKPOINTS = [750, 1080, 1366, 1920]; exports.EVERY_BREAKPOINT = [ 320, 654, 768, 1024, 1366, 1600, 1920, 2048, 2560, 3440, 3840, 4096, ]; var DEFAULT_FLUID_WIDTH = 800; var DEFAULT_FIXED_WIDTH = 800; var DEFAULT_ASPECT_RATIO = 4 / 3; var warn = function (message) { return console.warn(message); }; var sortNumeric = function (a, b) { return a - b; }; var getSizes = function (width, layout) { switch (layout) { // If screen is wider than the max size, image width is the max size, // otherwise it's the width of the screen case "constrained": return "(min-width: ".concat(width, "px) ").concat(width, "px, 100vw"); // Image is always the same width, whatever the size of the screen case "fixed": return "".concat(width, "px"); // Image is always the width of the screen case "fullWidth": return "100vw"; default: return undefined; } }; exports.getSizes = getSizes; var getSrcSet = function (images) { return images.map(function (image) { return "".concat(image.src, " ").concat(image.width, "w"); }).join(",\n"); }; exports.getSrcSet = getSrcSet; function formatFromFilename(filename) { var dot = filename.lastIndexOf("."); if (dot !== -1) { var ext = filename.slice(dot + 1); if (ext === "jpeg") { return "jpg"; } if (ext.length === 3 || ext.length === 4) { return ext; } } return undefined; } exports.formatFromFilename = formatFromFilename; function setDefaultDimensions(args) { var _a = args.layout, layout = _a === void 0 ? "constrained" : _a, width = args.width, height = args.height, sourceMetadata = args.sourceMetadata, breakpoints = args.breakpoints, aspectRatio = args.aspectRatio, _b = args.formats, formats = _b === void 0 ? ["auto", "webp"] : _b; formats = formats.map(function (format) { return format.toLowerCase(); }); layout = (0, camelcase_1["default"])(layout); if (width && height) { return __assign(__assign({}, args), { formats: formats, layout: layout, aspectRatio: width / height }); } if (sourceMetadata.width && sourceMetadata.height && !aspectRatio) { aspectRatio = sourceMetadata.width / sourceMetadata.height; } if (layout === "fullWidth") { width = width || sourceMetadata.width || breakpoints[breakpoints.length - 1]; height = height || Math.round(width / (aspectRatio || DEFAULT_ASPECT_RATIO)); } else { if (!width) { if (height && aspectRatio) { width = height * aspectRatio; } else if (sourceMetadata.width) { width = sourceMetadata.width; } else if (height) { width = Math.round(height / DEFAULT_ASPECT_RATIO); } else { width = DEFAULT_FIXED_WIDTH; } } if (aspectRatio && !height) { height = Math.round(width / aspectRatio); } else if (!aspectRatio) { aspectRatio = width / height; } } return __assign(__assign({}, args), { width: width, height: height, aspectRatio: aspectRatio, layout: layout, formats: formats }); } exports.setDefaultDimensions = setDefaultDimensions; /** * Use this for getting an image for the blurred placeholder. This ensures the * aspect ratio and crop match the main image */ function getLowResolutionImageURL(args, width) { var _a; if (width === void 0) { width = 20; } args = setDefaultDimensions(args); var generateImageSource = args.generateImageSource, filename = args.filename, aspectRatio = args.aspectRatio; return (_a = generateImageSource(filename, width, Math.round(width / aspectRatio), args.sourceMetadata.format || "jpg", args.fit, args.options)) === null || _a === void 0 ? void 0 : _a.src; } exports.getLowResolutionImageURL = getLowResolutionImageURL; function generateImageData(args) { args = setDefaultDimensions(args); var pluginName = args.pluginName, sourceMetadata = args.sourceMetadata, generateImageSource = args.generateImageSource, layout = args.layout, fit = args.fit, options = args.options, width = args.width, height = args.height, filename = args.filename, _a = args.reporter, reporter = _a === void 0 ? { warn: warn } : _a, backgroundColor = args.backgroundColor, placeholderURL = args.placeholderURL; if (!pluginName) { reporter.warn("[gatsby-plugin-image] \"generateImageData\" was not passed a plugin name"); } if (typeof generateImageSource !== "function") { throw new Error("generateImageSource must be a function"); } if (!sourceMetadata || (!sourceMetadata.width && !sourceMetadata.height)) { // No metadata means we let the CDN handle max size etc, aspect ratio etc sourceMetadata = { width: width, height: height, format: (sourceMetadata === null || sourceMetadata === void 0 ? void 0 : sourceMetadata.format) || formatFromFilename(filename) || "auto" }; } else if (!sourceMetadata.format) { sourceMetadata.format = formatFromFilename(filename); } var formats = new Set(args.formats); if (formats.size === 0 || formats.has("auto") || formats.has("")) { formats["delete"]("auto"); formats["delete"](""); formats.add(sourceMetadata.format); } if (formats.has("jpg") && formats.has("png")) { reporter.warn("[".concat(pluginName, "] Specifying both 'jpg' and 'png' formats is not supported. Using 'auto' instead")); if (sourceMetadata.format === "jpg") { formats["delete"]("png"); } else { formats["delete"]("jpg"); } } var imageSizes = calculateImageSizes(__assign(__assign({}, args), { sourceMetadata: sourceMetadata })); var result = { sources: [] }; var sizes = args.sizes; if (!sizes) { sizes = (0, exports.getSizes)(imageSizes.presentationWidth, layout); } formats.forEach(function (format) { var _a; var images = imageSizes.sizes .map(function (size) { var imageSrc = generateImageSource(filename, size, Math.round(size / imageSizes.aspectRatio), format, fit, options); if (!(imageSrc === null || imageSrc === void 0 ? void 0 : imageSrc.width) || !imageSrc.height || !imageSrc.src || !imageSrc.format) { reporter.warn("[".concat(pluginName, "] The resolver for image ").concat(filename, " returned an invalid value.")); return undefined; } return imageSrc; }) .filter(Boolean); if (format === "jpg" || format === "png" || format === "auto") { var unscaled = images.find(function (img) { return img.width === imageSizes.unscaledWidth; }) || images[0]; if (unscaled) { result.fallback = { src: unscaled.src, srcSet: (0, exports.getSrcSet)(images), sizes: sizes }; } } else { (_a = result.sources) === null || _a === void 0 ? void 0 : _a.push({ srcSet: (0, exports.getSrcSet)(images), sizes: sizes, type: "image/".concat(format) }); } }); var imageProps = { images: result, layout: layout, backgroundColor: backgroundColor }; if (placeholderURL) { imageProps.placeholder = { fallback: placeholderURL }; } switch (layout) { case "fixed": imageProps.width = imageSizes.presentationWidth; imageProps.height = imageSizes.presentationHeight; break; case "fullWidth": imageProps.width = 1; imageProps.height = 1 / imageSizes.aspectRatio; break; case "constrained": imageProps.width = args.width || imageSizes.presentationWidth || 1; imageProps.height = (imageProps.width || 1) / imageSizes.aspectRatio; } return imageProps; } exports.generateImageData = generateImageData; var dedupeAndSortDensities = function (values) { return Array.from(new Set(__spreadArray([1], __read(values), false))).sort(sortNumeric); }; function calculateImageSizes(args) { var width = args.width, height = args.height, filename = args.filename, _a = args.layout, layout = _a === void 0 ? "constrained" : _a, imgDimensions = args.sourceMetadata, _b = args.reporter, reporter = _b === void 0 ? { warn: warn } : _b, _c = args.breakpoints, breakpoints = _c === void 0 ? exports.DEFAULT_BREAKPOINTS : _c; // check that all dimensions provided are positive var userDimensions = { width: width, height: height }; var erroneousUserDimensions = Object.entries(userDimensions).filter(function (_a) { var _b = __read(_a, 2), _ = _b[0], size = _b[1]; return typeof size === "number" && size < 1; }); if (erroneousUserDimensions.length) { throw new Error("Specified dimensions for images must be positive numbers (> 0). Problem dimensions you have are ".concat(erroneousUserDimensions .map(function (dim) { return dim.join(": "); }) .join(", "))); } if (layout === "fixed") { return fixedImageSizes(args); } else if (layout === "constrained") { return responsiveImageSizes(args); } else if (layout === "fullWidth") { return responsiveImageSizes(__assign({ breakpoints: breakpoints }, args)); } else { reporter.warn("No valid layout was provided for the image at ".concat(filename, ". Valid image layouts are fixed, fullWidth, and constrained. Found ").concat(layout)); return { sizes: [imgDimensions.width], presentationWidth: imgDimensions.width, presentationHeight: imgDimensions.height, aspectRatio: imgDimensions.width / imgDimensions.height, unscaledWidth: imgDimensions.width }; } } exports.calculateImageSizes = calculateImageSizes; function fixedImageSizes(_a) { var filename = _a.filename, imgDimensions = _a.sourceMetadata, width = _a.width, height = _a.height, _b = _a.fit, fit = _b === void 0 ? "cover" : _b, _c = _a.outputPixelDensities, outputPixelDensities = _c === void 0 ? DEFAULT_PIXEL_DENSITIES : _c, _d = _a.reporter, reporter = _d === void 0 ? { warn: warn } : _d; var aspectRatio = imgDimensions.width / imgDimensions.height; // Sort, dedupe and ensure there's a 1 var densities = dedupeAndSortDensities(outputPixelDensities); // If both are provided then we need to check the fit if (width && height) { var calculated = getDimensionsAndAspectRatio(imgDimensions, { width: width, height: height, fit: fit }); width = calculated.width; height = calculated.height; aspectRatio = calculated.aspectRatio; } if (!width) { if (!height) { width = DEFAULT_FIXED_WIDTH; } else { width = Math.round(height * aspectRatio); } } else if (!height) { height = Math.round(width / aspectRatio); } var originalWidth = width; // will use this for presentationWidth, don't want to lose it var isTopSizeOverriden = imgDimensions.width < width || imgDimensions.height < height; // If the image is smaller than requested, warn the user that it's being processed as such // print out this message with the necessary information before we overwrite it for sizing if (isTopSizeOverriden) { var fixedDimension = imgDimensions.width < width ? "width" : "height"; reporter.warn((0, common_tags_1.stripIndent)(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n The requested ", " \"", "px\" for the image ", " was larger than the actual image ", " of ", "px. If possible, replace the current image with a larger one."], ["\n The requested ", " \"", "px\" for the image ", " was larger than the actual image ", " of ", "px. If possible, replace the current image with a larger one."])), fixedDimension, fixedDimension === "width" ? width : height, filename, fixedDimension, imgDimensions[fixedDimension])); if (fixedDimension === "width") { width = imgDimensions.width; height = Math.round(width / aspectRatio); } else { height = imgDimensions.height; width = height * aspectRatio; } } var sizes = densities .filter(function (size) { return size >= 1; }) // remove smaller densities because fixed images don't need them .map(function (density) { return Math.round(density * width); }) .filter(function (size) { return size <= imgDimensions.width; }); return { sizes: sizes, aspectRatio: aspectRatio, presentationWidth: originalWidth, presentationHeight: Math.round(originalWidth / aspectRatio), unscaledWidth: width }; } exports.fixedImageSizes = fixedImageSizes; function responsiveImageSizes(_a) { var imgDimensions = _a.sourceMetadata, width = _a.width, height = _a.height, _b = _a.fit, fit = _b === void 0 ? "cover" : _b, _c = _a.outputPixelDensities, outputPixelDensities = _c === void 0 ? DEFAULT_PIXEL_DENSITIES : _c, breakpoints = _a.breakpoints, layout = _a.layout; var sizes; var aspectRatio = imgDimensions.width / imgDimensions.height; // Sort, dedupe and ensure there's a 1 var densities = dedupeAndSortDensities(outputPixelDensities); // If both are provided then we need to check the fit if (width && height) { var calculated = getDimensionsAndAspectRatio(imgDimensions, { width: width, height: height, fit: fit }); width = calculated.width; height = calculated.height; aspectRatio = calculated.aspectRatio; } // Case 1: width of height were passed in, make sure it isn't larger than the actual image width = width && Math.min(width, imgDimensions.width); height = height && Math.min(height, imgDimensions.height); // Case 2: neither width or height were passed in, use default size if (!width && !height) { width = Math.min(DEFAULT_FLUID_WIDTH, imgDimensions.width); height = width / aspectRatio; } // if it still hasn't been found, calculate width from the derived height. // TS isn't smart enough to realise the type for height has been narrowed here if (!width) { width = height * aspectRatio; } var originalWidth = width; var isTopSizeOverriden = imgDimensions.width < width || imgDimensions.height < height; if (isTopSizeOverriden) { width = imgDimensions.width; height = imgDimensions.height; } width = Math.round(width); if ((breakpoints === null || breakpoints === void 0 ? void 0 : breakpoints.length) > 0) { sizes = breakpoints.filter(function (size) { return size <= imgDimensions.width; }); // If a larger breakpoint has been filtered-out, add the actual image width instead if (sizes.length < breakpoints.length && !sizes.includes(imgDimensions.width)) { sizes.push(imgDimensions.width); } } else { sizes = densities.map(function (density) { return Math.round(density * width); }); sizes = sizes.filter(function (size) { return size <= imgDimensions.width; }); } // ensure that the size passed in is included in the final output if (layout === "constrained" && !sizes.includes(width)) { sizes.push(width); } sizes = sizes.sort(sortNumeric); return { sizes: sizes, aspectRatio: aspectRatio, presentationWidth: originalWidth, presentationHeight: Math.round(originalWidth / aspectRatio), unscaledWidth: width }; } exports.responsiveImageSizes = responsiveImageSizes; function getDimensionsAndAspectRatio(dimensions, options) { // Calculate the eventual width/height of the image. var imageAspectRatio = dimensions.width / dimensions.height; var width = options.width; var height = options.height; switch (options.fit) { case "fill": { width = options.width ? options.width : dimensions.width; height = options.height ? options.height : dimensions.height; break; } case "inside": { var widthOption = options.width ? options.width : Number.MAX_SAFE_INTEGER; var heightOption = options.height ? options.height : Number.MAX_SAFE_INTEGER; width = Math.min(widthOption, Math.round(heightOption * imageAspectRatio)); height = Math.min(heightOption, Math.round(widthOption / imageAspectRatio)); break; } case "outside": { var widthOption = options.width ? options.width : 0; var heightOption = options.height ? options.height : 0; width = Math.max(widthOption, Math.round(heightOption * imageAspectRatio)); height = Math.max(heightOption, Math.round(widthOption / imageAspectRatio)); break; } default: { if (options.width && !options.height) { width = options.width; height = Math.round(options.width / imageAspectRatio); } if (options.height && !options.width) { width = Math.round(options.height * imageAspectRatio); height = options.height; } } } return { width: width, height: height, aspectRatio: width / height }; } exports.getDimensionsAndAspectRatio = getDimensionsAndAspectRatio; var templateObject_1;