Skip to content

Commit 0ab8b33

Browse files
committed
fix(worker): preserve regexp options for process workers
1 parent 0039e4c commit 0ab8b33

4 files changed

Lines changed: 108 additions & 8 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"minimizer-webpack-plugin": patch
3+
---
4+
5+
Fallback to the serialized worker path when regular expression options are used with process workers that cannot preserve them.

‎src/implementation.js‎

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,36 @@ function holdsFunction(value, seen = new Set()) {
104104
);
105105
}
106106

107+
/**
108+
* Whether a value holds a regular expression anywhere inside it.
109+
* Child-process workers on runtimes without worker threads use the default
110+
* process IPC serializer, which does not preserve regular expressions.
111+
* @param {unknown} value what a worker would be handed
112+
* @param {Set<unknown>=} seen values already walked
113+
* @returns {boolean} true when a regular expression is in there
114+
*/
115+
function holdsRegExp(value, seen = new Set()) {
116+
if (value instanceof RegExp) {
117+
return true;
118+
}
119+
if (!value || typeof value !== "object" || seen.has(value)) {
120+
return false;
121+
}
122+
seen.add(value);
123+
124+
if (value instanceof Map) {
125+
return [...value].some(
126+
([key, one]) => holdsRegExp(key, seen) || holdsRegExp(one, seen),
127+
);
128+
}
129+
if (value instanceof Set) {
130+
return [...value].some((one) => holdsRegExp(one, seen));
131+
}
132+
return Object.values(/** @type {Record<string, unknown>} */ (value)).some(
133+
(one) => holdsRegExp(one, seen),
134+
);
135+
}
136+
107137
/**
108138
* True when every `minimizer.implementation` is a module path (`string` or
109139
* `{ path, export }`). Inline minify functions keep `transform`. When
@@ -112,9 +142,10 @@ function holdsFunction(value, seen = new Set()) {
112142
* whole asset task, even if that asset's own matched minimizers are paths.
113143
* @template T
114144
* @param {import("./index.js").InternalOptions<T>} options options
145+
* @param {{ enableWorkerThreads?: boolean }=} capabilities worker capabilities
115146
* @returns {boolean} whether `worker.minify` can run without `transform`
116147
*/
117-
function canMinifyByPath(options) {
148+
function canMinifyByPath(options, capabilities = {}) {
118149
/**
119150
* @param {unknown} implementation implementation
120151
* @returns {boolean} true when a module path is known
@@ -138,6 +169,13 @@ function canMinifyByPath(options) {
138169
) {
139170
return false;
140171
}
172+
if (
173+
capabilities.enableWorkerThreads === false &&
174+
(holdsRegExp(options.extractComments) ||
175+
holdsRegExp(options.minimizer.options))
176+
) {
177+
return false;
178+
}
141179

142180
if (!options.embedded) {
143181
return true;
@@ -146,6 +184,12 @@ function canMinifyByPath(options) {
146184
if (holdsFunction(options.embedded.options)) {
147185
return false;
148186
}
187+
if (
188+
capabilities.enableWorkerThreads === false &&
189+
holdsRegExp(options.embedded.options)
190+
) {
191+
return false;
192+
}
149193

150194
const embedded = Array.isArray(options.embedded.implementation)
151195
? options.embedded.implementation

‎src/index.js‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,23 @@ const {
4444
uglifyJsMinify,
4545
} = require("./utils");
4646

47+
/** @type {boolean | undefined} */
48+
let _canUseWorkerThreads;
49+
50+
const canUseWorkerThreads = () => {
51+
if (_canUseWorkerThreads !== undefined) {
52+
return _canUseWorkerThreads;
53+
}
54+
try {
55+
// eslint-disable-next-line n/no-unsupported-features/node-builtins -- feature detection for Node 10
56+
require("worker_threads");
57+
_canUseWorkerThreads = true;
58+
} catch (_error) {
59+
_canUseWorkerThreads = false;
60+
}
61+
return _canUseWorkerThreads;
62+
};
63+
4764
/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
4865
/** @typedef {import("webpack").Compiler} Compiler */
4966
/** @typedef {import("webpack").Compilation} Compilation */
@@ -840,12 +857,14 @@ class MinimizerPlugin {
840857
({ fn }) =>
841858
typeof fn.supportsBinary === "function" && fn.supportsBinary(),
842859
);
843-
const enableWorkerThreads = minimizerSlots.every(
844-
({ fn }, i) =>
845-
!workerCapable[i] ||
846-
typeof fn.supportsWorkerThreads === "undefined" ||
847-
fn.supportsWorkerThreads() !== false,
848-
);
860+
const enableWorkerThreads =
861+
canUseWorkerThreads() &&
862+
minimizerSlots.every(
863+
({ fn }, i) =>
864+
!workerCapable[i] ||
865+
typeof fn.supportsWorkerThreads === "undefined" ||
866+
fn.supportsWorkerThreads() !== false,
867+
);
849868
const needCreateWorker =
850869
optimizeOptions.availableNumberOfCores > 0 &&
851870
workerCapable.includes(true);
@@ -908,7 +927,11 @@ class MinimizerPlugin {
908927
// module paths — including every entry on `embedded`, not just the
909928
// asset's matched subset. A mixed path + inline-function config keeps
910929
// the whole asset on `transform`.
911-
if (canMinifyByPath(options)) {
930+
if (
931+
canMinifyByPath(options, {
932+
enableWorkerThreads,
933+
})
934+
) {
912935
return getWorker().minify(options);
913936
}
914937

‎test/implementation.test.js‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,34 @@ describe("canMinifyByPath", () => {
164164
).toBe(true);
165165
});
166166

167+
it("should reject a RegExp option when the worker cannot transfer it", () => {
168+
expect(
169+
canMinifyByPath(
170+
{
171+
minimizer: {
172+
implementation: pathImpl,
173+
options: { comments: /license/i },
174+
},
175+
},
176+
{ enableWorkerThreads: false },
177+
),
178+
).toBe(false);
179+
});
180+
181+
it("should allow a RegExp option when the worker can transfer it", () => {
182+
expect(
183+
canMinifyByPath(
184+
{
185+
minimizer: {
186+
implementation: pathImpl,
187+
options: { comments: /license/i },
188+
},
189+
},
190+
{ enableWorkerThreads: true },
191+
),
192+
).toBe(true);
193+
});
194+
167195
it("should allow embedded when every implementation is a path", () => {
168196
expect(
169197
canMinifyByPath({

0 commit comments

Comments
 (0)