Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use os.availableParallelism() for parallelism when it is available #280

Merged
merged 2 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
43 changes: 35 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<T[P]> } : InferDefaultType<T>} MinimizerOptions
*/

/**
* @template T
* @callback BasicMinimizerImplementation
Expand All @@ -86,13 +91,13 @@ const { minify: minifyWorker } = require("./minify");
*/

/**
* @template T
* @typedef {T extends any[] ? { [P in keyof T]: BasicMinimizerImplementation<T[P]>; } : BasicMinimizerImplementation<T>} MinimizerImplementation
* @typedef {object} MinimizeFunctionHelpers
* @property {() => boolean | undefined} [supportsWorkerThreads]
*/

/**
* @template T
* @typedef {T extends any[] ? { [P in keyof T]?: InferDefaultType<T[P]> } : InferDefaultType<T>} MinimizerOptions
* @typedef {T extends any[] ? { [P in keyof T]: BasicMinimizerImplementation<T[P]> & MinimizeFunctionHelpers; } : BasicMinimizerImplementation<T> & MinimizeFunctionHelpers} MinimizerImplementation
*/

/**
Expand Down Expand Up @@ -389,11 +394,26 @@ 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);
}

/**
* @private
* @template T
* @param {BasicMinimizerImplementation<T> & MinimizeFunctionHelpers} implementation
* @returns {boolean}
*/
static isSupportsWorkerThreads(implementation) {
return typeof implementation.supportsWorkerThreads !== "undefined"
? implementation.supportsWorkerThreads() !== false
: true;
}

/**
Expand Down Expand Up @@ -477,7 +497,15 @@ class CssMinimizerPlugin {
initializedWorker = /** @type {MinimizerWorker<T>} */ (
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,
),
})
);

