// @flow import {validateConfig} from './validateConfig'; import {Transformer} from '@parcel/plugin'; import nullthrows from 'nullthrows'; import WorkerFarm from '@parcel/workers'; import loadSharp from './loadSharp'; // from https://github.com/lovell/sharp/blob/df7b8ba73808fc494be413e88cfb621b6279218c/lib/output.js#L6-L17 const FORMATS = new Map([ ['jpeg', 'jpeg'], ['jpg', 'jpeg'], ['png', 'png'], ['webp', 'webp'], ['gif', 'gif'], ['tiff', 'tiff'], ['avif', 'avif'], ['heic', 'heif'], ['heif', 'heif'], ]); let isSharpLoadedOnMainThread = false; export default (new Transformer({ async loadConfig({config}) { let configFile: any = await config.getConfig( ['sharp.config.json'], // '.sharprc', '.sharprc.json' {packageKey: 'sharp'}, ); if (configFile?.contents) { validateConfig(configFile.contents, configFile.filePath); return configFile.contents; } else { return {}; } }, async transform({config, asset, options}) { asset.bundleBehavior = 'isolated'; const originalFormat = FORMATS.get(asset.type); if (!originalFormat) { throw new Error( `The image transformer does not support ${asset.type} images.`, ); } const width = asset.query.has('width') ? parseInt(asset.query.get('width'), 10) : null; const height = asset.query.has('height') ? parseInt(asset.query.get('height'), 10) : null; const quality = asset.query.has('quality') ? parseInt(asset.query.get('quality'), 10) : config.quality; let targetFormat = asset.query.get('as')?.toLowerCase().trim(); if (targetFormat && !FORMATS.has(targetFormat)) { throw new Error( `The image transformer does not support ${targetFormat} images.`, ); } const format = nullthrows(FORMATS.get(targetFormat || originalFormat)); const outputOptions = config[format]; if (width || height || quality || targetFormat || outputOptions) { // Sharp must be required from the main thread as well to prevent errors when workers exit // See https://sharp.pixelplumbing.com/install#worker-threads and https://github.com/lovell/sharp/issues/2263 if (WorkerFarm.isWorker() && !isSharpLoadedOnMainThread) { let api = WorkerFarm.getWorkerApi(); await api.callMaster({ location: __dirname + '/loadSharp.js', args: [ options.packageManager, asset.filePath, options.shouldAutoInstall, ], }); isSharpLoadedOnMainThread = true; } let inputBuffer = await asset.getBuffer(); let sharp = await loadSharp( options.packageManager, asset.filePath, options.shouldAutoInstall, true, ); let imagePipeline = sharp(inputBuffer); imagePipeline.withMetadata(); if (width || height) { imagePipeline.resize(width, height); } imagePipeline.rotate(); const normalizedOutputOptions = outputOptions || {}; if (format === 'jpeg') { normalizedOutputOptions.mozjpeg = normalizedOutputOptions.mozjpeg ?? true; } imagePipeline[format]({ quality, ...normalizedOutputOptions, }); asset.type = format; let buffer = await imagePipeline.toBuffer(); asset.setBuffer(buffer); } return [asset]; }, }): Transformer);