UNPKG

gatsby-plugin-sharp

Version:

Wrapper of the Sharp image manipulation library for Gatsby plugins

322 lines (318 loc) • 10.9 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.generateImageData = generateImageData; exports.getImageMetadata = getImageMetadata; var _fsExtra = _interopRequireDefault(require("fs-extra")); var _utils = require("./utils"); var _ = require("."); var _safeSharp = _interopRequireDefault(require("./safe-sharp")); var _pluginOptions = require("./plugin-options"); var _reportError = require("./report-error"); /* eslint-disable no-unused-expressions */ const DEFAULT_BLURRED_IMAGE_WIDTH = 20; const DOMINANT_COLOR_IMAGE_SIZE = 200; const DEFAULT_BREAKPOINTS = [750, 1080, 1366, 1920]; const metadataCache = new Map(); async function getImageMetadata(file, getDominantColor, cache) { if (!getDominantColor) { // If we don't need the dominant color we can use the cheaper size function const { width, height, type } = await (0, _.getImageSizeAsync)(file); return { width, height, format: type }; } let metadata; const METADATA_KEY = `metadata-${file.internal.contentDigest}`; if (cache) { // Use plugin cache metadata = await cache.get(METADATA_KEY); } else { // Use in-memory cache instead metadata = metadataCache.get(METADATA_KEY); } if (metadata && process.env.NODE_ENV !== `test`) { return metadata; } try { const pipeline = (0, _safeSharp.default)({ failOn: (0, _pluginOptions.getPluginOptions)().failOn }); _fsExtra.default.createReadStream(file.absolutePath).pipe(pipeline); const { width, height, density, format } = await pipeline.metadata(); // Downsize the image before calculating the dominant color const buffer = await pipeline.resize(DOMINANT_COLOR_IMAGE_SIZE, DOMINANT_COLOR_IMAGE_SIZE, { fit: `inside`, withoutEnlargement: true }).toBuffer(); const { dominant } = await (0, _safeSharp.default)(buffer).stats(); // Fallback in case sharp doesn't support dominant const dominantColor = dominant ? (0, _utils.rgbToHex)(dominant.r, dominant.g, dominant.b) : `#000000`; metadata = { width, height, density, format, dominantColor }; if (cache) { await cache.set(METADATA_KEY, metadata); } else { metadataCache.set(METADATA_KEY, metadata); } } catch (err) { (0, _reportError.reportError)(`Failed to process image ${file.absolutePath}`, err); return {}; } return metadata; } function normalizeFormat(format) { if (format === `jpeg`) { return `jpg`; } return format; } let didShowTraceSVGRemovalWarning = false; async function generateImageData({ file, args, pathPrefix, reporter, cache }) { args = (0, _pluginOptions.mergeDefaults)(args); let { layout = `constrained`, placeholder = `dominantColor`, tracedSVGOptions = {}, transformOptions = {}, quality, backgroundColor } = args; args.formats = args.formats || [`auto`, `webp`]; if (layout === `fullWidth`) { var _args$breakpoints; args.breakpoints = (_args$breakpoints = args.breakpoints) !== null && _args$breakpoints !== void 0 && _args$breakpoints.length ? args.breakpoints : DEFAULT_BREAKPOINTS; } if (placeholder === `tracedSVG`) { if (!didShowTraceSVGRemovalWarning) { console.warn(`"TRACED_SVG" placeholder argument value is no longer supported (used in gatsbyImageData processing), falling back to "DOMINANT_COLOR". See https://gatsby.dev/tracesvg-removal/`); didShowTraceSVGRemovalWarning = true; } placeholder = `dominantColor`; } const { fit = `cover`, cropFocus = _safeSharp.default.strategy.attention, duotone } = transformOptions; if (duotone && (!duotone.highlight || !duotone.shadow)) { reporter.warn(`Invalid duotone option specified for ${file.absolutePath}, ignoring. Please pass an object to duotone with the keys "highlight" and "shadow" set to the corresponding hex values you want to use.`); } const metadata = await getImageMetadata(file, placeholder === `dominantColor`, cache); if ((args.width || args.height) && layout === `fullWidth`) { reporter.warn(`Specifying fullWidth images will ignore the width and height arguments, you may want a constrained image instead. Otherwise, use the breakpoints argument.`); args.width = metadata === null || metadata === void 0 ? void 0 : metadata.width; args.height = undefined; } if (!args.width && !args.height && metadata.width) { args.width = metadata.width; } if (args.aspectRatio) { if (args.width && args.height) { reporter.warn(`Specifying aspectRatio along with both width and height will cause aspectRatio to be ignored.`); } else if (args.width) { args.height = args.width / args.aspectRatio; } else if (args.height) { args.width = args.height * args.aspectRatio; } } const formats = new Set(args.formats); let useAuto = formats.has(``) || formats.has(`auto`) || formats.size === 0; if (formats.has(`jpg`) && formats.has(`png`)) { reporter.warn(`Specifying both "jpg" and "png" formats is not supported and will be ignored. Please use "auto" instead.`); useAuto = true; } let primaryFormat; if (useAuto) { primaryFormat = normalizeFormat((metadata === null || metadata === void 0 ? void 0 : metadata.format) || file.extension); } else if (formats.has(`png`)) { primaryFormat = `png`; } else if (formats.has(`jpg`)) { primaryFormat = `jpg`; } else if (formats.has(`webp`)) { primaryFormat = `webp`; } else if (formats.has(`avif`)) { reporter.warn(`Image ${file.relativePath} specified only AVIF format, with no fallback format. This will mean your site cannot be viewed in all browsers.`); primaryFormat = `avif`; } const optionsMap = { jpg: args.jpgOptions, png: args.pngOptions, webp: args.webpOptions, avif: args.avifOptions }; const options = primaryFormat ? optionsMap[primaryFormat] : undefined; const imageSizes = (0, _utils.calculateImageSizes)({ file, layout, ...args, imgDimensions: metadata, reporter }); const sharedOptions = { quality, ...transformOptions, fit, cropFocus, background: backgroundColor }; const transforms = imageSizes.sizes.map(outputWidth => { const width = Math.round(outputWidth); const transform = (0, _pluginOptions.createTransformObject)({ ...sharedOptions, ...options, width, height: Math.round(width / imageSizes.aspectRatio), toFormat: primaryFormat }); if (pathPrefix) { transform.pathPrefix = pathPrefix; } return transform; }); const images = (0, _.batchQueueImageResizing)({ file, transforms, reporter }); const srcSet = (0, _utils.getSrcSet)(images); const sizes = args.sizes || (0, _utils.getSizes)(imageSizes.unscaledWidth, layout); const primaryIndex = layout === `fullWidth` ? imageSizes.sizes.length - 1 // The largest image : imageSizes.sizes.findIndex(size => size === Math.round(imageSizes.unscaledWidth)); if (primaryIndex === -1) { reporter.error(`No image of the specified size found. Images may not have been processed correctly.`); return undefined; } const primaryImage = images[primaryIndex]; if (!(images !== null && images !== void 0 && images.length)) { return undefined; } const imageProps = { layout, placeholder: undefined, backgroundColor, images: { fallback: { src: primaryImage.src, srcSet, sizes }, sources: [] } }; if (primaryFormat !== `avif` && formats.has(`avif`)) { var _imageProps$images$so; const transforms = imageSizes.sizes.map(outputWidth => { const width = Math.round(outputWidth); const transform = (0, _pluginOptions.createTransformObject)({ ...sharedOptions, ...args.avifOptions, width, height: Math.round(width / imageSizes.aspectRatio), toFormat: `avif` }); if (pathPrefix) { transform.pathPrefix = pathPrefix; } return transform; }); const avifImages = (0, _.batchQueueImageResizing)({ file, transforms, reporter }); (_imageProps$images$so = imageProps.images.sources) === null || _imageProps$images$so === void 0 ? void 0 : _imageProps$images$so.push({ srcSet: (0, _utils.getSrcSet)(avifImages), type: `image/avif`, sizes }); } if (primaryFormat !== `webp` && formats.has(`webp`)) { var _imageProps$images$so2; const transforms = imageSizes.sizes.map(outputWidth => { const width = Math.round(outputWidth); const transform = (0, _pluginOptions.createTransformObject)({ ...sharedOptions, ...args.webpOptions, width, height: Math.round(width / imageSizes.aspectRatio), toFormat: `webp` }); if (pathPrefix) { transform.pathPrefix = pathPrefix; } return transform; }); const webpImages = (0, _.batchQueueImageResizing)({ file, transforms, reporter }); (_imageProps$images$so2 = imageProps.images.sources) === null || _imageProps$images$so2 === void 0 ? void 0 : _imageProps$images$so2.push({ srcSet: (0, _utils.getSrcSet)(webpImages), type: `image/webp`, sizes }); } if (placeholder === `blurred`) { var _args$blurredOptions, _args$blurredOptions2; const placeholderWidth = ((_args$blurredOptions = args.blurredOptions) === null || _args$blurredOptions === void 0 ? void 0 : _args$blurredOptions.width) || DEFAULT_BLURRED_IMAGE_WIDTH; const { src: fallback } = await (0, _.base64)({ file, args: { ...sharedOptions, ...options, toFormatBase64: (_args$blurredOptions2 = args.blurredOptions) === null || _args$blurredOptions2 === void 0 ? void 0 : _args$blurredOptions2.toFormat, width: placeholderWidth, height: Math.max(1, Math.round(placeholderWidth / imageSizes.aspectRatio)) }, reporter }); imageProps.placeholder = { fallback }; } else if (metadata !== null && metadata !== void 0 && metadata.dominantColor) { imageProps.backgroundColor = metadata.dominantColor; } primaryImage.aspectRatio = primaryImage.aspectRatio || 1; switch (layout) { case `fixed`: imageProps.width = imageSizes.presentationWidth; imageProps.height = imageSizes.presentationHeight; break; case `fullWidth`: imageProps.width = 1; imageProps.height = 1 / primaryImage.aspectRatio; break; case `constrained`: imageProps.width = args.width || primaryImage.width || 1; imageProps.height = (imageProps.width || 1) / primaryImage.aspectRatio; } return imageProps; }