From 8e19cbf07fcca04ff4ce06a18c0aa2c910abc962 Mon Sep 17 00:00:00 2001 From: xiaoxiaojx <784487301@qq.com> Date: Tue, 31 Mar 2026 23:56:19 +0800 Subject: [PATCH 1/3] feat: support webpack processContent hook for CSS embedded in JS - Add `compilation.hooks.processContent` support to minimize CSS content embedded in JS bundles (text/style/css-style-sheet export types) - Extract `transformSource` method to share minification logic, error handling, source map validation, and warning filtering between processAssets and processContent paths - Extract `createWorkerFactory` for reusable lazy worker pool management --- package-lock.json | 18 +- package.json | 2 +- src/index.js | 530 +++++++++++------- .../__snapshots__/processContent.test.js.snap | 13 + test/fixtures/process-content.css | 6 + test/fixtures/process-content.js | 4 + test/processContent.test.js | 143 +++++ types/index.d.ts | 23 + 8 files changed, 519 insertions(+), 220 deletions(-) create mode 100644 test/__snapshots__/processContent.test.js.snap create mode 100644 test/fixtures/process-content.css create mode 100644 test/fixtures/process-content.js create mode 100644 test/processContent.test.js diff --git a/package-lock.json b/package-lock.json index d25e05f..7246790 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,7 @@ "standard-version": "^9.5.0", "sugarss": "^5.0.0", "typescript": "^5.5.4", - "webpack": "^5.93.0" + "webpack": "https://pkg.pr.new/webpack@11c12b8" }, "engines": { "node": ">= 20.9.0" @@ -19502,9 +19502,9 @@ } }, "node_modules/terser": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", - "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -20347,9 +20347,9 @@ } }, "node_modules/webpack": { - "version": "5.105.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.3.tgz", - "integrity": "sha512-LLBBA4oLmT7sZdHiYE/PeVuifOxYyE2uL/V+9VQP7YSYdJU7bSf7H8bZRRxW8kEPMkmVjnrXmoR3oejIdX0xbg==", + "version": "5.105.4", + "resolved": "https://pkg.pr.new/webpack@11c12b8", + "integrity": "sha512-wM8wK3GmlRyVJ3Au2n+fnN+9M5olX+64aYjv4Im2jsB02T19mTsSeTy0cAInupXLyOTkse43GDBwMiJwryKQqg==", "dev": true, "license": "MIT", "dependencies": { @@ -20363,7 +20363,7 @@ "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.19.0", + "enhanced-resolve": "^5.20.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -20375,7 +20375,7 @@ "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.16", + "terser-webpack-plugin": "^5.3.17", "watchpack": "^2.5.1", "webpack-sources": "^3.3.4" }, diff --git a/package.json b/package.json index 2c154ca..730bfe7 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "standard-version": "^9.5.0", "sugarss": "^5.0.0", "typescript": "^5.5.4", - "webpack": "^5.93.0" + "webpack": "https://pkg.pr.new/webpack@11c12b8" }, "peerDependencies": { "webpack": "^5.0.0" diff --git a/src/index.js b/src/index.js index 6e2e34f..020af4e 100644 --- a/src/index.js +++ b/src/index.js @@ -415,6 +415,75 @@ class CssMinimizerPlugin { : true; } + /** + * Create a lazy worker factory. Workers are initialized on first call and + * shared across all callers within the same compilation. + * @private + * @param {number} availableNumberOfCores Available cores + * @param {number=} maxTasks Maximum tasks (limits worker count, defaults to availableNumberOfCores) + * @returns {{ getWorker: (() => MinimizerWorker) | undefined, numberOfWorkers: number, end: () => Promise }} Worker factory and cleanup + */ + createWorkerFactory(availableNumberOfCores, maxTasks) { + if (availableNumberOfCores <= 0) { + return { getWorker: undefined, numberOfWorkers: 0, end: async () => {} }; + } + + /** @type {MinimizerWorker | undefined} */ + let initializedWorker; + const numberOfWorkers = Math.min( + maxTasks || availableNumberOfCores, + availableNumberOfCores, + ); + + /** @type {() => MinimizerWorker} */ + const getWorker = () => { + if (initializedWorker) { + return initializedWorker; + } + + const { Worker } = require("jest-worker"); + + initializedWorker = /** @type {MinimizerWorker} */ ( + new Worker(require.resolve("./minify"), { + numWorkers: numberOfWorkers, + enableWorkerThreads: Array.isArray( + this.options.minimizer.implementation, + ) + ? this.options.minimizer.implementation.every((item) => + CssMinimizerPlugin.isSupportsWorkerThreads(item), + ) + : CssMinimizerPlugin.isSupportsWorkerThreads( + this.options.minimizer.implementation, + ), + }) + ); + + // https://github.com/facebook/jest/issues/8872#issuecomment-524822081 + const workerStdout = initializedWorker.getStdout(); + + if (workerStdout) { + workerStdout.on("data", (chunk) => process.stdout.write(chunk)); + } + + const workerStderr = initializedWorker.getStderr(); + + if (workerStderr) { + workerStderr.on("data", (chunk) => process.stderr.write(chunk)); + } + + return initializedWorker; + }; + + const end = async () => { + if (initializedWorker) { + await initializedWorker.end(); + initializedWorker = undefined; + } + }; + + return { getWorker, numberOfWorkers, end }; + } + /** * @private * @param {Compiler} compiler Compiler @@ -470,60 +539,11 @@ class CssMinimizerPlugin { return; } - /** @type {undefined | (() => MinimizerWorker)} */ - let getWorker; - /** @type {undefined | MinimizerWorker} */ - let initializedWorker; - /** @type {undefined | number} */ - let numberOfWorkers; - - if (optimizeOptions.availableNumberOfCores > 0) { - // Do not create unnecessary workers when the number of files is less than the available cores, it saves memory - numberOfWorkers = Math.min( - numberOfAssetsForMinify, - optimizeOptions.availableNumberOfCores, - ); - - getWorker = () => { - if (initializedWorker) { - return initializedWorker; - } - - const { Worker } = require("jest-worker"); - - initializedWorker = /** @type {MinimizerWorker} */ ( - new Worker(require.resolve("./minify"), { - numWorkers: numberOfWorkers, - enableWorkerThreads: Array.isArray( - this.options.minimizer.implementation, - ) - ? this.options.minimizer.implementation.every((item) => - CssMinimizerPlugin.isSupportsWorkerThreads(item), - ) - : CssMinimizerPlugin.isSupportsWorkerThreads( - this.options.minimizer.implementation, - ), - }) - ); - - // https://github.com/facebook/jest/issues/8872#issuecomment-524822081 - const workerStdout = initializedWorker.getStdout(); - - if (workerStdout) { - workerStdout.on("data", (chunk) => process.stdout.write(chunk)); - } - - const workerStderr = initializedWorker.getStderr(); - - if (workerStderr) { - workerStderr.on("data", (chunk) => process.stderr.write(chunk)); - } - - return initializedWorker; - }; - } - - const { SourceMapSource, RawSource } = compiler.webpack.sources; + const workerFactory = this.createWorkerFactory( + optimizeOptions.availableNumberOfCores, + numberOfAssetsForMinify, + ); + const { getWorker, numberOfWorkers } = workerFactory; const scheduledTasks = []; for (const asset of assetsForMinify) { @@ -532,164 +552,32 @@ class CssMinimizerPlugin { let { output } = asset; if (!output) { - let input; - /** @type {RawSourceMap | undefined} */ - let inputSourceMap; - - const { source: sourceFromInputSource, map } = - inputSource.sourceAndMap(); - - input = sourceFromInputSource; - - if (map) { - if (!CssMinimizerPlugin.isSourceMap(map)) { - compilation.warnings.push( - /** @type {WebpackError} */ ( - new Error(`${name} contains invalid source map`) - ), - ); - } else { - inputSourceMap = /** @type {RawSourceMap} */ (map); - } - } + const { source: input, map } = inputSource.sourceAndMap(); - if (Buffer.isBuffer(input)) { - input = input.toString(); - } - - /** - * @type {InternalOptions} - */ - const options = { + output = await this.transformSource( + compiler, + compilation, name, input, - inputSourceMap, - minimizer: { - implementation: this.options.minimizer.implementation, - options: this.options.minimizer.options, - }, - }; - - let result; - - try { - result = await (getWorker - ? getWorker().transform(getSerializeJavascript()(options)) - : minify(options)); - } catch (error) { - const hasSourceMap = - inputSourceMap && CssMinimizerPlugin.isSourceMap(inputSourceMap); - - compilation.errors.push( - /** @type {WebpackError} */ ( - CssMinimizerPlugin.buildError( - /** @type {Error} */ (error), - name, - hasSourceMap - ? new (getTraceMapping().TraceMap)( - /** @type {RawSourceMap} */ (inputSourceMap), - ) - : undefined, - - hasSourceMap ? compilation.requestShortener : undefined, - ) - ), - ); - - return; - } - - output = { warnings: [], errors: [] }; - - for (const item of result.outputs) { - if (item.map) { - let originalSource; - let innerSourceMap; - - if (output.source) { - ({ source: originalSource, map: innerSourceMap } = - output.source.sourceAndMap()); - } else { - originalSource = input; - innerSourceMap = inputSourceMap; - } - - // TODO need API for merging source maps in `webpack-source` - output.source = new SourceMapSource( - item.code, - name, - item.map, - originalSource, - innerSourceMap, - true, - ); - } else if (typeof item.code !== "undefined" && item.code !== null) { - output.source = new RawSource(item.code); - } - } - - if (result.errors && result.errors.length > 0) { - const hasSourceMap = - inputSourceMap && CssMinimizerPlugin.isSourceMap(inputSourceMap); - - for (const error of result.errors) { - output.errors.push( - CssMinimizerPlugin.buildError( - error, - name, - hasSourceMap - ? new (getTraceMapping().TraceMap)( - /** @type {RawSourceMap} */ (inputSourceMap), - ) - : undefined, - - hasSourceMap ? compilation.requestShortener : undefined, - ), - ); - } - } - - if (result.warnings && result.warnings.length > 0) { - const hasSourceMap = - inputSourceMap && CssMinimizerPlugin.isSourceMap(inputSourceMap); - - for (const warning of result.warnings) { - const buildWarning = CssMinimizerPlugin.buildWarning( - warning, - name, - this.options.warningsFilter, - hasSourceMap - ? new (getTraceMapping().TraceMap)( - /** @type {RawSourceMap} */ (inputSourceMap), - ) - : undefined, - - hasSourceMap ? compilation.requestShortener : undefined, - ); + map, + getWorker, + ); - if (buildWarning) { - output.warnings.push(buildWarning); - } - } + if (output.source) { + await cacheItem.storePromise({ + source: output.source, + warnings: output.warnings, + errors: output.errors, + }); } - - await cacheItem.storePromise({ - source: output.source, - warnings: output.warnings, - errors: output.errors, - }); } - if (output.warnings && output.warnings.length > 0) { - for (const warning of output.warnings) { - compilation.warnings.push(warning); - } + for (const warning of output.warnings || []) { + compilation.warnings.push(warning); } - if (output.errors && output.errors.length > 0) { - for (const error of output.errors) { - compilation.errors.push(error); - } + for (const error of output.errors || []) { + compilation.errors.push(error); } if (!output.source) { @@ -704,14 +592,175 @@ class CssMinimizerPlugin { } const limit = - getWorker && numberOfAssetsForMinify > 0 - ? /** @type {number} */ (numberOfWorkers) + getWorker && numberOfWorkers > 0 + ? numberOfWorkers : scheduledTasks.length; await throttleAll(limit, scheduledTasks); - if (initializedWorker) { - await initializedWorker.end(); + await workerFactory.end(); + } + + /** + * Minify a CSS source and return the result with errors/warnings. + * Shared by both processAssets and processContent hooks. + * Callers are responsible for pushing returned warnings/errors to compilation. + * @private + * @param {Compiler} compiler Compiler + * @param {Compilation} compilation Compilation + * @param {string} name Asset or resource name + * @param {string | Buffer} rawInput CSS content + * @param {unknown} rawSourceMap Source map (validated internally) + * @param {(() => MinimizerWorker) | undefined} getWorker Worker factory + * @returns {Promise<{ source: import("webpack").sources.Source | undefined, warnings: EXPECTED_ANY[], errors: EXPECTED_ANY[] }>} Result + */ + async transformSource( + compiler, + compilation, + name, + rawInput, + rawSourceMap, + getWorker, + ) { + const input = Buffer.isBuffer(rawInput) ? rawInput.toString() : rawInput; + const { SourceMapSource, RawSource } = compiler.webpack.sources; + + /** @type {RawSourceMap | undefined} */ + let inputSourceMap; + + /** @type {EXPECTED_ANY[]} */ + const earlyWarnings = []; + + if (rawSourceMap) { + if (!CssMinimizerPlugin.isSourceMap(rawSourceMap)) { + earlyWarnings.push( + /** @type {WebpackError} */ ( + new Error(`${name} contains invalid source map`) + ), + ); + } else { + inputSourceMap = /** @type {RawSourceMap} */ (rawSourceMap); + } + } + + /** @type {InternalOptions} */ + const options = { + name, + input, + inputSourceMap, + minimizer: { + implementation: this.options.minimizer.implementation, + options: this.options.minimizer.options, + }, + }; + + /** @type {InternalResult} */ + let result; + + try { + result = await (getWorker + ? getWorker().transform(getSerializeJavascript()(options)) + : minify(options)); + } catch (error) { + const hasSourceMap = + inputSourceMap && CssMinimizerPlugin.isSourceMap(inputSourceMap); + + const builtError = CssMinimizerPlugin.buildError( + /** @type {Error} */ (error), + name, + hasSourceMap + ? new (getTraceMapping().TraceMap)( + /** @type {RawSourceMap} */ (inputSourceMap), + ) + : undefined, + hasSourceMap ? compilation.requestShortener : undefined, + ); + + return { + source: undefined, + warnings: earlyWarnings, + errors: [builtError], + }; + } + + /** @type {import("webpack").sources.Source | undefined} */ + let source; + + for (const item of result.outputs) { + if (item.map) { + let originalSource; + let innerSourceMap; + + if (source) { + ({ source: originalSource, map: innerSourceMap } = + source.sourceAndMap()); + } else { + originalSource = input; + innerSourceMap = inputSourceMap; + } + + // TODO need API for merging source maps in `webpack-source` + source = new SourceMapSource( + item.code, + name, + item.map, + originalSource, + innerSourceMap, + true, + ); + } else if (typeof item.code !== "undefined" && item.code !== null) { + source = new RawSource(item.code); + } + } + + /** @type {EXPECTED_ANY[]} */ + const errors = []; + /** @type {EXPECTED_ANY[]} */ + const warnings = []; + + if (result.errors && result.errors.length > 0) { + const hasSourceMap = + inputSourceMap && CssMinimizerPlugin.isSourceMap(inputSourceMap); + + for (const error of result.errors) { + errors.push( + CssMinimizerPlugin.buildError( + error, + name, + hasSourceMap + ? new (getTraceMapping().TraceMap)( + /** @type {RawSourceMap} */ (inputSourceMap), + ) + : undefined, + hasSourceMap ? compilation.requestShortener : undefined, + ), + ); + } } + + if (result.warnings && result.warnings.length > 0) { + const hasSourceMap = + inputSourceMap && CssMinimizerPlugin.isSourceMap(inputSourceMap); + + for (const warning of result.warnings) { + const buildWarning = CssMinimizerPlugin.buildWarning( + warning, + name, + this.options.warningsFilter, + hasSourceMap + ? new (getTraceMapping().TraceMap)( + /** @type {RawSourceMap} */ (inputSourceMap), + ) + : undefined, + hasSourceMap ? compilation.requestShortener : undefined, + ); + + if (buildWarning) { + warnings.push(buildWarning); + } + } + } + + return { source, warnings: [...earlyWarnings, ...warnings], errors }; } /** @@ -725,6 +774,60 @@ class CssMinimizerPlugin { ); compiler.hooks.compilation.tap(pluginName, (compilation) => { + // Process CSS content embedded in JS modules (text/style/css-style-sheet export types) + // processContent runs during code generation, which happens before processAssets, + // so the worker is cleaned up in the processAssets handler below. + /** @type {ReturnType | undefined} */ + let contentWorkerFactory; + + if (compilation.hooks.processContent) { + compilation.hooks.processContent.tapPromise( + pluginName, + async (source, name) => { + if ( + !compiler.webpack.ModuleFilenameHelpers.matchObject.bind( + undefined, + this.options, + )(name) + ) { + return source; + } + + const [content, sourceMap] = source; + + if (!contentWorkerFactory) { + contentWorkerFactory = this.createWorkerFactory( + availableNumberOfCores, + ); + } + + const output = await this.transformSource( + compiler, + compilation, + name, + content, + sourceMap, + contentWorkerFactory.getWorker, + ); + + for (const warning of output.warnings) { + compilation.warnings.push(warning); + } + + for (const error of output.errors) { + compilation.errors.push(error); + } + + if (output.source) { + const { source: code, map } = output.source.sourceAndMap(); + return [/** @type {string} */ (code), map || undefined]; + } + + return source; + }, + ); + } + compilation.hooks.processAssets.tapPromise( { name: pluginName, @@ -732,10 +835,17 @@ class CssMinimizerPlugin { compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE, additionalAssets: true, }, - (assets) => - this.optimize(compiler, compilation, assets, { + async (assets) => { + // Clean up processContent workers before processing assets + if (contentWorkerFactory) { + await contentWorkerFactory.end(); + contentWorkerFactory = undefined; + } + + await this.optimize(compiler, compilation, assets, { availableNumberOfCores, - }), + }); + }, ); compilation.hooks.statsPrinter.tap(pluginName, (stats) => { diff --git a/test/__snapshots__/processContent.test.js.snap b/test/__snapshots__/processContent.test.js.snap new file mode 100644 index 0000000..a66a024 --- /dev/null +++ b/test/__snapshots__/processContent.test.js.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`processContent hook should minimize CSS embedded in JS via processContent hook: errors 1`] = `[]`; + +exports[`processContent hook should minimize CSS embedded in JS via processContent hook: warnings 1`] = `[]`; + +exports[`processContent hook should not minimize CSS when name does not match test option: errors 1`] = `[]`; + +exports[`processContent hook should not minimize CSS when name does not match test option: warnings 1`] = `[]`; + +exports[`processContent hook should work alongside processAssets for standalone .css files: errors 1`] = `[]`; + +exports[`processContent hook should work alongside processAssets for standalone .css files: warnings 1`] = `[]`; diff --git a/test/fixtures/process-content.css b/test/fixtures/process-content.css new file mode 100644 index 0000000..8e7ce09 --- /dev/null +++ b/test/fixtures/process-content.css @@ -0,0 +1,6 @@ +body { + color: red; +} +a { + color: blue; +} diff --git a/test/fixtures/process-content.js b/test/fixtures/process-content.js new file mode 100644 index 0000000..5e0a3e3 --- /dev/null +++ b/test/fixtures/process-content.js @@ -0,0 +1,4 @@ +import text from "./process-content.css"; + +// Use the export so it's not tree-shaken +console.log(text); diff --git a/test/processContent.test.js b/test/processContent.test.js new file mode 100644 index 0000000..d90fbbf --- /dev/null +++ b/test/processContent.test.js @@ -0,0 +1,143 @@ +import path from "node:path"; + +import { Volume, createFsFromVolume } from "memfs"; +import webpack from "webpack"; + +import CssMinimizerPlugin from "../src/index"; + +import { compile, getErrors, getWarnings } from "./helpers"; + +/** + * @param {import("webpack").Configuration} config Extra config + * @returns {import("webpack").Compiler} Compiler + */ +function getCompilerWithNativeCSS(config = {}) { + const compiler = webpack({ + mode: "production", + devtool: false, + context: path.resolve(__dirname, "fixtures"), + entry: "./process-content.js", + output: { + pathinfo: false, + path: path.resolve(__dirname, "outputs"), + filename: "[name].js", + }, + module: { + rules: [ + { + test: /\.css$/, + type: "css/module", + parser: { + exportType: "text", + }, + }, + ], + }, + optimization: { + minimize: false, + }, + experiments: { + css: true, + }, + ...config, + }); + + compiler.outputFileSystem = createFsFromVolume(new Volume()); + + return compiler; +} + +describe("processContent hook", () => { + it("should minimize CSS embedded in JS via processContent hook", async () => { + const compiler = getCompilerWithNativeCSS(); + + new CssMinimizerPlugin().apply(compiler); + + const stats = await compile(compiler); + + expect(getErrors(stats)).toMatchSnapshot("errors"); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + + // Read the JS output and verify CSS was minimized + const output = compiler.outputFileSystem.readFileSync( + path.resolve(__dirname, "outputs/main.js"), + "utf8", + ); + + // cssnano should minify "body {\n color: red;\n}\na {\n color: blue;\n}" + // into something like "a,body{color:red}a{color:blue}" or similar + expect(output).not.toContain("body {\\n"); + expect(output).toContain("color"); + }); + + it("should not minimize CSS when name does not match test option", async () => { + const compiler = getCompilerWithNativeCSS(); + + new CssMinimizerPlugin({ + test: /\.scss$/, + }).apply(compiler); + + const stats = await compile(compiler); + + expect(getErrors(stats)).toMatchSnapshot("errors"); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + + const output = compiler.outputFileSystem.readFileSync( + path.resolve(__dirname, "outputs/main.js"), + "utf8", + ); + + // CSS should NOT be minimized since test doesn't match .css files + expect(output).toContain("color: red;"); + }); + + it("should work alongside processAssets for standalone .css files", async () => { + const MiniCssExtractPlugin = require("mini-css-extract-plugin"); + + const compiler = webpack({ + mode: "production", + devtool: false, + context: path.resolve(__dirname, "fixtures"), + entry: "./entry.js", + output: { + pathinfo: false, + path: path.resolve(__dirname, "outputs"), + filename: "[name].js", + chunkFilename: "[id].[name].js", + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: "[name].css", + }), + ], + module: { + rules: [ + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"], + }, + ], + }, + optimization: { + minimize: false, + }, + }); + + compiler.outputFileSystem = createFsFromVolume(new Volume()); + + new CssMinimizerPlugin().apply(compiler); + + const stats = await compile(compiler); + + expect(getErrors(stats)).toMatchSnapshot("errors"); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + + // The standalone .css file should be minimized via processAssets + const cssOutput = compiler.outputFileSystem.readFileSync( + path.resolve(__dirname, "outputs/main.css"), + "utf8", + ); + + expect(cssOutput).not.toContain("color: red"); + }); +}); diff --git a/types/index.d.ts b/types/index.d.ts index 333b310..ced84cf 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -54,6 +54,15 @@ declare class CssMinimizerPlugin { * @type {InternalPluginOptions} */ private options; + /** + * Create a lazy worker factory. Workers are initialized on first call and + * shared across all callers within the same compilation. + * @private + * @param {number} availableNumberOfCores Available cores + * @param {number=} maxTasks Maximum tasks (limits worker count, defaults to availableNumberOfCores) + * @returns {{ getWorker: (() => MinimizerWorker) | undefined, numberOfWorkers: number, end: () => Promise }} Worker factory and cleanup + */ + private createWorkerFactory; /** * @private * @param {Compiler} compiler Compiler @@ -63,6 +72,20 @@ declare class CssMinimizerPlugin { * @returns {Promise} Promise */ private optimize; + /** + * Minify a CSS source and return the result with errors/warnings. + * Shared by both processAssets and processContent hooks. + * Callers are responsible for pushing returned warnings/errors to compilation. + * @private + * @param {Compiler} compiler Compiler + * @param {Compilation} compilation Compilation + * @param {string} name Asset or resource name + * @param {string | Buffer} rawInput CSS content + * @param {unknown} rawSourceMap Source map (validated internally) + * @param {(() => MinimizerWorker) | undefined} getWorker Worker factory + * @returns {Promise<{ source: import("webpack").sources.Source | undefined, warnings: EXPECTED_ANY[], errors: EXPECTED_ANY[] }>} Result + */ + private transformSource; /** * @param {Compiler} compiler Compiler * @returns {void} Void From 595a1497d38ef64f6cbebb83ed1b27b114120aaa Mon Sep 17 00:00:00 2001 From: xiaoxiaojx <784487301@qq.com> Date: Wed, 1 Apr 2026 01:08:21 +0800 Subject: [PATCH 2/3] fix: update --- package-lock.json | 6 +++--- package.json | 2 +- src/index.js | 23 +++++------------------ 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7246790..e248e70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,7 @@ "standard-version": "^9.5.0", "sugarss": "^5.0.0", "typescript": "^5.5.4", - "webpack": "https://pkg.pr.new/webpack@11c12b8" + "webpack": "https://pkg.pr.new/webpack@ba3f97c" }, "engines": { "node": ">= 20.9.0" @@ -20348,8 +20348,8 @@ }, "node_modules/webpack": { "version": "5.105.4", - "resolved": "https://pkg.pr.new/webpack@11c12b8", - "integrity": "sha512-wM8wK3GmlRyVJ3Au2n+fnN+9M5olX+64aYjv4Im2jsB02T19mTsSeTy0cAInupXLyOTkse43GDBwMiJwryKQqg==", + "resolved": "https://pkg.pr.new/webpack@ba3f97c", + "integrity": "sha512-INNrISoFMCR6Uu9zjFSwoHW7nI/iBK9FPtfQlw4DCgt+7ksUEqIBzaUFlnbjVhHQWxjmHfCDZwo0Mu2B6HNuJA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 730bfe7..0dd45fd 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "standard-version": "^9.5.0", "sugarss": "^5.0.0", "typescript": "^5.5.4", - "webpack": "https://pkg.pr.new/webpack@11c12b8" + "webpack": "https://pkg.pr.new/webpack@ba3f97c" }, "peerDependencies": { "webpack": "^5.0.0" diff --git a/src/index.js b/src/index.js index 020af4e..0c9d54d 100644 --- a/src/index.js +++ b/src/index.js @@ -793,37 +793,24 @@ class CssMinimizerPlugin { return source; } - const [content, sourceMap] = source; - if (!contentWorkerFactory) { contentWorkerFactory = this.createWorkerFactory( availableNumberOfCores, ); } + const { source: rawInput, map } = source.sourceAndMap(); + const output = await this.transformSource( compiler, compilation, name, - content, - sourceMap, + rawInput, + map, contentWorkerFactory.getWorker, ); - for (const warning of output.warnings) { - compilation.warnings.push(warning); - } - - for (const error of output.errors) { - compilation.errors.push(error); - } - - if (output.source) { - const { source: code, map } = output.source.sourceAndMap(); - return [/** @type {string} */ (code), map || undefined]; - } - - return source; + return output.source || source; }, ); } From 5d2d9c98146f220ec38634842fe4eae3aa8f40d9 Mon Sep 17 00:00:00 2001 From: xiaoxiaojx <784487301@qq.com> Date: Wed, 1 Apr 2026 01:16:20 +0800 Subject: [PATCH 3/3] fix: update --- src/index.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/index.js b/src/index.js index 0c9d54d..e728a92 100644 --- a/src/index.js +++ b/src/index.js @@ -810,6 +810,14 @@ class CssMinimizerPlugin { contentWorkerFactory.getWorker, ); + for (const warning of output.warnings) { + compilation.warnings.push(warning); + } + + for (const error of output.errors) { + compilation.errors.push(error); + } + return output.source || source; }, );