UNPKG

22.4 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(); // workarounds for https://github.com/GoogleChromeLabs/squoosh/issues/1152
655 // @ts-ignore
656
657 delete globalThis.navigator;
658 }
659}
660
661async function squooshImagePoolTeardown() {
662 if (pool) {
663 await pool.close(); // eslint-disable-next-line require-atomic-updates
664
665 pool = undefined;
666 }
667}
668/**
669 * @template T
670 * @param {WorkerResult} original
671 * @param {T} minifyOptions
672 * @returns {Promise<WorkerResult>}
673 */
674
675
676async function squooshGenerate(original, minifyOptions) {
677 // eslint-disable-next-line node/no-unpublished-require
678 const squoosh = require("@squoosh/lib");
679
680 const isReusePool = Boolean(pool);
681 const imagePool = pool || squooshImagePoolCreate();
682 const image = imagePool.ingestImage(new Uint8Array(original.data));
683 const squooshOptions =
684 /** @type {SquooshOptions} */
685 minifyOptions || {};
686 /**
687 * @type {undefined | Object.<string, any>}
688 */
689
690 let preprocessors;
691
692 for (const preprocessor of Object.keys(squoosh.preprocessors)) {
693 if (typeof squooshOptions[preprocessor] !== "undefined") {
694 if (!preprocessors) {
695 preprocessors = {};
696 }
697
698 preprocessors[preprocessor] = squooshOptions[preprocessor];
699 }
700 }
701
702 if (typeof preprocessors !== "undefined") {
703 await image.preprocess(preprocessors);
704 }
705
706 const {
707 encodeOptions
708 } = squooshOptions;
709
710 try {
711 await image.encode(encodeOptions);
712 } catch (error) {
713 if (!isReusePool) {
714 await imagePool.close();
715 }
716
717 const originalError = error instanceof Error ? error : new Error(
718 /** @type {string} */
719 error);
720 const newError = new Error(`Error with '${original.filename}': ${originalError.message}`);
721 original.errors.push(newError);
722 return original;
723 }
724
725 if (!isReusePool) {
726 await imagePool.close();
727 }
728
729 if (Object.keys(image.encodedWith).length === 0) {
730 original.errors.push(new Error(`No result from 'squoosh' for '${original.filename}', please configure the 'encodeOptions' option to generate images`));
731 return original;
732 }
733
734 if (Object.keys(image.encodedWith).length > 1) {
735 original.errors.push(new Error(`Multiple values for the 'encodeOptions' option is not supported for '${original.filename}', specify only one codec for the generator`));
736 return original;
737 }
738
739 const ext = path.extname(original.filename).toLowerCase();
740 const {
741 binary,
742 extension
743 } = await Object.values(image.encodedWith)[0];
744 const newFilename = original.filename.replace(new RegExp(`${ext}$`), `.${extension}`);
745 return {
746 filename: newFilename,
747 data: Buffer.from(binary),
748 warnings: [...original.warnings],
749 errors: [...original.errors],
750 info: { ...original.info,
751 generated: true,
752 generatedBy: original.info && original.info.generatedBy ? ["squoosh", ...original.info.generatedBy] : ["squoosh"]
753 }
754 };
755}
756
757squooshGenerate.setup = squooshImagePoolSetup;
758squooshGenerate.teardown = squooshImagePoolTeardown;
759/**
760 * @template T
761 * @param {WorkerResult} original
762 * @param {T} options
763 * @returns {Promise<WorkerResult>}
764 */
765
766async function squooshMinify(original, options) {
767 // eslint-disable-next-line node/no-unpublished-require
768 const squoosh = require("@squoosh/lib");
769
770 const {
771 encoders
772 } = squoosh;
773 /**
774 * @type {Record<string, string>}
775 */
776
777 const targets = {};
778
779 for (const [codec, {
780 extension
781 }] of Object.entries(encoders)) {
782 const extensionNormalized = extension.toLowerCase();
783
784 if (extensionNormalized === "jpg") {
785 targets.jpeg = codec;
786 }
787
788 targets[extensionNormalized] = codec;
789 }
790
791 const ext = path.extname(original.filename).slice(1).toLowerCase();
792 const targetCodec = targets[ext];
793
794 if (!targetCodec) {
795 return original;
796 }
797
798 const isReusePool = Boolean(pool);
799 const imagePool = pool || squooshImagePoolCreate();
800 const image = imagePool.ingestImage(new Uint8Array(original.data));
801 const squooshOptions =
802 /** @type {SquooshOptions} */
803 options || {};
804 /**
805 * @type {undefined | Object.<string, any>}
806 */
807
808 let preprocessors;
809
810 for (const preprocessor of Object.keys(squoosh.preprocessors)) {
811 if (typeof squooshOptions[preprocessor] !== "undefined") {
812 if (!preprocessors) {
813 preprocessors = {};
814 }
815
816 preprocessors[preprocessor] = squooshOptions[preprocessor];
817 }
818 }
819
820 if (typeof preprocessors !== "undefined") {
821 await image.preprocess(preprocessors);
822 }
823
824 const {
825 encodeOptions = {}
826 } = squooshOptions;
827
828 if (!encodeOptions[targetCodec]) {
829 encodeOptions[targetCodec] = {};
830 }
831
832 try {
833 await image.encode({
834 [targetCodec]: encodeOptions[targetCodec]
835 });
836 } catch (error) {
837 if (!isReusePool) {
838 await imagePool.close();
839 }
840
841 const originalError = error instanceof Error ? error : new Error(
842 /** @type {string} */
843 error);
844 const newError = new Error(`Error with '${original.filename}': ${originalError.message}`);
845 original.errors.push(newError);
846 return original;
847 }
848
849 if (!isReusePool) {
850 await imagePool.close();
851 }
852
853 const {
854 binary
855 } = await image.encodedWith[targets[ext]];
856 return {
857 filename: original.filename,
858 data: Buffer.from(binary),
859 warnings: [...original.warnings],
860 errors: [...original.errors],
861 info: { ...original.info,
862 minimized: true,
863 minimizedBy: original.info && original.info.minimizedBy ? ["squoosh", ...original.info.minimizedBy] : ["squoosh"]
864 }
865 };
866}
867
868squooshMinify.setup = squooshImagePoolSetup;
869squooshMinify.teardown = squooshImagePoolTeardown;
870module.exports = {
871 throttleAll,
872 isAbsoluteURL,
873 imageminNormalizeConfig,
874 imageminMinify,
875 imageminGenerate,
876 squooshMinify,
877 squooshGenerate
878};
\No newline at end of file