UNPKG

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