diff --git a/src/utils.js b/src/utils.js index 0369104f..84482f20 100644 --- a/src/utils.js +++ b/src/utils.js @@ -557,7 +557,7 @@ async function imageminNormalizeConfig(imageminConfig) { * @template T * @param {WorkerResult} original * @param {T} minimizerOptions - * @returns {Promise} + * @returns {Promise} */ async function imageminGenerate(original, minimizerOptions) { const minimizerOptionsNormalized = /** @type {ImageminOptions} */ ( @@ -582,10 +582,8 @@ async function imageminGenerate(original, minimizerOptions) { `Error with '${original.filename}': ${originalError.message}` ); - original.info.original = true; original.errors.push(newError); - - return original; + return null; } const { ext: extOutput } = fileTypeFromBuffer(result) || {}; @@ -617,7 +615,7 @@ async function imageminGenerate(original, minimizerOptions) { * @template T * @param {WorkerResult} original * @param {T} options - * @returns {Promise} + * @returns {Promise} */ async function imageminMinify(original, options) { const minimizerOptionsNormalized = /** @type {ImageminOptions} */ ( @@ -642,10 +640,8 @@ async function imageminMinify(original, options) { `Error with '${original.filename}': ${originalError.message}` ); - original.info.original = true; original.errors.push(newError); - - return original; + return null; } if (!isAbsoluteURL(original.filename)) { @@ -653,14 +649,13 @@ async function imageminMinify(original, options) { const { ext: extOutput } = fileTypeFromBuffer(result) || {}; if (extOutput && extInput !== extOutput) { - original.info.original = true; original.warnings.push( new Error( `"imageminMinify" function do not support generate to "${extOutput}" from "${original.filename}". Please use "imageminGenerate" function.` ) ); - return original; + return null; } } @@ -729,7 +724,7 @@ async function squooshImagePoolTeardown() { * @template T * @param {WorkerResult} original * @param {T} minifyOptions - * @returns {Promise} + * @returns {Promise} */ async function squooshGenerate(original, minifyOptions) { // eslint-disable-next-line node/no-unpublished-require @@ -774,10 +769,8 @@ async function squooshGenerate(original, minifyOptions) { `Error with '${original.filename}': ${originalError.message}` ); - original.info.original = true; original.errors.push(newError); - - return original; + return null; } if (!isReusePool) { @@ -785,27 +778,23 @@ async function squooshGenerate(original, minifyOptions) { } if (Object.keys(image.encodedWith).length === 0) { - // eslint-disable-next-line require-atomic-updates - original.info.original = true; original.errors.push( new Error( `No result from 'squoosh' for '${original.filename}', please configure the 'encodeOptions' option to generate images` ) ); - return original; + return null; } if (Object.keys(image.encodedWith).length > 1) { - // eslint-disable-next-line require-atomic-updates - original.info.original = true; original.errors.push( new Error( `Multiple values for the 'encodeOptions' option is not supported for '${original.filename}', specify only one codec for the generator` ) ); - return original; + return null; } const { binary, extension } = await Object.values(image.encodedWith)[0]; @@ -833,7 +822,7 @@ squooshGenerate.teardown = squooshImagePoolTeardown; * @template T * @param {WorkerResult} original * @param {T} options - * @returns {Promise} + * @returns {Promise} */ async function squooshMinify(original, options) { // eslint-disable-next-line node/no-unpublished-require @@ -859,15 +848,14 @@ async function squooshMinify(original, options) { const targetCodec = targets[ext]; if (!targetCodec) { - original.info.original = true; - - return original; + return null; } const isReusePool = Boolean(pool); const imagePool = pool || squooshImagePoolCreate(); const image = imagePool.ingestImage(new Uint8Array(original.data)); const squooshOptions = /** @type {SquooshOptions} */ (options || {}); + /** * @type {undefined | Object.} */ @@ -906,10 +894,8 @@ async function squooshMinify(original, options) { `Error with '${original.filename}': ${originalError.message}` ); - original.info.original = true; original.errors.push(newError); - - return original; + return null; } if (!isReusePool) { @@ -993,7 +979,7 @@ const SHARP_FORMATS = new Map([ * @param {WorkerResult} original * @param {SharpOptions} minimizerOptions * @param {SharpFormat | null} targetFormat - * @returns {Promise} + * @returns {Promise} */ async function sharpTransform( original, @@ -1003,9 +989,7 @@ async function sharpTransform( const inputExt = path.extname(original.filename).slice(1).toLowerCase(); if (!SHARP_FORMATS.has(inputExt)) { - original.info.original = true; - - return original; + return null; } /** @type {SharpLib} */ @@ -1080,7 +1064,7 @@ async function sharpTransform( * @template T * @param {WorkerResult} original * @param {T} minimizerOptions - * @returns {Promise} + * @returns {Promise} */ function sharpGenerate(original, minimizerOptions) { const sharpOptions = /** @type {SharpOptions} */ (minimizerOptions ?? {}); @@ -1094,10 +1078,8 @@ function sharpGenerate(original, minimizerOptions) { `No result from 'sharp' for '${original.filename}', please configure the 'encodeOptions' option to generate images` ); - original.info.original = true; original.errors.push(error); - - return Promise.resolve(original); + return Promise.resolve(null); } if (targetFormats.length > 1) { @@ -1105,10 +1087,8 @@ function sharpGenerate(original, minimizerOptions) { `Multiple values for the 'encodeOptions' option is not supported for '${original.filename}', specify only one codec for the generator` ); - original.info.original = true; original.errors.push(error); - - return Promise.resolve(original); + return Promise.resolve(null); } const [targetFormat] = targetFormats; @@ -1120,7 +1100,7 @@ function sharpGenerate(original, minimizerOptions) { * @template T * @param {WorkerResult} original * @param {T} minimizerOptions - * @returns {Promise} + * @returns {Promise} */ function sharpMinify(original, minimizerOptions) { return sharpTransform( diff --git a/src/worker.js b/src/worker.js index 87826974..0cb00b77 100644 --- a/src/worker.js +++ b/src/worker.js @@ -1,40 +1,43 @@ /** @typedef {import("./index").WorkerResult} WorkerResult */ /** @typedef {import("./index").FilenameFn} FilenameFn */ +const isFilenameProcessed = Symbol("isFilenameProcessed"); + /** * @template T + * @param {WorkerResult} result * @param {import("./index").InternalWorkerOptions} options - * @param {WorkerResult} item - * @param {undefined | string | FilenameFn} filename - * @returns {WorkerResult} + * @param {undefined | string | FilenameFn} filenameTemplate */ -function normalizeProcessedResult(options, item, filename) { - item.info ??= {}; - item.filename ??= options.filename; - item.errors ??= []; - item.warnings ??= []; - - if (options.severityError === "off") { - item.warnings = []; - item.errors = []; - } else if (options.severityError === "warning") { - item.warnings = [...item.warnings, ...item.errors]; - item.errors = []; - } - +function processFilenameTemplate(result, options, filenameTemplate) { if ( - typeof filename !== "undefined" && - typeof options.generateFilename === "function" && - !item.info.original + // @ts-ignore + !result.info[isFilenameProcessed] && + typeof filenameTemplate !== "undefined" && + typeof options.generateFilename === "function" ) { - item.filename = options.generateFilename(filename, { - filename: item.filename, + result.filename = options.generateFilename(filenameTemplate, { + filename: result.filename, }); - } - delete item.info.original; + // @ts-ignore + result.info[isFilenameProcessed] = true; + } +} - return item; +/** + * @template T + * @param {WorkerResult} result + * @param {import("./index").InternalWorkerOptions} options + */ +function processSeverityError(result, options) { + if (options.severityError === "off") { + result.warnings = []; + result.errors = []; + } else if (options.severityError === "warning") { + result.warnings = [...result.warnings, ...result.errors]; + result.errors = []; + } } /** @@ -54,7 +57,6 @@ async function worker(options) { if (!result.data) { result.errors.push(new Error("Empty input")); - return result; } @@ -62,6 +64,9 @@ async function worker(options) { ? options.transformer : [options.transformer]; + /** @type {undefined | string | FilenameFn} */ + let filenameTemplate; + for (const transformer of transformers) { if ( typeof transformer.filter === "function" && @@ -70,7 +75,7 @@ async function worker(options) { continue; } - /** @type {WorkerResult} */ + /** @type {WorkerResult | null} */ let processedResult; try { @@ -89,7 +94,7 @@ async function worker(options) { return result; } - if (!processedResult || !Buffer.isBuffer(processedResult.data)) { + if (processedResult && !Buffer.isBuffer(processedResult.data)) { result.errors.push( new Error( "minimizer function doesn't return the 'data' property or result is not a 'Buffer' value" @@ -99,13 +104,20 @@ async function worker(options) { return result; } - result = normalizeProcessedResult( - options, - processedResult, - transformer.filename - ); + if (processedResult) { + result = processedResult; + filenameTemplate ??= transformer.filename; + } } + result.info ??= {}; + result.errors ??= []; + result.warnings ??= []; + result.filename ??= options.filename; + + processSeverityError(result, options); + processFilenameTemplate(result, options, filenameTemplate); + return result; } diff --git a/test/loader-generator-option.test.js b/test/loader-generator-option.test.js index 43e36039..a720f593 100644 --- a/test/loader-generator-option.test.js +++ b/test/loader-generator-option.test.js @@ -6,6 +6,8 @@ import ImageMinimizerPlugin from "../src"; import { runWebpack, fixturesPath } from "./helpers"; +jest.setTimeout(10000); + describe("loader generator option", () => { it("should work", async () => { const stats = await runWebpack({ diff --git a/test/plugin-minimizer-option.test.js b/test/plugin-minimizer-option.test.js index 2386f7f2..ba670c50 100644 --- a/test/plugin-minimizer-option.test.js +++ b/test/plugin-minimizer-option.test.js @@ -11,6 +11,8 @@ import { hasLoader, } from "./helpers"; +jest.setTimeout(10000); + describe("plugin minify option", () => { it('should work with "imageminMinify" minifier', async () => { const stats = await runWebpack({ @@ -663,4 +665,61 @@ describe("plugin minify option", () => { expect(warnings).toHaveLength(0); expect(errors).toHaveLength(0); }); + + it("should rename if minimizer is array", async () => { + const stats = await runWebpack({ + entry: path.join(fixturesPath, "./svg-and-jpg.js"), + fileLoaderOff: true, + imageminPluginOptions: { + minimizer: [ + { + implementation: ImageMinimizerPlugin.sharpMinify, + filename: "minified-by-sharp-[name][ext]", + options: { + encodeOptions: { jpeg: { quality: 90 } }, + }, + }, + { + implementation: ImageMinimizerPlugin.sharpMinify, + filename: "should-be-skipped-[name][ext]", + options: { + encodeOptions: { jpeg: { quality: 90 } }, + }, + }, + { + implementation: ImageMinimizerPlugin.imageminMinify, + filename: "minified-by-svgo-[name][ext]", + options: { + plugins: ["gifsicle", "mozjpeg", "pngquant", "svgo"], + }, + }, + { + implementation: ImageMinimizerPlugin.imageminMinify, + filename: "should-be-skipped-[name][ext]", + options: { + plugins: ["gifsicle", "mozjpeg", "pngquant", "svgo"], + }, + }, + ], + }, + }); + const { compilation } = stats; + const { warnings, errors, assets } = compilation; + + const sharpAsset = Object.keys(assets).filter((asset) => + asset.includes("minified-by-sharp-loader-test.jpg") + ); + const svgoAsset = Object.keys(assets).filter((asset) => + asset.includes("minified-by-svgo-loader-test.svg") + ); + const skippedAsset = Object.keys(assets).filter((asset) => + asset.includes("should-be-skipped-loader-test.svg") + ); + + expect(sharpAsset).toHaveLength(1); + expect(svgoAsset).toHaveLength(1); + expect(skippedAsset).toHaveLength(0); + expect(warnings).toHaveLength(0); + expect(errors).toHaveLength(0); + }); }); diff --git a/test/worker.test.js b/test/worker.test.js index 1a2c65fa..59715ec4 100644 --- a/test/worker.test.js +++ b/test/worker.test.js @@ -250,7 +250,7 @@ describe("minify", () => { }); expect(result.warnings).toHaveLength(0); - expect(result.errors).toHaveLength(2); + expect(result.errors).toHaveLength(1); expect(result.data).toBe(input); }); @@ -889,7 +889,7 @@ describe("minify", () => { expect(result.warnings).toHaveLength(0); expect(result.errors).toHaveLength(0); - expect(result.filename).toBe("generated-generated-image.jpg"); + expect(result.filename).toBe("generated-image.jpg"); const imagePool = new ImagePool(1); const image = imagePool.ingestImage(new Uint8Array(input)); diff --git a/types/utils.d.ts b/types/utils.d.ts index 240746da..35225634 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -68,32 +68,32 @@ export function imageminNormalizeConfig( * @template T * @param {WorkerResult} original * @param {T} options - * @returns {Promise} + * @returns {Promise} */ export function imageminMinify( original: WorkerResult, options: T -): Promise; +): Promise; /** * @template T * @param {WorkerResult} original * @param {T} minimizerOptions - * @returns {Promise} + * @returns {Promise} */ export function imageminGenerate( original: WorkerResult, minimizerOptions: T -): Promise; +): Promise; /** * @template T * @param {WorkerResult} original * @param {T} options - * @returns {Promise} + * @returns {Promise} */ export function squooshMinify( original: WorkerResult, options: T -): Promise; +): Promise; export namespace squooshMinify { export { squooshImagePoolSetup as setup }; export { squooshImagePoolTeardown as teardown }; @@ -102,12 +102,12 @@ export namespace squooshMinify { * @template T * @param {WorkerResult} original * @param {T} minifyOptions - * @returns {Promise} + * @returns {Promise} */ export function squooshGenerate( original: WorkerResult, minifyOptions: T -): Promise; +): Promise; export namespace squooshGenerate { export { squooshImagePoolSetup as setup }; export { squooshImagePoolTeardown as teardown }; @@ -116,22 +116,22 @@ export namespace squooshGenerate { * @template T * @param {WorkerResult} original * @param {T} minimizerOptions - * @returns {Promise} + * @returns {Promise} */ export function sharpMinify( original: WorkerResult, minimizerOptions: T -): Promise; +): Promise; /** * @template T * @param {WorkerResult} original * @param {T} minimizerOptions - * @returns {Promise} + * @returns {Promise} */ export function sharpGenerate( original: WorkerResult, minimizerOptions: T -): Promise; +): Promise; declare function squooshImagePoolSetup(): void; declare function squooshImagePoolTeardown(): Promise; export {};