From ed1e2935d86b383f4ac96d0ea8028d6707c7a9c0 Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Thu, 6 Mar 2025 16:47:32 +0300 Subject: [PATCH 1/2] fix: use os.availableParallelism() for parallelism when it is available --- README.md | 2 +- src/index.js | 10 +++-- .../parallel-option.test.js.snap | 13 +++++++ test/parallel-option.test.js | 37 +++++++++++++++++-- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2b7d6ef..793fa31 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ Type: `Boolean|Number` Default: `true` Use multi-process parallel running to improve the build speed. -Default number of concurrent runs: `os.cpus().length - 1`. +Default number of concurrent runs: `os.cpus().length - 1` or `os.availableParallelism() - 1` (if this function is supported). > ℹ️ Parallelization can speed up your build significantly and is therefore **highly recommended**. > If a parallelization is enabled, the packages in `minimizerOptions` must be required via strings (`packageName` or `require.resolve(packageName)`). Read more in [`minimizerOptions`](#minimizeroptions) diff --git a/src/index.js b/src/index.js index ef3a434..ccdb8e7 100644 --- a/src/index.js +++ b/src/index.js @@ -389,11 +389,14 @@ class CssMinimizerPlugin { static getAvailableNumberOfCores(parallel) { // In some cases cpus() returns undefined // https://github.com/nodejs/node/issues/19022 - const cpus = os.cpus() || { length: 1 }; + const cpus = + typeof os.availableParallelism === "function" + ? { length: os.availableParallelism() } + : os.cpus() || { length: 1 }; - return parallel === true + return parallel === true || typeof parallel === "undefined" ? cpus.length - 1 - : Math.min(Number(parallel) || 0, cpus.length - 1); + : Math.min(parallel || 0, cpus.length - 1); } /** @@ -681,7 +684,6 @@ class CssMinimizerPlugin { getWorker && numberOfAssetsForMinify > 0 ? /** @type {number} */ (numberOfWorkers) : scheduledTasks.length; - await throttleAll(limit, scheduledTasks); if (initializedWorker) { diff --git a/test/__snapshots__/parallel-option.test.js.snap b/test/__snapshots__/parallel-option.test.js.snap index 69075e0..fd72572 100644 --- a/test/__snapshots__/parallel-option.test.js.snap +++ b/test/__snapshots__/parallel-option.test.js.snap @@ -90,6 +90,19 @@ exports[`parallel option should match snapshot for the "true" value: errors 1`] exports[`parallel option should match snapshot for the "true" value: warnings 1`] = `[]`; +exports[`parallel option should match snapshot for the "undefined" value: assets 1`] = ` +{ + "four.css": "body{color:red}a{color:blue}", + "one.css": "body{color:red}a{color:blue}", + "three.css": "body{color:red}a{color:blue}", + "two.css": "body{color:red}a{color:blue}", +} +`; + +exports[`parallel option should match snapshot for the "undefined" value: errors 1`] = `[]`; + +exports[`parallel option should match snapshot for the "undefined" value: warnings 1`] = `[]`; + exports[`parallel option should match snapshot when a value is not specify: assets 1`] = ` { "four.css": "body{color:red}a{color:blue}", diff --git a/test/parallel-option.test.js b/test/parallel-option.test.js index 44091d4..a4db4cc 100644 --- a/test/parallel-option.test.js +++ b/test/parallel-option.test.js @@ -14,8 +14,11 @@ import { jest.mock("os", () => { const actualOs = jest.requireActual("os"); + const isAvailableParallelism = + typeof actualOs.availableParallelism !== "undefined"; const mocked = { + availableParallelism: isAvailableParallelism ? jest.fn(() => 4) : undefined, cpus: jest.fn(() => { return { length: 4 }; }), @@ -52,6 +55,14 @@ jest.mock("jest-worker", () => { const workerPath = require.resolve("../src/minify"); +const getParallelism = () => { + if (typeof os.availableParallelism !== "undefined") { + return os.availableParallelism(); + } + + return os.cpus().length; +}; + describe("parallel option", () => { let compiler; @@ -76,7 +87,7 @@ describe("parallel option", () => { expect(Worker).toHaveBeenCalledTimes(1); expect(Worker).toHaveBeenLastCalledWith(workerPath, { enableWorkerThreads: ENABLE_WORKER_THREADS, - numWorkers: os.cpus().length - 1, + numWorkers: getParallelism() - 1, }); expect(workerTransform).toHaveBeenCalledTimes( Object.keys(readAssets(compiler, stats, /\.css$/)).length, @@ -108,7 +119,27 @@ describe("parallel option", () => { expect(Worker).toHaveBeenCalledTimes(1); expect(Worker).toHaveBeenLastCalledWith(workerPath, { enableWorkerThreads: ENABLE_WORKER_THREADS, - numWorkers: Math.min(4, os.cpus().length - 1), + numWorkers: Math.min(4, getParallelism() - 1), + }); + expect(workerTransform).toHaveBeenCalledTimes( + Object.keys(readAssets(compiler, stats, /\.css$/)).length, + ); + expect(workerEnd).toHaveBeenCalledTimes(1); + + expect(readAssets(compiler, stats, /\.css$/)).toMatchSnapshot("assets"); + expect(getErrors(stats)).toMatchSnapshot("errors"); + expect(getWarnings(stats)).toMatchSnapshot("warnings"); + }); + + it('should match snapshot for the "undefined" value', async () => { + new CssMinimizerPlugin({ parallel: undefined }).apply(compiler); + + const stats = await compile(compiler); + + expect(Worker).toHaveBeenCalledTimes(1); + expect(Worker).toHaveBeenLastCalledWith(workerPath, { + enableWorkerThreads: ENABLE_WORKER_THREADS, + numWorkers: Math.min(4, getParallelism() - 1), }); expect(workerTransform).toHaveBeenCalledTimes( Object.keys(readAssets(compiler, stats, /\.css$/)).length, @@ -152,7 +183,7 @@ describe("parallel option", () => { expect(Worker).toHaveBeenCalledTimes(1); expect(Worker).toHaveBeenLastCalledWith(workerPath, { enableWorkerThreads: ENABLE_WORKER_THREADS, - numWorkers: Math.min(1, os.cpus().length - 1), + numWorkers: Math.min(1, getParallelism() - 1), }); expect(workerTransform).toHaveBeenCalledTimes( Object.keys(readAssets(compiler, stats, /\.css$/)).length, From f13fd83c47c8cfad6c371220c95f59008f3bae07 Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Thu, 6 Mar 2025 17:01:29 +0300 Subject: [PATCH 2/2] fix: better support worker threads --- src/index.js | 33 +++++++++++++++++++++++++++++---- src/utils.js | 14 ++++++++++++++ types/index.d.ts | 26 ++++++++++++++++++++------ types/utils.d.ts | 21 +++++++++++++++++++++ 4 files changed, 84 insertions(+), 10 deletions(-) diff --git a/src/index.js b/src/index.js index ccdb8e7..8a9f367 100644 --- a/src/index.js +++ b/src/index.js @@ -76,6 +76,11 @@ const { minify: minifyWorker } = require("./minify"); * @typedef {T extends infer U ? U : CustomOptions} InferDefaultType */ +/** + * @template T + * @typedef {T extends any[] ? { [P in keyof T]?: InferDefaultType } : InferDefaultType} MinimizerOptions + */ + /** * @template T * @callback BasicMinimizerImplementation @@ -86,13 +91,13 @@ const { minify: minifyWorker } = require("./minify"); */ /** - * @template T - * @typedef {T extends any[] ? { [P in keyof T]: BasicMinimizerImplementation; } : BasicMinimizerImplementation} MinimizerImplementation + * @typedef {object} MinimizeFunctionHelpers + * @property {() => boolean | undefined} [supportsWorkerThreads] */ /** * @template T - * @typedef {T extends any[] ? { [P in keyof T]?: InferDefaultType } : InferDefaultType} MinimizerOptions + * @typedef {T extends any[] ? { [P in keyof T]: BasicMinimizerImplementation & MinimizeFunctionHelpers; } : BasicMinimizerImplementation & MinimizeFunctionHelpers} MinimizerImplementation */ /** @@ -399,6 +404,18 @@ class CssMinimizerPlugin { : Math.min(parallel || 0, cpus.length - 1); } + /** + * @private + * @template T + * @param {BasicMinimizerImplementation & MinimizeFunctionHelpers} implementation + * @returns {boolean} + */ + static isSupportsWorkerThreads(implementation) { + return typeof implementation.supportsWorkerThreads !== "undefined" + ? implementation.supportsWorkerThreads() !== false + : true; + } + /** * @private * @param {Compiler} compiler @@ -480,7 +497,15 @@ class CssMinimizerPlugin { initializedWorker = /** @type {MinimizerWorker} */ ( new Worker(require.resolve("./minify"), { numWorkers: numberOfWorkers, - enableWorkerThreads: true, + enableWorkerThreads: Array.isArray( + this.options.minimizer.implementation, + ) + ? this.options.minimizer.implementation.every((item) => + CssMinimizerPlugin.isSupportsWorkerThreads(item), + ) + : CssMinimizerPlugin.isSupportsWorkerThreads( + this.options.minimizer.implementation, + ), }) ); diff --git a/src/utils.js b/src/utils.js index 036cfb5..a576746 100644 --- a/src/utils.js +++ b/src/utils.js @@ -186,6 +186,8 @@ async function cssnanoMinify( }; } +cssnanoMinify.supportsWorkerThreads = () => true; + /* istanbul ignore next */ /** * @param {Input} input @@ -213,6 +215,8 @@ async function cssoMinify(input, sourceMap, minimizerOptions) { }; } +cssoMinify.supportsWorkerThreads = () => true; + /* istanbul ignore next */ /** * @param {Input} input @@ -256,6 +260,8 @@ async function cleanCssMinify(input, sourceMap, minimizerOptions) { }; } +cleanCssMinify.supportsWorkerThreads = () => true; + /* istanbul ignore next */ /** * @param {Input} input @@ -347,6 +353,8 @@ async function esbuildMinify(input, sourceMap, minimizerOptions) { }; } +esbuildMinify.supportsWorkerThreads = () => false; + // TODO remove in the next major release /* istanbul ignore next */ /** @@ -392,6 +400,8 @@ async function parcelCssMinify(input, sourceMap, minimizerOptions) { }; } +parcelCssMinify.supportsWorkerThreads = () => false; + /* istanbul ignore next */ /** * @param {Input} input @@ -436,6 +446,8 @@ async function lightningCssMinify(input, sourceMap, minimizerOptions) { }; } +lightningCssMinify.supportsWorkerThreads = () => false; + /* istanbul ignore next */ /** * @param {Input} input @@ -490,6 +502,8 @@ async function swcMinify(input, sourceMap, minimizerOptions) { }; } +swcMinify.supportsWorkerThreads = () => false; + /** * @template T * @param fn {(function(): any) | undefined} diff --git a/types/index.d.ts b/types/index.d.ts index 4f10559..8ff1703 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -34,6 +34,13 @@ declare class CssMinimizerPlugin { * @returns {number} */ private static getAvailableNumberOfCores; + /** + * @private + * @template T + * @param {BasicMinimizerImplementation & MinimizeFunctionHelpers} implementation + * @returns {boolean} + */ + private static isSupportsWorkerThreads; /** * @param {BasePluginOptions & DefinedDefaultMinimizerAndOptions} [options] */ @@ -91,9 +98,10 @@ declare namespace CssMinimizerPlugin { Input, CustomOptions, InferDefaultType, + MinimizerOptions, BasicMinimizerImplementation, + MinimizeFunctionHelpers, MinimizerImplementation, - MinimizerOptions, InternalOptions, InternalResult, Parallel, @@ -164,17 +172,23 @@ type CustomOptions = { [key: string]: any; }; type InferDefaultType = T extends infer U ? U : CustomOptions; +type MinimizerOptions = T extends any[] + ? { [P in keyof T]?: InferDefaultType } + : InferDefaultType; type BasicMinimizerImplementation = ( input: Input, sourceMap: RawSourceMap | undefined, minifyOptions: InferDefaultType, ) => Promise; +type MinimizeFunctionHelpers = { + supportsWorkerThreads?: (() => boolean | undefined) | undefined; +}; type MinimizerImplementation = T extends any[] - ? { [P in keyof T]: BasicMinimizerImplementation } - : BasicMinimizerImplementation; -type MinimizerOptions = T extends any[] - ? { [P in keyof T]?: InferDefaultType } - : InferDefaultType; + ? { + [P in keyof T]: BasicMinimizerImplementation & + MinimizeFunctionHelpers; + } + : BasicMinimizerImplementation & MinimizeFunctionHelpers; type InternalOptions = { name: string; input: string; diff --git a/types/utils.d.ts b/types/utils.d.ts index d4fb2f8..9549d12 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -34,6 +34,9 @@ export function cssnanoMinify( sourceMap: RawSourceMap | undefined, minimizerOptions?: CustomOptions, ): Promise; +export namespace cssnanoMinify { + function supportsWorkerThreads(): boolean; +} /** * @param {Input} input * @param {RawSourceMap | undefined} sourceMap @@ -45,6 +48,9 @@ export function cssoMinify( sourceMap: RawSourceMap | undefined, minimizerOptions: CustomOptions, ): Promise; +export namespace cssoMinify { + function supportsWorkerThreads(): boolean; +} /** * @param {Input} input * @param {RawSourceMap | undefined} sourceMap @@ -56,6 +62,9 @@ export function cleanCssMinify( sourceMap: RawSourceMap | undefined, minimizerOptions: CustomOptions, ): Promise; +export namespace cleanCssMinify { + function supportsWorkerThreads(): boolean; +} /** * @param {Input} input * @param {RawSourceMap | undefined} sourceMap @@ -67,6 +76,9 @@ export function esbuildMinify( sourceMap: RawSourceMap | undefined, minimizerOptions: CustomOptions, ): Promise; +export namespace esbuildMinify { + function supportsWorkerThreads(): boolean; +} /** * @param {Input} input * @param {RawSourceMap | undefined} sourceMap @@ -78,6 +90,9 @@ export function parcelCssMinify( sourceMap: RawSourceMap | undefined, minimizerOptions: CustomOptions, ): Promise; +export namespace parcelCssMinify { + function supportsWorkerThreads(): boolean; +} /** * @param {Input} input * @param {RawSourceMap | undefined} sourceMap @@ -89,6 +104,9 @@ export function lightningCssMinify( sourceMap: RawSourceMap | undefined, minimizerOptions: CustomOptions, ): Promise; +export namespace lightningCssMinify { + function supportsWorkerThreads(): boolean; +} /** * @param {Input} input * @param {RawSourceMap | undefined} sourceMap @@ -100,3 +118,6 @@ export function swcMinify( sourceMap: RawSourceMap | undefined, minimizerOptions: CustomOptions, ): Promise; +export namespace swcMinify { + function supportsWorkerThreads(): boolean; +}