Skip to content

Commit 74dd5f0

Browse files
authored
fix(dependencies): preserve wrappedContextRegExp flags in context matcher (#22303)
* fix(dependencies): preserve wrappedContextRegExp flags in context matcher The wrapped-request branches of ContextDependencyHelpers rebuilt the matcher from wrappedContextRegExp.source only, dropping the user's i/m/s/u flags, while the fallback path passed the RegExp object intact. So module.parser.javascript.wrappedContextRegExp: /\.jsx?$/i silently became case-sensitive. Reuse the flags (minus stateful g/y) when rebuilding the RegExp. Signed-off-by: Eshiv Pandey <[email protected]> * fix(dependencies): keep context matcher valid under Unicode flags Preserving the user's `wrappedContextRegExp` flags means `u`/`v` can now reach the rebuilt matcher. `quoteMeta` escaped `-` as `\-`, which is an invalid escape outside a character class in Unicode mode, so a request with a literal `-` in its static prefix or postfix (for example importing a template literal like ./dir/module-${x}.js) threw and aborted the build. `-` is a literal outside `[...]` and the quoted string is only ever concatenated between anchors, so leave it unescaped. Covers both the wrapped-context (template-literal) and wrapped-expression (concatenation) paths with Unicode-mode, hyphen-in-prefix cases. Signed-off-by: Eshiv Pandey <[email protected]> * test(dependencies): trim wrapped-context-regexp-flags comment to 3 lines Signed-off-by: Eshiv Pandey <[email protected]> --------- Signed-off-by: Eshiv Pandey <[email protected]>
1 parent e9e02fb commit 74dd5f0

5 files changed

Lines changed: 73 additions & 4 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": patch
3+
---
4+
5+
Preserve `wrappedContextRegExp` flags when rebuilding context request matchers.

‎lib/dependencies/context/ContextDependencyHelpers.js‎

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,27 @@ const { parseResource } = require("../../util/identifier");
2323
*/
2424

2525
/**
26-
* Escapes regular expression metacharacters
26+
* Escapes regular expression metacharacters.
27+
*
28+
* `-` is intentionally left unescaped: outside a character class it is a
29+
* literal, and the quoted string is only ever concatenated between anchors
30+
* (never inside `[...]`). Escaping it as `\-` would also be an invalid escape
31+
* under the `u`/`v` flags, which `getWrappedContextFlags` now preserves — so
32+
* e.g. `` import(`./dir/module-${x}.js`) `` with a Unicode `wrappedContextRegExp`
33+
* would throw when the matcher is rebuilt.
2734
* @param {string} str String to quote
2835
* @returns {string} Escaped string
2936
*/
30-
const quoteMeta = (str) => str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&");
37+
const quoteMeta = (str) => str.replace(/[[\]\\/{}()*+?.^$|]/g, "\\$&");
38+
39+
/**
40+
* Returns the flags of a wrapped context RegExp, minus the stateful `g`/`y`
41+
* flags, so the rebuilt matcher keeps `i`/`m`/`s`/`u` but stays safe to reuse
42+
* across ContextModuleFactory's per-candidate `regExp.test()` calls.
43+
* @param {RegExp} regExp the user's wrappedContextRegExp
44+
* @returns {string} flags to apply to the rebuilt RegExp
45+
*/
46+
const getWrappedContextFlags = (regExp) => regExp.flags.replace(/[gy]/g, "");
3147

3248
/**
3349
* Split context from prefix.
@@ -111,7 +127,10 @@ module.exports.create = (
111127
// expressions the gaps between them, so `./context/pre${e}inner${e}post`
112128
// matches `/^\.\/pre.*inner.*post$/` under the context `./context`.
113129
const regExp = new RegExp(
114-
`^${quoteMeta(prefix)}${innerRegExp}${quoteMeta(postfix)}$`
130+
`^${quoteMeta(prefix)}${innerRegExp}${quoteMeta(postfix)}$`,
131+
getWrappedContextFlags(
132+
/** @type {RegExp} */ (options.wrappedContextRegExp)
133+
)
115134
);
116135
const dep = new Dep(
117136
{
@@ -209,7 +228,10 @@ module.exports.create = (
209228
const regExp = new RegExp(
210229
`^${quoteMeta(prefix)}${
211230
/** @type {RegExp} */ (options.wrappedContextRegExp).source
212-
}${quoteMeta(postfix)}$`
231+
}${quoteMeta(postfix)}$`,
232+
getWrappedContextFlags(
233+
/** @type {RegExp} */ (options.wrappedContextRegExp)
234+
)
213235
);
214236
const dep = new Dep(
215237
{
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default "a";
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
it("should keep the `i` flag for concatenation context requests", async () => {
2+
const name = "module-a.js";
3+
// `import("./dir/" + name)` builds a wrapped context whose matcher is
4+
// rebuilt from `wrappedContextRegExp`; dropping its flags would exclude
5+
// `module-a.js` from the case-insensitive `/.*\.JS/i`.
6+
expect((await import("./dir/" + name)).default).toBe("a");
7+
});
8+
9+
it("should keep the `i` flag for template-literal context requests", async () => {
10+
const name = "module-a.js";
11+
expect((await import(`./dir/${name}`)).default).toBe("a");
12+
});
13+
14+
it("should build a valid Unicode-mode matcher for concatenation requests with a hyphen in the prefix", async () => {
15+
const suffix = "a.js";
16+
// The static prefix `./dir/module-` contains a `-`. With the `u` flag
17+
// preserved, quoting it as `\-` would be an invalid escape and rebuilding
18+
// the wrapped-expression matcher would throw, failing the build.
19+
expect((await import("./dir/module-" + suffix)).default).toBe("a");
20+
});
21+
22+
it("should build a valid Unicode-mode matcher for template-literal requests with a hyphen in the prefix", async () => {
23+
const suffix = "a.js";
24+
// Same for the wrapped-context (template-literal) path.
25+
expect((await import(`./dir/module-${suffix}`)).default).toBe("a");
26+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"use strict";
2+
3+
/** @type {import("../../../../").Configuration} */
4+
module.exports = {
5+
module: {
6+
parser: {
7+
javascript: {
8+
// `i` must survive so `.js` matches `\.JS`, and `u` must survive so a
9+
// literal `-` in the request prefix/postfix stays valid unescaped
10+
// (escaping it as `\-` is an invalid escape under Unicode mode).
11+
wrappedContextRegExp: /.*\.JS/iu
12+
}
13+
}
14+
}
15+
};

0 commit comments

Comments
 (0)