UNPKG

21.3 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.imageminGenerate = imageminGenerate;
7exports.imageminMinify = imageminMinify;
8exports.imageminNormalizeConfig = imageminNormalizeConfig;
9exports.isAbsoluteURL = isAbsoluteURL;
10exports.squooshGenerate = squooshGenerate;
11exports.squooshMinify = squooshMinify;
12exports.throttleAll = throttleAll;
13
14var _path = _interopRequireDefault(require("path"));
15
16function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
18/** @typedef {import("./index").WorkerResult} WorkerResult */
19
20/** @typedef {import("./index").SquooshOptions} SquooshOptions */
21
22/** @typedef {import("imagemin").Options} ImageminOptions */
23
24/** @typedef {import("webpack").WebpackError} WebpackError */
25const notSettled = Symbol("not-settled");
26/**
27 * @template T
28 * @typedef {() => Promise<T>} Task
29 */
30
31/**
32 * Run tasks with limited concurency.
33 * @template T
34 * @param {number} limit - Limit of tasks that run at once.
35 * @param {Task<T>[]} tasks - List of tasks to run.
36 * @returns {Promise<T[]>} A promise that fulfills to an array of the results
37 */
38
39function throttleAll(limit, tasks) {
40 if (!Number.isInteger(limit) || limit < 1) {
41 throw new TypeError(`Expected 'limit' to be a finite number > 0, got \`${limit}\` (${typeof limit})`);
42 }
43
44 if (!Array.isArray(tasks) || !tasks.every(task => typeof task === "function")) {
45 throw new TypeError("Expected 'tasks' to be a list of functions returning a promise");
46 }
47
48 return new Promise((resolve, reject) => {
49 // eslint-disable-next-line unicorn/new-for-builtins
50 const result = Array(tasks.length).fill(notSettled);
51 const entries = tasks.entries();
52
53 const next = () => {
54 const {
55 done,
56 value
57 } = entries.next();
58
59 if (done) {
60 const isLast = !result.includes(notSettled);
61
62 if (isLast) {
63 // eslint-disable-next-line node/callback-return
64 resolve(result);
65 }
66
67 return;
68 }
69
70 const [index, task] = value;
71 /**
72 * @param {T} i
73 */
74
75 const onFulfilled = i => {
76 result[index] = i;
77 next();
78 };
79
80 task().then(onFulfilled, reject);
81 }; // eslint-disable-next-line unicorn/new-for-builtins
82
83
84 Array(limit).fill(0).forEach(next);
85 });
86}
87
88const ABSOLUTE_URL_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/;
89const WINDOWS_PATH_REGEX = /^[a-zA-Z]:\\/;
90/**
91 * @param {string} url
92 * @returns {boolean}
93 */
94
95function isAbsoluteURL(url) {
96 if (WINDOWS_PATH_REGEX.test(url)) {
97 return false;
98 }
99
100 return ABSOLUTE_URL_REGEX.test(url);
101}
102/**
103 * @callback Uint8ArrayUtf8ByteString
104 * @param {number[] | Uint8Array} array
105 * @param {number} start
106 * @param {number} end
107 * @returns {string}
108 */
109
110/** @type {Uint8ArrayUtf8ByteString} */
111
112
113const uint8ArrayUtf8ByteString = (array, start, end) => String.fromCodePoint(...array.slice(start, end));
114/**
115 * @callback StringToBytes
116 * @param {string} string
117 * @returns {number[]}
118 */
119
120/** @type {StringToBytes} */
121
122
123const stringToBytes = string => // eslint-disable-next-line unicorn/prefer-code-point
124[...string].map(character => character.charCodeAt(0));
125/**
126 * @param {ArrayBuffer | ArrayLike<number>} input
127 * @returns {{ext: string, mime: string} | undefined}
128 */
129
130
131function fileTypeFromBuffer(input) {
132 if (!(input instanceof Uint8Array || input instanceof ArrayBuffer || Buffer.isBuffer(input))) {
133 throw new TypeError(`Expected the \`input\` argument to be of type \`Uint8Array\` or \`Buffer\` or \`ArrayBuffer\`, got \`${typeof input}\``);
134 }
135
136 const buffer = input instanceof Uint8Array ? input : new Uint8Array(input);
137
138 if (!(buffer && buffer.length > 1)) {
139 return;
140 }
141 /**
142 * @param {number[]} header
143 * @param {{offset: number, mask?: number[]}} [options]
144 * @returns {boolean}
145 */
146
147
148 const check = (header, options) => {
149 // eslint-disable-next-line no-param-reassign
150 options = {
151 offset: 0,
152 ...options
153 };
154
155 for (let i = 0; i < header.length; i++) {
156 if (options.mask) {
157 // eslint-disable-next-line no-bitwise
158 if (header[i] !== (options.mask[i] & buffer[i + options.offset])) {
159 return false;
160 }
161 } else if (header[i] !== buffer[i + options.offset]) {
162 return false;
163 }
164 }
165
166 return true;
167 };
168 /**
169 * @param {string} header
170 * @param {{offset: number, mask?: number[]}} [options]
171 * @returns {boolean}
172 */
173
174
175 const checkString = (header, options) => check(stringToBytes(header), options);
176
177 if (check([0xff, 0xd8, 0xff])) {
178 return {
179 ext: "jpg",
180 mime: "image/jpeg"
181 };
182 }
183
184 if (check([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
185 // APNG format (https://wiki.mozilla.org/APNG_Specification)
186 // 1. Find the first IDAT (image data) chunk (49 44 41 54)
187 // 2. Check if there is an "acTL" chunk before the IDAT one (61 63 54 4C)
188 // Offset calculated as follows:
189 // - 8 bytes: PNG signature
190 // - 4 (length) + 4 (chunk type) + 13 (chunk data) + 4 (CRC): IHDR chunk
191 const startIndex = 33;
192 const firstImageDataChunkIndex = buffer.findIndex((el, i) => i >= startIndex && buffer[i] === 0x49 && buffer[i + 1] === 0x44 && buffer[i + 2] === 0x41 && buffer[i + 3] === 0x54);
193 const sliced = buffer.subarray(startIndex, firstImageDataChunkIndex);
194
195 if (sliced.findIndex((el, i) => sliced[i] === 0x61 && sliced[i + 1] === 0x63 && sliced[i + 2] === 0x54 && sliced[i + 3] === 0x4c) >= 0) {
196 return {
197 ext: "apng",
198 mime: "image/apng"
199 };
200 }
201
202 return {
203 ext: "png",
204 mime: "image/png"
205 };
206 }
207
208 if (check([0x47, 0x49, 0x46])) {
209 return {
210 ext: "gif",
211 mime: "image/gif"
212 };
213 }
214
215 if (check([0x57, 0x45, 0x42, 0x50], {
216 offset: 8
217 })) {
218 return {
219 ext: "webp",
220 mime: "image/webp"
221 };
222 }
223
224 if (check([0x46, 0x4c, 0x49, 0x46])) {
225 return {
226 ext: "flif",
227 mime: "image/flif"
228 };
229 } // `cr2`, `orf`, and `arw` need to be before `tif` check
230
231
232 if ((check([0x49, 0x49, 0x2a, 0x0]) || check([0x4d, 0x4d, 0x0, 0x2a])) && check([0x43, 0x52], {
233 offset: 8
234 })) {
235 return {
236 ext: "cr2",
237 mime: "image/x-canon-cr2"
238 };
239 }
240
241 if (check([0x49, 0x49, 0x52, 0x4f, 0x08, 0x00, 0x00, 0x00, 0x18])) {
242 return {
243 ext: "orf",
244 mime: "image/x-olympus-orf"
245 };
246 }
247
248 if (check([0x49, 0x49, 0x2a, 0x00]) && (check([0x10, 0xfb, 0x86, 0x01], {
249 offset: 4
250 }) || check([0x08, 0x00, 0x00, 0x00], {
251 offset: 4
252 })) && // This pattern differentiates ARW from other TIFF-ish file types:
253 check([0x00, 0xfe, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x01], {
254 offset: 9
255 })) {
256 return {
257 ext: "arw",
258 mime: "image/x-sony-arw"
259 };
260 }
261
262 if (check([0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00]) && (check([0x2d, 0x00, 0xfe, 0x00], {
263 offset: 8
264 }) || check([0x27, 0x00, 0xfe, 0x00], {
265 offset: 8
266 }))) {
267 return {
268 ext: "dng",
269 mime: "image/x-adobe-dng"
270 };
271 }
272
273 if (check([0x49, 0x49, 0x2a, 0x00]) && check([0x1c, 0x00, 0xfe, 0x00], {
274 offset: 8
275 })) {
276 return {
277 ext: "nef",
278 mime: "image/x-nikon-nef"
279 };
280 }
281
282 if (check([0x49, 0x49, 0x55, 0x00, 0x18, 0x00, 0x00, 0x00, 0x88, 0xe7, 0x74, 0xd8])) {
283 return {
284 ext: "rw2",
285 mime: "image/x-panasonic-rw2"
286 };
287 } // `raf` is here just to keep all the raw image detectors together.
288
289
290 if (checkString("FUJIFILMCCD-RAW")) {
291 return {
292 ext: "raf",
293 mime: "image/x-fujifilm-raf"
294 };
295 }
296
297 if (check([0x49, 0x49, 0x2a, 0x0]) || check([0x4d, 0x4d, 0x0, 0x2a])) {
298 return {
299 ext: "tif",
300 mime: "image/tiff"
301 };
302 }
303
304 if (check([0x42, 0x4d])) {
305 return {
306 ext: "bmp",
307 mime: "image/bmp"
308 };
309 }
310
311 if (check([0x49, 0x49, 0xbc])) {
312 return {
313 ext: "jxr",
314 mime: "image/vnd.ms-photo"
315 };
316 }
317
318 if (check([0x38, 0x42, 0x50, 0x53])) {
319 return {
320 ext: "psd",
321 mime: "image/vnd.adobe.photoshop"
322 };
323 }
324
325 if (checkString("ftyp", {
326 offset: 4
327 }) && // eslint-disable-next-line no-bitwise
328 (buffer[8] & 0x60) !== 0x00 // Brand major, first character ASCII?
329 ) {
330 // They all can have MIME `video/mp4` except `application/mp4` special-case which is hard to detect.
331 // For some cases, we're specific, everything else falls to `video/mp4` with `mp4` extension.
332 const brandMajor = uint8ArrayUtf8ByteString(buffer, 8, 12).replace("\0", " ").trim(); // eslint-disable-next-line default-case
333
334 switch (brandMajor) {
335 case "avif":
336 return {
337 ext: "avif",
338 mime: "image/avif"
339 };
340
341 case "mif1":
342 return {
343 ext: "heic",
344 mime: "image/heif"
345 };
346
347 case "msf1":
348 return {
349 ext: "heic",
350 mime: "image/heif-sequence"
351 };
352
353 case "heic":
354 case "heix":
355 return {
356 ext: "heic",
357 mime: "image/heic"
358 };
359
360 case "hevc":
361 case "hevx":
362 return {
363 ext: "heic",
364 mime: "image/heic-sequence"
365 };
366 }
367 }
368
369 if (check([0x00, 0x00, 0x01, 0x00])) {
370 return {
371 ext: "ico",
372 mime: "image/x-icon"
373 };
374 }
375
376 if (check([0x00, 0x00, 0x02, 0x00])) {
377 return {
378 ext: "cur",
379 mime: "image/x-icon"
380 };
381 }
382
383 if (check([0x42, 0x50, 0x47, 0xfb])) {
384 return {
385 ext: "bpg",
386 mime: "image/bpg"
387 };
388 }
389
390 if (check([0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a])) {
391 // JPEG-2000 family
392 if (check([0x6a, 0x70, 0x32, 0x20], {
393 offset: 20
394 })) {
395 return {
396 ext: "jp2",
397 mime: "image/jp2"
398 };
399 }
400
401 if (check([0x6a, 0x70, 0x78, 0x20], {
402 offset: 20
403 })) {
404 return {
405 ext: "jpx",
406 mime: "image/jpx"
407 };
408 }
409
410 if (check([0x6a, 0x70, 0x6d, 0x20], {
411 offset: 20
412 })) {
413 return {
414 ext: "jpm",
415 mime: "image/jpm"
416 };
417 }
418
419 if (check([0x6d, 0x6a, 0x70, 0x32], {
420 offset: 20
421 })) {
422 return {
423 ext: "mj2",
424 mime: "image/mj2"
425 };
426 }
427 }
428
429 if (check([0xff, 0x0a]) || check([0x00, 0x00, 0x00, 0x0c, 0x4a, 0x58, 0x4c, 0x20, 0x0d, 0x0a, 0x87, 0x0a])) {
430 return {
431 ext: "jxl",
432 mime: "image/jxl"
433 };
434 }
435
436 if (check([0xab, 0x4b, 0x54, 0x58, 0x20, 0x31, 0x31, 0xbb, 0x0d, 0x0a, 0x1a, 0x0a])) {
437 return {
438 ext: "ktx",
439 mime: "image/ktx"
440 };
441 }
442}
443/**
444 * @typedef {Object} MetaData
445 * @property {Array<Error>} warnings
446 * @property {Array<Error>} errors
447 */
448
449
450class InvalidConfigError extends Error {
451 /**
452 * @param {string | undefined} message
453 */
454 constructor(message) {
455 super(message);
456 this.name = "InvalidConfigError";
457 }
458
459}
460/**
461 * @template T
462 * @param {ImageminOptions} imageminConfig
463 * @returns {Promise<ImageminOptions>}
464 */
465
466
467async function imageminNormalizeConfig(imageminConfig) {
468 if (!imageminConfig || !imageminConfig.plugins || imageminConfig.plugins && imageminConfig.plugins.length === 0) {
469 throw new Error("No plugins found for `imagemin`, please read documentation");
470 }
471 /**
472 * @type {import("imagemin").Plugin[]}
473 */
474
475
476 const plugins = [];
477
478 for (const plugin of imageminConfig.plugins) {
479 const isPluginArray = Array.isArray(plugin);
480
481 if (typeof plugin === "string" || isPluginArray) {
482 const pluginName = isPluginArray ? plugin[0] : plugin;
483 const pluginOptions = isPluginArray ? plugin[1] : undefined;
484 let requiredPlugin = null;
485 let requiredPluginName = `imagemin-${pluginName}`;
486
487 try {
488 // @ts-ignore
489 // eslint-disable-next-line no-await-in-loop
490 requiredPlugin = (await import(requiredPluginName)).default(pluginOptions);
491 } catch {
492 requiredPluginName = pluginName;
493
494 try {
495 // @ts-ignore
496 // eslint-disable-next-line no-await-in-loop
497 requiredPlugin = (await import(requiredPluginName)).default(pluginOptions);
498 } catch {
499 const pluginNameForError = pluginName.startsWith("imagemin") ? pluginName : `imagemin-${pluginName}`;
500 throw new Error(`Unknown plugin: ${pluginNameForError}\n\nDid you forget to install the plugin?\nYou can install it with:\n\n$ npm install ${pluginNameForError} --save-dev\n$ yarn add ${pluginNameForError} --dev`);
501 } // Nothing
502
503 } // let version = "unknown";
504 // try {
505 // // eslint-disable-next-line import/no-dynamic-require
506 // ({ version } = require(`${requiredPluginName}/package.json`));
507 // } catch {
508 // // Nothing
509 // }
510 // /** @type {Array<Object>} imageminConfig.pluginsMeta */
511 // pluginsMeta.push([
512 // {
513 // name: requiredPluginName,
514 // options: pluginOptions || {},
515 // version,
516 // },
517 // ]);
518
519
520 plugins.push(requiredPlugin);
521 } else {
522 throw new InvalidConfigError(`Invalid plugin configuration '${JSON.stringify(plugin)}', plugin configuration should be 'string' or '[string, object]'"`);
523 }
524 }
525
526 return {
527 plugins
528 };
529}
530/**
531 * @template T
532 * @param {WorkerResult} original
533 * @param {T} minimizerOptions
534 * @returns {Promise<WorkerResult>}
535 */
536
537
538async function imageminGenerate(original, minimizerOptions) {
539 const minimizerOptionsNormalized =
540 /** @type {ImageminOptions} */
541 await imageminNormalizeConfig(
542 /** @type {ImageminOptions} */
543
544 /** @type {?} */
545 minimizerOptions || {}); // @ts-ignore
546 // eslint-disable-next-line node/no-unpublished-import
547
548 const imagemin = (await import("imagemin")).default;
549 let result;
550
551 try {
552 // @ts-ignore
553 result = await imagemin.buffer(original.data, minimizerOptionsNormalized);
554 } catch (error) {
555 const originalError = error instanceof Error ? error : new Error(
556 /** @type {string} */
557 error);
558 const newError = new Error(`Error with '${original.filename}': ${originalError.message}`);
559 original.errors.push(newError);
560 return original;
561 }
562
563 const {
564 ext: extOutput
565 } = fileTypeFromBuffer(result) || {};
566
567 const extInput = _path.default.extname(original.filename).slice(1).toLowerCase();
568
569 let newFilename = original.filename;
570
571 if (extOutput && extInput !== extOutput) {
572 newFilename = original.filename.replace(new RegExp(`${extInput}$`), `${extOutput}`);
573 }
574
575 return {
576 filename: newFilename,
577 data: result,
578 warnings: [...original.warnings],
579 errors: [...original.errors],
580 info: { ...original.info,
581 generated: true,
582 generatedBy: original.info && original.info.generatedBy ? ["imagemin", ...original.info.generatedBy] : ["imagemin"]
583 }
584 };
585}
586/**
587 * @template T
588 * @param {WorkerResult} original
589 * @param {T} options
590 * @returns {Promise<WorkerResult>}
591 */
592
593
594async function imageminMinify(original, options) {
595 const minimizerOptionsNormalized =
596 /** @type {ImageminOptions} */
597 await imageminNormalizeConfig(
598 /** @type {ImageminOptions} */
599
600 /** @type {?} */
601 options || {}); // @ts-ignore
602 // eslint-disable-next-line node/no-unpublished-import
603
604 const imagemin = (await import("imagemin")).default;
605 let result;
606
607 try {
608 // @ts-ignore
609 result = await imagemin.buffer(original.data, minimizerOptionsNormalized);
610 } catch (error) {
611 const originalError = error instanceof Error ? error : new Error(
612 /** @type {string} */
613 error);
614 const newError = new Error(`Error with '${original.filename}': ${originalError.message}`);
615 original.errors.push(newError);
616 return original;
617 }
618
619 if (!isAbsoluteURL(original.filename)) {
620 const extInput = _path.default.extname(original.filename).slice(1).toLowerCase();
621
622 const {
623 ext: extOutput
624 } = fileTypeFromBuffer(result) || {};
625
626 if (extOutput && extInput !== extOutput) {
627 original.warnings.push(new Error(`"imageminMinify" function do not support generate to "${extOutput}" from "${original.filename}". Please use "imageminGenerate" function.`));
628 return original;
629 }
630 }
631
632 return {
633 filename: original.filename,
634 data: result,
635 warnings: [...original.warnings],
636 errors: [...original.errors],
637 info: { ...original.info,
638 minimized: true,
639 minimizedBy: original.info && original.info.minimizedBy ? ["imagemin", ...original.info.minimizedBy] : ["imagemin"]
640 }
641 };
642}
643/**
644 * @template T
645 * @param {WorkerResult} original
646 * @param {T} minifyOptions
647 * @returns {Promise<WorkerResult>}
648 */
649
650
651async function squooshGenerate(original, minifyOptions) {
652 // eslint-disable-next-line node/no-unpublished-require
653 const squoosh = require("@squoosh/lib");
654
655 const {
656 ImagePool
657 } = squoosh; // TODO https://github.com/GoogleChromeLabs/squoosh/issues/1111
658
659 const imagePool = new ImagePool(1);
660 const image = imagePool.ingestImage(new Uint8Array(original.data));
661 const squooshOptions =
662 /** @type {SquooshOptions} */
663 minifyOptions || {};
664 /**
665 * @type {undefined | Object.<string, any>}
666 */
667
668 let preprocessors;
669
670 for (const preprocessor of Object.keys(squoosh.preprocessors)) {
671 if (typeof squooshOptions[preprocessor] !== "undefined") {
672 if (!preprocessors) {
673 preprocessors = {};
674 }
675
676 preprocessors[preprocessor] = squooshOptions[preprocessor];
677 }
678 }
679
680 if (typeof preprocessors !== "undefined") {
681 await image.preprocess(preprocessors);
682 }
683
684 const {
685 encodeOptions
686 } = squooshOptions;
687
688 try {
689 await image.encode(encodeOptions);
690 } catch (error) {
691 await imagePool.close();
692 const originalError = error instanceof Error ? error : new Error(
693 /** @type {string} */
694 error);
695 const newError = new Error(`Error with '${original.filename}': ${originalError.message}`);
696 original.errors.push(newError);
697 return original;
698 }
699
700 await imagePool.close();
701
702 if (Object.keys(image.encodedWith).length === 0) {
703 original.errors.push(new Error(`No result from 'squoosh' for '${original.filename}', please configure the 'encodeOptions' option to generate images`));
704 return original;
705 }
706
707 if (Object.keys(image.encodedWith).length > 1) {
708 original.errors.push(new Error(`Multiple values for the 'encodeOptions' option is not supported for '${original.filename}', specify only one codec for the generator`));
709 return original;
710 }
711
712 const ext = _path.default.extname(original.filename).toLowerCase();
713
714 const {
715 binary,
716 extension
717 } = await Object.values(image.encodedWith)[0];
718 const newFilename = original.filename.replace(new RegExp(`${ext}$`), `.${extension}`);
719 return {
720 filename: newFilename,
721 data: Buffer.from(binary),
722 warnings: [...original.warnings],
723 errors: [...original.errors],
724 info: { ...original.info,
725 generated: true,
726 generatedBy: original.info && original.info.generatedBy ? ["squoosh", ...original.info.generatedBy] : ["squoosh"]
727 }
728 };
729}
730/**
731 * @template T
732 * @param {WorkerResult} original
733 * @param {T} options
734 * @returns {Promise<WorkerResult>}
735 */
736
737
738async function squooshMinify(original, options) {
739 // eslint-disable-next-line node/no-unpublished-require
740 const squoosh = require("@squoosh/lib");
741
742 const {
743 ImagePool,
744 encoders
745 } = squoosh;
746 /**
747 * @type {Record<string, string>}
748 */
749
750 const targets = {};
751
752 for (const [codec, {
753 extension
754 }] of Object.entries(encoders)) {
755 const extensionNormalized = extension.toLowerCase();
756
757 if (extensionNormalized === "jpg") {
758 targets.jpeg = codec;
759 }
760
761 targets[extensionNormalized] = codec;
762 }
763
764 const ext = _path.default.extname(original.filename).slice(1).toLowerCase();
765
766 const targetCodec = targets[ext];
767
768 if (!targetCodec) {
769 return original;
770 }
771
772 const squooshOptions =
773 /** @type {SquooshOptions} */
774 options || {};
775 const imagePool = new ImagePool(1);
776 const image = imagePool.ingestImage(new Uint8Array(original.data));
777 /**
778 * @type {undefined | Object.<string, any>}
779 */
780
781 let preprocessors;
782
783 for (const preprocessor of Object.keys(squoosh.preprocessors)) {
784 if (typeof squooshOptions[preprocessor] !== "undefined") {
785 if (!preprocessors) {
786 preprocessors = {};
787 }
788
789 preprocessors[preprocessor] = squooshOptions[preprocessor];
790 }
791 }
792
793 if (typeof preprocessors !== "undefined") {
794 await image.preprocess(preprocessors);
795 }
796
797 const {
798 encodeOptions = {}
799 } = squooshOptions;
800
801 if (!encodeOptions[targetCodec]) {
802 encodeOptions[targetCodec] = {};
803 }
804
805 try {
806 await image.encode({
807 [targetCodec]: encodeOptions[targetCodec]
808 });
809 } catch (error) {
810 await imagePool.close();
811 const originalError = error instanceof Error ? error : new Error(
812 /** @type {string} */
813 error);
814 const newError = new Error(`Error with '${original.filename}': ${originalError.message}`);
815 original.errors.push(newError);
816 return original;
817 }
818
819 await imagePool.close();
820 const {
821 binary
822 } = await image.encodedWith[targets[ext]];
823 return {
824 filename: original.filename,
825 data: Buffer.from(binary),
826 warnings: [...original.warnings],
827 errors: [...original.errors],
828 info: { ...original.info,
829 minimized: true,
830 minimizedBy: original.info && original.info.minimizedBy ? ["squoosh", ...original.info.minimizedBy] : ["squoosh"]
831 }
832 };
833}
\No newline at end of file