Expand Down Expand Up @@ -681,7 +709,6 @@ class CssMinimizerPlugin {
getWorker && numberOfAssetsForMinify > 0
? /** @type {number} */ (numberOfWorkers)
: scheduledTasks.length;

await throttleAll(limit, scheduledTasks);

if (initializedWorker) {
Expand Down
14 changes: 14 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ async function cssnanoMinify(
};
}

cssnanoMinify.supportsWorkerThreads = () => true;

/* istanbul ignore next */
/**
* @param {Input} input
Expand Down Expand Up @@ -213,6 +215,8 @@ async function cssoMinify(input, sourceMap, minimizerOptions) {
};
}

cssoMinify.supportsWorkerThreads = () => true;

/* istanbul ignore next */
/**
* @param {Input} input
Expand Down Expand Up @@ -256,6 +260,8 @@ async function cleanCssMinify(input, sourceMap, minimizerOptions) {
};
}

cleanCssMinify.supportsWorkerThreads = () => true;

/* istanbul ignore next */
/**
* @param {Input} input
Expand Down Expand Up @@ -347,6 +353,8 @@ async function esbuildMinify(input, sourceMap, minimizerOptions) {
};
}

esbuildMinify.supportsWorkerThreads = () => false;

// TODO remove in the next major release
/* istanbul ignore next */
/**
Expand Down Expand Up @@ -392,6 +400,8 @@ async function parcelCssMinify(input, sourceMap, minimizerOptions) {
};
}

parcelCssMinify.supportsWorkerThreads = () => false;

/* istanbul ignore next */
/**
* @param {Input} input
Expand Down Expand Up @@ -436,6 +446,8 @@ async function lightningCssMinify(input, sourceMap, minimizerOptions) {
};
}

lightningCssMinify.supportsWorkerThreads = () => false;

/* istanbul ignore next */
/**
* @param {Input} input
Expand Down Expand Up @@ -490,6 +502,8 @@ async function swcMinify(input, sourceMap, minimizerOptions) {
};
}

swcMinify.supportsWorkerThreads = () => false;

/**
* @template T
* @param fn {(function(): any) | undefined}
Expand Down
13 changes: 13 additions & 0 deletions test/__snapshots__/parallel-option.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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}",
Expand Down
37 changes: 34 additions & 3 deletions test/parallel-option.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}),
Expand Down Expand Up @@ -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;

Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
26 changes: 20 additions & 6 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ declare class CssMinimizerPlugin<T = CssNanoOptionsExtended> {
* @returns {number}
*/
private static getAvailableNumberOfCores;
/**
* @private
* @template T
* @param {BasicMinimizerImplementation<T> & MinimizeFunctionHelpers} implementation
* @returns {boolean}
*/
private static isSupportsWorkerThreads;
/**
* @param {BasePluginOptions & DefinedDefaultMinimizerAndOptions<T>} [options]
*/
Expand Down Expand Up @@ -91,9 +98,10 @@ declare namespace CssMinimizerPlugin {
Input,
CustomOptions,
InferDefaultType,
MinimizerOptions,
BasicMinimizerImplementation,
MinimizeFunctionHelpers,
MinimizerImplementation,
MinimizerOptions,
InternalOptions,
InternalResult,
Parallel,
Expand Down Expand Up @@ -164,17 +172,23 @@ type CustomOptions = {
[key: string]: any;
};
type InferDefaultType<T> = T extends infer U ? U : CustomOptions;
type MinimizerOptions<T> = T extends any[]
? { [P in keyof T]?: InferDefaultType<T[P]> }
: InferDefaultType<T>;
type BasicMinimizerImplementation<T> = (
input: Input,
sourceMap: RawSourceMap | undefined,
minifyOptions: InferDefaultType<T>,
) => Promise<MinimizedResult>;
type MinimizeFunctionHelpers = {
supportsWorkerThreads?: (() => boolean | undefined) | undefined;
};
type MinimizerImplementation<T> = T extends any[]
? { [P in keyof T]: BasicMinimizerImplementation<T[P]> }
: BasicMinimizerImplementation<T>;
type MinimizerOptions<T> = T extends any[]
? { [P in keyof T]?: InferDefaultType<T[P]> }
: InferDefaultType<T>;
? {
[P in keyof T]: BasicMinimizerImplementation<T[P]> &
MinimizeFunctionHelpers;
}
: BasicMinimizerImplementation<T> & MinimizeFunctionHelpers;
type InternalOptions<T> = {
name: string;
input: string;
Expand Down
21 changes: 21 additions & 0 deletions types/utils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export function cssnanoMinify(
sourceMap: RawSourceMap | undefined,
minimizerOptions?: CustomOptions,
): Promise<MinimizedResult>;
export namespace cssnanoMinify {
function supportsWorkerThreads(): boolean;
}
/**
* @param {Input} input
* @param {RawSourceMap | undefined} sourceMap
Expand All @@ -45,6 +48,9 @@ export function cssoMinify(
sourceMap: RawSourceMap | undefined,
minimizerOptions: CustomOptions,
): Promise<MinimizedResult>;
export namespace cssoMinify {
function supportsWorkerThreads(): boolean;
}
/**
* @param {Input} input
* @param {RawSourceMap | undefined} sourceMap
Expand All @@ -56,6 +62,9 @@ export function cleanCssMinify(
sourceMap: RawSourceMap | undefined,
minimizerOptions: CustomOptions,
): Promise<MinimizedResult>;
export namespace cleanCssMinify {
function supportsWorkerThreads(): boolean;
}
/**
* @param {Input} input
* @param {RawSourceMap | undefined} sourceMap
Expand All @@ -67,6 +76,9 @@ export function esbuildMinify(
sourceMap: RawSourceMap | undefined,
minimizerOptions: CustomOptions,
): Promise<MinimizedResult>;
export namespace esbuildMinify {
function supportsWorkerThreads(): boolean;
}
/**
* @param {Input} input
* @param {RawSourceMap | undefined} sourceMap
Expand All @@ -78,6 +90,9 @@ export function parcelCssMinify(
sourceMap: RawSourceMap | undefined,
minimizerOptions: CustomOptions,
): Promise<MinimizedResult>;
export namespace parcelCssMinify {
function supportsWorkerThreads(): boolean;
}
/**
* @param {Input} input
* @param {RawSourceMap | undefined} sourceMap
Expand All @@ -89,6 +104,9 @@ export function lightningCssMinify(
sourceMap: RawSourceMap | undefined,
minimizerOptions: CustomOptions,
): Promise<MinimizedResult>;
export namespace lightningCssMinify {
function supportsWorkerThreads(): boolean;
}
/**
* @param {Input} input
* @param {RawSourceMap | undefined} sourceMap
Expand All @@ -100,3 +118,6 @@ export function swcMinify(
sourceMap: RawSourceMap | undefined,
minimizerOptions: CustomOptions,
): Promise<MinimizedResult>;
export namespace swcMinify {
function supportsWorkerThreads(): boolean;
}