@rspack/core 2.1.0-beta.0 → 2.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,143 +1,6 @@
1
1
  /******/ (() => { // webpackBootstrap
2
2
  /******/ var __webpack_modules__ = ({
3
3
 
4
- /***/ 428:
5
- /***/ ((module) => {
6
-
7
- module.exports = function (glob, opts) {
8
- if (typeof glob !== 'string') {
9
- throw new TypeError('Expected a string');
10
- }
11
-
12
- var str = String(glob);
13
-
14
- // The regexp we are building, as a string.
15
- var reStr = "";
16
-
17
- // Whether we are matching so called "extended" globs (like bash) and should
18
- // support single character matching, matching ranges of characters, group
19
- // matching, etc.
20
- var extended = opts ? !!opts.extended : false;
21
-
22
- // When globstar is _false_ (default), '/foo/*' is translated a regexp like
23
- // '^\/foo\/.*$' which will match any string beginning with '/foo/'
24
- // When globstar is _true_, '/foo/*' is translated to regexp like
25
- // '^\/foo\/[^/]*$' which will match any string beginning with '/foo/' BUT
26
- // which does not have a '/' to the right of it.
27
- // E.g. with '/foo/*' these will match: '/foo/bar', '/foo/bar.txt' but
28
- // these will not '/foo/bar/baz', '/foo/bar/baz.txt'
29
- // Lastely, when globstar is _true_, '/foo/**' is equivelant to '/foo/*' when
30
- // globstar is _false_
31
- var globstar = opts ? !!opts.globstar : false;
32
-
33
- // If we are doing extended matching, this boolean is true when we are inside
34
- // a group (eg {*.html,*.js}), and false otherwise.
35
- var inGroup = false;
36
-
37
- // RegExp flags (eg "i" ) to pass in to RegExp constructor.
38
- var flags = opts && typeof( opts.flags ) === "string" ? opts.flags : "";
39
-
40
- var c;
41
- for (var i = 0, len = str.length; i < len; i++) {
42
- c = str[i];
43
-
44
- switch (c) {
45
- case "/":
46
- case "$":
47
- case "^":
48
- case "+":
49
- case ".":
50
- case "(":
51
- case ")":
52
- case "=":
53
- case "!":
54
- case "|":
55
- reStr += "\\" + c;
56
- break;
57
-
58
- case "?":
59
- if (extended) {
60
- reStr += ".";
61
- break;
62
- }
63
-
64
- case "[":
65
- case "]":
66
- if (extended) {
67
- reStr += c;
68
- break;
69
- }
70
-
71
- case "{":
72
- if (extended) {
73
- inGroup = true;
74
- reStr += "(";
75
- break;
76
- }
77
-
78
- case "}":
79
- if (extended) {
80
- inGroup = false;
81
- reStr += ")";
82
- break;
83
- }
84
-
85
- case ",":
86
- if (inGroup) {
87
- reStr += "|";
88
- break;
89
- }
90
- reStr += "\\" + c;
91
- break;
92
-
93
- case "*":
94
- // Move over all consecutive "*"'s.
95
- // Also store the previous and next characters
96
- var prevChar = str[i - 1];
97
- var starCount = 1;
98
- while(str[i + 1] === "*") {
99
- starCount++;
100
- i++;
101
- }
102
- var nextChar = str[i + 1];
103
-
104
- if (!globstar) {
105
- // globstar is disabled, so treat any number of "*" as one
106
- reStr += ".*";
107
- } else {
108
- // globstar is enabled, so determine if this is a globstar segment
109
- var isGlobstar = starCount > 1 // multiple "*"'s
110
- && (prevChar === "/" || prevChar === undefined) // from the start of the segment
111
- && (nextChar === "/" || nextChar === undefined) // to the end of the segment
112
-
113
- if (isGlobstar) {
114
- // it's a globstar, so match zero or more path segments
115
- reStr += "((?:[^/]*(?:\/|$))*)";
116
- i++; // move over the "/"
117
- } else {
118
- // it's not a globstar, so only match one path segment
119
- reStr += "([^/]*)";
120
- }
121
- }
122
- break;
123
-
124
- default:
125
- reStr += c;
126
- }
127
- }
128
-
129
- // When regexp 'g' flag is specified don't
130
- // constrain the regular expression with ^ & $
131
- if (!flags || !~flags.indexOf('g')) {
132
- reStr = "^" + reStr + "$";
133
- }
134
-
135
- return new RegExp(reStr, flags);
136
- };
137
-
138
-
139
- /***/ }),
140
-
141
4
  /***/ 648:
142
5
  /***/ ((module) => {
143
6
 
@@ -1111,7 +974,7 @@ function patch (fs) {
1111
974
 
1112
975
  /***/ }),
1113
976
 
1114
- /***/ 600:
977
+ /***/ 409:
1115
978
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
1116
979
 
1117
980
  "use strict";
@@ -1125,7 +988,7 @@ const { EventEmitter } = __nccwpck_require__(434);
1125
988
  const path = __nccwpck_require__(928);
1126
989
  const fs = __nccwpck_require__(692);
1127
990
 
1128
- const watchEventSource = __nccwpck_require__(313);
991
+ const watchEventSource = __nccwpck_require__(92);
1129
992
 
1130
993
  /** @typedef {import("./index").IgnoredFunction} IgnoredFunction */
1131
994
  /** @typedef {import("./index").EventType} EventType */
@@ -1145,13 +1008,28 @@ let FS_ACCURACY = 2000;
1145
1008
  const IS_OSX = (__nccwpck_require__(857).platform)() === "darwin";
1146
1009
  const IS_WIN = (__nccwpck_require__(857).platform)() === "win32";
1147
1010
 
1148
- const { WATCHPACK_POLLING } = process.env;
1011
+ const { WATCHPACK_POLLING, WATCHPACK_RETRIES } = process.env;
1149
1012
  const FORCE_POLLING =
1150
1013
  // @ts-expect-error avoid additional checks
1151
1014
  `${+WATCHPACK_POLLING}` === WATCHPACK_POLLING
1152
1015
  ? +WATCHPACK_POLLING
1153
1016
  : Boolean(WATCHPACK_POLLING) && WATCHPACK_POLLING !== "false";
1154
1017
 
1018
+ // Number of retries (and delay between retries, in ms) when an fs operation
1019
+ // returns EBUSY. EBUSY is transient on Windows when an AV scanner, indexer,
1020
+ // or another process briefly locks a file; retrying avoids incorrectly
1021
+ // reporting the file as removed (see webpack/watchpack#223, #44).
1022
+ //
1023
+ // Configurable via `WATCHPACK_RETRIES` env var: an integer >= 0, or "false"
1024
+ // to disable retries entirely. Unset / invalid values fall back to 3.
1025
+ const BUSY_RETRIES = (() => {
1026
+ if (WATCHPACK_RETRIES === undefined) return 3;
1027
+ if (WATCHPACK_RETRIES === "false") return 0;
1028
+ const n = Number(WATCHPACK_RETRIES);
1029
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 3;
1030
+ })();
1031
+ const BUSY_RETRY_DELAY = 100;
1032
+
1155
1033
  /**
1156
1034
  * @param {string} str string
1157
1035
  * @returns {string} lower cased string
@@ -1194,6 +1072,38 @@ function ensureFsAccuracy(mtime) {
1194
1072
  else if (FS_ACCURACY > 1000 && mtime % 1000 !== 0) FS_ACCURACY = 1000;
1195
1073
  }
1196
1074
 
1075
+ /**
1076
+ * Call `fs.lstat` with retries on EBUSY. Transient EBUSY errors are common
1077
+ * on Windows when another process (AV scanner, indexer, editor) holds an
1078
+ * open handle on the file. See webpack/watchpack#223, #44.
1079
+ *
1080
+ * The retry count is taken from the `WATCHPACK_RETRIES` env var (default
1081
+ * 3, set to "0" or "false" to disable retrying). The hot path is a single
1082
+ * `fs.lstat` call with one inline callback; the timer and the recursive
1083
+ * call are only scheduled when an EBUSY is actually observed.
1084
+ * @param {string} target target path
1085
+ * @param {{ closed: boolean }} watcher owning watcher (checked between retries)
1086
+ * @param {(err: NodeJS.ErrnoException | null, stats: import("fs").Stats) => void} callback callback
1087
+ * @param {number=} remaining retries remaining (defaults to `BUSY_RETRIES`)
1088
+ */
1089
+ function lstatWithRetry(target, watcher, callback, remaining = BUSY_RETRIES) {
1090
+ fs.lstat(target, (err, stats) => {
1091
+ if (
1092
+ err &&
1093
+ /** @type {NodeJS.ErrnoException} */ (err).code === "EBUSY" &&
1094
+ remaining > 0 &&
1095
+ !watcher.closed
1096
+ ) {
1097
+ setTimeout(
1098
+ () => lstatWithRetry(target, watcher, callback, remaining - 1),
1099
+ BUSY_RETRY_DELAY,
1100
+ );
1101
+ return;
1102
+ }
1103
+ callback(err, stats);
1104
+ });
1105
+ }
1106
+
1197
1107
  /**
1198
1108
  * @typedef {object} FileWatcherEvents
1199
1109
  * @property {(type: EventType) => void} initial-missing initial missing event
@@ -1284,6 +1194,8 @@ class DirectoryWatcher extends EventEmitter {
1284
1194
  this.filesWithoutCase = new Map();
1285
1195
  /** @type {Map<string, Watcher<DirectoryWatcherEvents> | boolean>} */
1286
1196
  this.directories = new Map();
1197
+ /** @type {Map<string, Watcher<FileWatcherEvents>>} */
1198
+ this._symlinkTargetWatchers = new Map();
1287
1199
  this.lastWatchEvent = 0;
1288
1200
  this.initialScan = true;
1289
1201
  this.ignored = options.ignored || (() => false);
@@ -1544,6 +1456,10 @@ class DirectoryWatcher extends EventEmitter {
1544
1456
  (watcher).close();
1545
1457
  this.directories.set(directory, true);
1546
1458
  }
1459
+ for (const w of this._symlinkTargetWatchers.values()) {
1460
+ w.close();
1461
+ }
1462
+ this._symlinkTargetWatchers.clear();
1547
1463
  }
1548
1464
  }
1549
1465
  }
@@ -1667,7 +1583,7 @@ class DirectoryWatcher extends EventEmitter {
1667
1583
  const checkStats = () => {
1668
1584
  if (this.closed) return;
1669
1585
  this._activeEvents.set(filename, false);
1670
- fs.lstat(target, (err, stats) => {
1586
+ lstatWithRetry(target, this, (err, stats) => {
1671
1587
  if (this.closed) return;
1672
1588
  if (this._activeEvents.get(filename) === true) {
1673
1589
  process.nextTick(checkStats);
@@ -1676,6 +1592,9 @@ class DirectoryWatcher extends EventEmitter {
1676
1592
  this._activeEvents.delete(filename);
1677
1593
  // ENOENT happens when the file/directory doesn't exist
1678
1594
  // EPERM happens when the containing directory doesn't exist
1595
+ // EBUSY happens when another process has the file locked (e.g.
1596
+ // Windows AV scanner). lstatWithRetry already retried before
1597
+ // giving up here.
1679
1598
  if (err) {
1680
1599
  if (
1681
1600
  err.code !== "ENOENT" &&
@@ -1692,6 +1611,13 @@ class DirectoryWatcher extends EventEmitter {
1692
1611
  }
1693
1612
  this.lastWatchEvent = Date.now();
1694
1613
  if (!stats) {
1614
+ // On EBUSY we keep the tracked state: the file is likely still
1615
+ // there and a later event or scan will reconcile. Emitting a
1616
+ // remove here would make the watch appear to stop after a
1617
+ // transient lock (webpack/watchpack#223, #44).
1618
+ if (err && err.code === "EBUSY" && BUSY_RETRIES > 0) {
1619
+ return;
1620
+ }
1695
1621
  this.setMissing(target, false, eventType);
1696
1622
  } else if (stats.isDirectory()) {
1697
1623
  this.setDirectory(target, +stats.birthtime || 1, false, eventType);
@@ -1841,7 +1767,20 @@ class DirectoryWatcher extends EventEmitter {
1841
1767
  fs.readdir(this.path, (err, items) => {
1842
1768
  if (this.closed) return;
1843
1769
  if (err) {
1844
- if (err.code === "ENOENT" || err.code === "EPERM") {
1770
+ // Mirror the lstat error handling below: treat permission /
1771
+ // invalid-argument / no-device errors on the directory itself
1772
+ // as removed rather than logging "Watchpack Error (initial
1773
+ // scan)". These surface for unreadable mounts (WSL `/mnt/c`,
1774
+ // fuse mounts), unmounted devices (`/efi`), and libuv's
1775
+ // post-Node 22.17 `EINVAL` on protected Windows paths
1776
+ // (see #187).
1777
+ if (
1778
+ err.code === "ENOENT" ||
1779
+ err.code === "EPERM" ||
1780
+ err.code === "EACCES" ||
1781
+ err.code === "ENODEV" ||
1782
+ (err.code === "EINVAL" && IS_WIN)
1783
+ ) {
1845
1784
  this.onDirectoryRemoved("scan readdir failed");
1846
1785
  } else {
1847
1786
  this.onScanError(err);
@@ -1918,7 +1857,7 @@ class DirectoryWatcher extends EventEmitter {
1918
1857
  }
1919
1858
  });
1920
1859
  for (const itemPath of itemPaths) {
1921
- fs.lstat(itemPath, (err2, stats) => {
1860
+ lstatWithRetry(itemPath, this, (err2, stats) => {
1922
1861
  if (this.closed) return;
1923
1862
  if (err2) {
1924
1863
  if (
@@ -1926,39 +1865,115 @@ class DirectoryWatcher extends EventEmitter {
1926
1865
  err2.code === "EPERM" ||
1927
1866
  err2.code === "EACCES" ||
1928
1867
  err2.code === "EBUSY" ||
1868
+ err2.code === "ENODEV" ||
1929
1869
  // TODO https://github.com/libuv/libuv/pull/4566
1930
1870
  (err2.code === "EINVAL" && IS_WIN)
1931
1871
  ) {
1932
- this.setMissing(itemPath, initial, `scan (${err2.code})`);
1872
+ // readdir saw the entry but we can't stat it due to a
1873
+ // transient lock — keep the previously-known entry instead
1874
+ // of incorrectly flagging it as missing.
1875
+ if (
1876
+ !(
1877
+ err2.code === "EBUSY" &&
1878
+ BUSY_RETRIES > 0 &&
1879
+ this.files.has(itemPath)
1880
+ )
1881
+ ) {
1882
+ this.setMissing(itemPath, initial, `scan (${err2.code})`);
1883
+ }
1933
1884
  } else {
1934
1885
  this.onScanError(err2);
1935
1886
  }
1936
1887
  itemFinished();
1937
1888
  return;
1938
1889
  }
1939
- if (stats.isFile() || stats.isSymbolicLink()) {
1940
- if (stats.mtime) {
1941
- ensureFsAccuracy(+stats.mtime);
1890
+ /**
1891
+ * @param {string | null} realPath resolved real path for an outside-dir symlink target
1892
+ */
1893
+ const apply = (realPath) => {
1894
+ if (stats.isFile() || stats.isSymbolicLink()) {
1895
+ if (stats.mtime) ensureFsAccuracy(+stats.mtime);
1896
+ this.setFileTime(
1897
+ itemPath,
1898
+ +stats.mtime || +stats.ctime || 1,
1899
+ initial,
1900
+ true,
1901
+ "scan (file)",
1902
+ );
1903
+ if (realPath && !this._symlinkTargetWatchers.has(itemPath)) {
1904
+ const w = this.watcherManager.watchFile(realPath, Date.now());
1905
+ if (w) {
1906
+ w.on("change", (mtime, type, wInitial) => {
1907
+ if (wInitial) return;
1908
+ this.setFileTime(itemPath, mtime, false, false, type);
1909
+ });
1910
+ this._symlinkTargetWatchers.set(itemPath, w);
1911
+ }
1912
+ }
1913
+ } else if (
1914
+ stats.isDirectory() &&
1915
+ (!initial || !this.directories.has(itemPath))
1916
+ ) {
1917
+ this.setDirectory(
1918
+ itemPath,
1919
+ +stats.birthtime || 1,
1920
+ initial,
1921
+ "scan (dir)",
1922
+ );
1942
1923
  }
1943
- this.setFileTime(
1944
- itemPath,
1945
- +stats.mtime || +stats.ctime || 1,
1946
- initial,
1947
- true,
1948
- "scan (file)",
1949
- );
1950
- } else if (
1951
- stats.isDirectory() &&
1952
- (!initial || !this.directories.has(itemPath))
1924
+ itemFinished();
1925
+ };
1926
+ if (
1927
+ this.options.followSymlinks &&
1928
+ this.nestedWatching &&
1929
+ stats.isSymbolicLink()
1953
1930
  ) {
1954
- this.setDirectory(
1955
- itemPath,
1956
- +stats.birthtime || 1,
1957
- initial,
1958
- "scan (dir)",
1959
- );
1931
+ fs.realpath(itemPath, (err3, realPath) => {
1932
+ if (this.closed) return;
1933
+ if (
1934
+ err3 ||
1935
+ !realPath ||
1936
+ withoutCase(path.dirname(realPath)) === withoutCase(this.path)
1937
+ ) {
1938
+ apply(null);
1939
+ return;
1940
+ }
1941
+ // Cycle protection: when the symlink's target is the symlink
1942
+ // path itself or one of its ancestors, descending would
1943
+ // create an unbounded chain of `DirectoryWatcher`s as we walk
1944
+ // back into territory we are already watching. Treat the
1945
+ // symlink as a plain entry instead so the symlink itself is
1946
+ // still tracked but no recursion happens.
1947
+ const rel = path.relative(realPath, itemPath);
1948
+ if (!path.isAbsolute(rel) && !rel.startsWith("..")) {
1949
+ apply(null);
1950
+ return;
1951
+ }
1952
+ fs.stat(realPath, (err4, targetStats) => {
1953
+ if (this.closed) return;
1954
+ if (err4 || !targetStats) {
1955
+ apply(null);
1956
+ } else if (targetStats.isFile()) {
1957
+ apply(realPath);
1958
+ } else if (targetStats.isDirectory()) {
1959
+ // Treat a symlink whose target is a directory as a
1960
+ // nested watched directory so files inside the target
1961
+ // propagate change events to the symlink path.
1962
+ this.setDirectory(
1963
+ itemPath,
1964
+ +targetStats.birthtime || +stats.birthtime || 1,
1965
+ initial,
1966
+ "scan (dir)",
1967
+ );
1968
+ itemFinished();
1969
+ } else {
1970
+ apply(null);
1971
+ }
1972
+ });
1973
+ });
1974
+ return;
1960
1975
  }
1961
- itemFinished();
1976
+ apply(null);
1962
1977
  });
1963
1978
  }
1964
1979
  itemFinished();
@@ -2056,6 +2071,7 @@ class DirectoryWatcher extends EventEmitter {
2056
2071
  }
2057
2072
 
2058
2073
  close() {
2074
+ if (this.closed) return;
2059
2075
  this.closed = true;
2060
2076
  this.initialScan = false;
2061
2077
  if (this.watcher) {
@@ -2069,6 +2085,10 @@ class DirectoryWatcher extends EventEmitter {
2069
2085
  }
2070
2086
  this.directories.clear();
2071
2087
  }
2088
+ for (const w of this._symlinkTargetWatchers.values()) {
2089
+ w.close();
2090
+ }
2091
+ this._symlinkTargetWatchers.clear();
2072
2092
  if (this.parentWatcher) {
2073
2093
  this.parentWatcher.close();
2074
2094
  this.parentWatcher = null;
@@ -2084,7 +2104,7 @@ module.exports.Watcher = Watcher;
2084
2104
 
2085
2105
  /***/ }),
2086
2106
 
2087
- /***/ 703:
2107
+ /***/ 366:
2088
2108
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
2089
2109
 
2090
2110
  "use strict";
@@ -2206,7 +2226,7 @@ module.exports = LinkResolver;
2206
2226
 
2207
2227
  /***/ }),
2208
2228
 
2209
- /***/ 254:
2229
+ /***/ 213:
2210
2230
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
2211
2231
 
2212
2232
  "use strict";
@@ -2217,7 +2237,7 @@ module.exports = LinkResolver;
2217
2237
 
2218
2238
 
2219
2239
  const path = __nccwpck_require__(928);
2220
- const DirectoryWatcher = __nccwpck_require__(600);
2240
+ const DirectoryWatcher = __nccwpck_require__(409);
2221
2241
 
2222
2242
  /** @typedef {import("./index").EventMap} EventMap */
2223
2243
  /** @typedef {import("./DirectoryWatcher").DirectoryWatcherOptions} DirectoryWatcherOptions */
@@ -2294,7 +2314,7 @@ module.exports.WatcherManager = WatcherManager;
2294
2314
 
2295
2315
  /***/ }),
2296
2316
 
2297
- /***/ 17:
2317
+ /***/ 934:
2298
2318
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
2299
2319
 
2300
2320
  "use strict";
@@ -2305,10 +2325,10 @@ module.exports.WatcherManager = WatcherManager;
2305
2325
 
2306
2326
 
2307
2327
  const { EventEmitter } = __nccwpck_require__(434);
2308
- const globToRegExp = __nccwpck_require__(428);
2309
- const LinkResolver = __nccwpck_require__(703);
2310
- const getWatcherManager = __nccwpck_require__(254);
2311
- const watchEventSource = __nccwpck_require__(313);
2328
+ const LinkResolver = __nccwpck_require__(366);
2329
+ const getWatcherManager = __nccwpck_require__(213);
2330
+ const globToRegExp = __nccwpck_require__(737);
2331
+ const watchEventSource = __nccwpck_require__(92);
2312
2332
 
2313
2333
  /** @typedef {import("./getWatcherManager").WatcherManager} WatcherManager */
2314
2334
  /** @typedef {import("./DirectoryWatcher")} DirectoryWatcher */
@@ -2345,8 +2365,9 @@ const watchEventSource = __nccwpck_require__(313);
2345
2365
  /** @typedef {`scan (${string})` | "change" | "rename" | `watch ${string}` | `directory-removed ${string}`} EventType */
2346
2366
  /** @typedef {{ safeTime: number, timestamp: number, accuracy: number }} Entry */
2347
2367
  /** @typedef {{ safeTime: number }} OnlySafeTimeEntry */
2368
+
2348
2369
  // eslint-disable-next-line jsdoc/ts-no-empty-object-type
2349
- /** @typedef {{}} ExistenceOnlyTimeEntry */
2370
+ /** @typedef {{ }} ExistenceOnlyTimeEntry */
2350
2371
  /** @typedef {Map<string, Entry | OnlySafeTimeEntry | ExistenceOnlyTimeEntry | null>} TimeInfoEntries */
2351
2372
  /** @typedef {Set<string>} Changes */
2352
2373
  /** @typedef {Set<string>} Removals */
@@ -2360,10 +2381,8 @@ const watchEventSource = __nccwpck_require__(313);
2360
2381
  */
2361
2382
  function addWatchersToSet(watchers, set) {
2362
2383
  for (const ww of watchers) {
2363
- const w = ww.watcher;
2364
- if (!set.has(w.directoryWatcher)) {
2365
- set.add(w.directoryWatcher);
2366
- }
2384
+ // Set.add is already idempotent, so skip the redundant has() probe.
2385
+ set.add(ww.watcher.directoryWatcher);
2367
2386
  }
2368
2387
  }
2369
2388
 
@@ -2375,31 +2394,47 @@ const stringToRegexp = (ignored) => {
2375
2394
  if (ignored.length === 0) {
2376
2395
  return;
2377
2396
  }
2378
- const { source } = globToRegExp(ignored, { globstar: true, extended: true });
2379
- return `${source.slice(0, -1)}(?:$|\\/)`;
2397
+ return `^${globToRegExp(ignored)}(?:$|\\/)`;
2380
2398
  };
2381
2399
 
2400
+ /**
2401
+ * Normalizes path separators for regex testing. `String.prototype.replace`
2402
+ * always allocates a new string, even when the pattern finds nothing; for
2403
+ * POSIX paths (the common case) that allocation is pure overhead. Check for
2404
+ * a backslash with `indexOf` first so we skip the copy on paths that are
2405
+ * already normalized.
2406
+ * @param {string} item item
2407
+ * @returns {string} item with backslashes normalized to forward slashes
2408
+ */
2409
+ const normalizeSeparators = (item) =>
2410
+ item.includes("\\") ? item.replace(/\\/g, "/") : item;
2411
+
2382
2412
  /**
2383
2413
  * @param {Ignored=} ignored ignored
2384
2414
  * @returns {(item: string) => boolean} ignored to function
2385
2415
  */
2386
2416
  const ignoredToFunction = (ignored) => {
2387
2417
  if (Array.isArray(ignored)) {
2388
- const stringRegexps = ignored.map((i) => stringToRegexp(i)).filter(Boolean);
2418
+ const stringRegexps =
2419
+ /** @type {string[]} */
2420
+ (ignored.map((i) => stringToRegexp(i)).filter(Boolean));
2389
2421
  if (stringRegexps.length === 0) {
2390
2422
  return () => false;
2391
2423
  }
2392
- const regexp = new RegExp(stringRegexps.join("|"));
2393
- return (item) => regexp.test(item.replace(/\\/g, "/"));
2424
+ const regexp =
2425
+ stringRegexps.length === 1
2426
+ ? new RegExp(stringRegexps[0])
2427
+ : new RegExp(stringRegexps.join("|"));
2428
+ return (item) => regexp.test(normalizeSeparators(item));
2394
2429
  } else if (typeof ignored === "string") {
2395
2430
  const stringRegexp = stringToRegexp(ignored);
2396
2431
  if (!stringRegexp) {
2397
2432
  return () => false;
2398
2433
  }
2399
2434
  const regexp = new RegExp(stringRegexp);
2400
- return (item) => regexp.test(item.replace(/\\/g, "/"));
2435
+ return (item) => regexp.test(normalizeSeparators(item));
2401
2436
  } else if (ignored instanceof RegExp) {
2402
- return (item) => ignored.test(item.replace(/\\/g, "/"));
2437
+ return (item) => ignored.test(normalizeSeparators(item));
2403
2438
  } else if (typeof ignored === "function") {
2404
2439
  return ignored;
2405
2440
  } else if (ignored) {
@@ -2763,8 +2798,10 @@ class Watchpack extends EventEmitter {
2763
2798
  /** @type {Record<string, number>} */
2764
2799
  const obj = Object.create(null);
2765
2800
  for (const w of directoryWatchers) {
2801
+ // getTimes() returns a prototype-less object, so for...in is safe
2802
+ // and avoids the throwaway array that Object.keys would allocate.
2766
2803
  const times = w.getTimes();
2767
- for (const file of Object.keys(times)) obj[file] = times[file];
2804
+ for (const file in times) obj[file] = times[file];
2768
2805
  }
2769
2806
  return obj;
2770
2807
  }
@@ -2851,12 +2888,35 @@ class Watchpack extends EventEmitter {
2851
2888
  }
2852
2889
  }
2853
2890
 
2854
- module.exports = Watchpack;
2891
+ /**
2892
+ * @template A
2893
+ * @template B
2894
+ * @param {A} obj input a
2895
+ * @param {B} exports input b
2896
+ * @returns {A & B} merged
2897
+ */
2898
+ const mergeExports = (obj, exports) => {
2899
+ const descriptors = Object.getOwnPropertyDescriptors(exports);
2900
+ Object.defineProperties(obj, descriptors);
2901
+ return /** @type {A & B} */ (Object.freeze(obj));
2902
+ };
2903
+
2904
+ /** @typedef {typeof Watchpack & { util: { readonly globToRegExp: typeof globToRegExp } }} WatchpackExports */
2905
+
2906
+ module.exports = /** @type {WatchpackExports} */ (
2907
+ mergeExports(Watchpack, {
2908
+ util: {
2909
+ get globToRegExp() {
2910
+ return globToRegExp;
2911
+ },
2912
+ },
2913
+ })
2914
+ );
2855
2915
 
2856
2916
 
2857
2917
  /***/ }),
2858
2918
 
2859
- /***/ 240:
2919
+ /***/ 817:
2860
2920
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
2861
2921
 
2862
2922
  "use strict";
@@ -2929,45 +2989,66 @@ module.exports = (plan, limit) => {
2929
2989
  }
2930
2990
  }
2931
2991
  }
2932
- // Reduce until limit reached
2933
- while (currentCount > limit) {
2934
- // Select node that helps reaching the limit most effectively without overmerging
2935
- const overLimit = currentCount - limit;
2936
- let bestNode;
2937
- let bestCost = Infinity;
2992
+ // Reduce until limit reached. When no reduction is needed at all, skip
2993
+ // building the candidate set entirely to avoid paying for the setup on the
2994
+ // common fast path.
2995
+ if (currentCount > limit) {
2996
+ // Pre-filter candidate nodes so the inner selection loop skips structural
2997
+ // non-candidates entirely. `children` length and parent presence are
2998
+ // fixed after tree construction; only `entries` can change (it can only
2999
+ // decrease), so a node that fails the `entries` check in a later round
3000
+ // is simply skipped via `continue`. When we merge a subtree we drop the
3001
+ // descendants from the candidate set to keep it shrinking over
3002
+ // iterations.
3003
+ /** @type {Set<TreeNode<T>>} */
3004
+ const candidates = new Set();
2938
3005
  for (const node of treeMap.values()) {
2939
- if (node.entries <= 1 || !node.children || !node.parent) continue;
3006
+ if (!node.parent || !node.children) continue;
2940
3007
  if (node.children.length === 0) continue;
2941
3008
  if (node.children.length === 1 && !node.value) continue;
2942
- // Try to select the node with has just a bit more entries than we need to reduce
2943
- // When just a bit more is over 30% over the limit,
2944
- // also consider just a bit less entries then we need to reduce
2945
- const cost =
2946
- node.entries - 1 >= overLimit
2947
- ? node.entries - 1 - overLimit
2948
- : overLimit - node.entries + 1 + limit * 0.3;
2949
- if (cost < bestCost) {
2950
- bestNode = node;
2951
- bestCost = cost;
3009
+ candidates.add(node);
3010
+ }
3011
+ const costBias = limit * 0.3;
3012
+ while (currentCount > limit) {
3013
+ // Select node that helps reaching the limit most effectively without overmerging
3014
+ const overLimit = currentCount - limit;
3015
+ let bestNode;
3016
+ let bestCost = Infinity;
3017
+ for (const node of candidates) {
3018
+ if (node.entries <= 1) continue;
3019
+ // Try to select the node with has just a bit more entries than we need to reduce
3020
+ // When just a bit more is over 30% over the limit,
3021
+ // also consider just a bit less entries then we need to reduce
3022
+ const diff = node.entries - 1 - overLimit;
3023
+ const cost = diff >= 0 ? diff : -diff + costBias;
3024
+ if (cost < bestCost) {
3025
+ bestNode = node;
3026
+ bestCost = cost;
3027
+ // A cost of 0 means the merge reduces exactly to the limit;
3028
+ // no further candidate can improve on that, so stop scanning.
3029
+ if (cost === 0) break;
3030
+ }
2952
3031
  }
2953
- }
2954
- if (!bestNode) break;
2955
- // Merge all children
2956
- const reduction = bestNode.entries - 1;
2957
- bestNode.active = true;
2958
- bestNode.entries = 1;
2959
- currentCount -= reduction;
2960
- let { parent } = bestNode;
2961
- while (parent) {
2962
- parent.entries -= reduction;
2963
- parent = parent.parent;
2964
- }
2965
- const queue = new Set(bestNode.children);
2966
- for (const node of queue) {
2967
- node.active = false;
2968
- node.entries = 0;
2969
- if (node.children) {
2970
- for (const child of node.children) queue.add(child);
3032
+ if (!bestNode) break;
3033
+ // Merge all children
3034
+ const reduction = bestNode.entries - 1;
3035
+ bestNode.active = true;
3036
+ bestNode.entries = 1;
3037
+ candidates.delete(bestNode);
3038
+ currentCount -= reduction;
3039
+ let { parent } = bestNode;
3040
+ while (parent) {
3041
+ parent.entries -= reduction;
3042
+ parent = parent.parent;
3043
+ }
3044
+ const queue = new Set(bestNode.children);
3045
+ for (const node of queue) {
3046
+ node.active = false;
3047
+ node.entries = 0;
3048
+ candidates.delete(node);
3049
+ if (node.children) {
3050
+ for (const child of node.children) queue.add(child);
3051
+ }
2971
3052
  }
2972
3053
  }
2973
3054
  }
@@ -3002,7 +3083,157 @@ module.exports = (plan, limit) => {
3002
3083
 
3003
3084
  /***/ }),
3004
3085
 
3005
- /***/ 313:
3086
+ /***/ 737:
3087
+ /***/ ((module) => {
3088
+
3089
+ "use strict";
3090
+ /*
3091
+ MIT License http://www.opensource.org/licenses/mit-license.php
3092
+ Author Haijie Xie @hai-x
3093
+ */
3094
+
3095
+
3096
+
3097
+ // Based on https://github.com/fitzgen/glob-to-regexp (MIT)
3098
+ // Specialized for watchpack: `extended` and `globstar` always enabled.
3099
+ // Returns the regexp source without `^`/`$` anchors.
3100
+
3101
+ const CC_EXCLAMATION = 33; // "!"
3102
+ const CC_DOLLAR = 36; // "$"
3103
+ const CC_LEFT_PARENTHESIS = 40; // "("
3104
+ const CC_RIGHT_PARENTHESIS = 41; // ")"
3105
+ const CC_ASTERISK = 42; // "*"
3106
+ const CC_PLUS = 43; // "+"
3107
+ const CC_COMMA = 44; // ","
3108
+ const CC_DOT = 46; // "."
3109
+ const CC_SLASH = 47; // "/"
3110
+ const CC_EQUAL = 61; // "="
3111
+ const CC_QUESTION_MARK = 63; // "?"
3112
+ const CC_LEFT_BRACKET = 91; // "["
3113
+ const CC_RIGHT_BRACKET = 93; // "]"
3114
+ const CC_CARET = 94; // "^"
3115
+ const CC_LEFT_BRACE = 123; // "{"
3116
+ const CC_PIPE = 124; // "|"
3117
+ const CC_RIGHT_BRACE = 125; // "}"
3118
+
3119
+ /**
3120
+ * @param {string} glob glob pattern
3121
+ * @returns {string} regexp source without anchors
3122
+ */
3123
+ module.exports = (glob) => {
3124
+ if (typeof glob !== "string") {
3125
+ throw new TypeError("Expected a string");
3126
+ }
3127
+
3128
+ const len = glob.length;
3129
+ let reStr = "";
3130
+ let inGroup = false;
3131
+ // Start of the current run of literal characters, copied with one slice
3132
+ let literalStart = 0;
3133
+
3134
+ for (let i = 0; i < len; i++) {
3135
+ const cc = glob.charCodeAt(i);
3136
+ const tokenStart = i;
3137
+ let mapped;
3138
+
3139
+ switch (cc) {
3140
+ case CC_SLASH:
3141
+ mapped = "\\/";
3142
+ break;
3143
+ case CC_DOLLAR:
3144
+ mapped = "\\$";
3145
+ break;
3146
+ case CC_CARET:
3147
+ mapped = "\\^";
3148
+ break;
3149
+ case CC_PLUS:
3150
+ mapped = "\\+";
3151
+ break;
3152
+ case CC_DOT:
3153
+ mapped = "\\.";
3154
+ break;
3155
+ case CC_LEFT_PARENTHESIS:
3156
+ mapped = "\\(";
3157
+ break;
3158
+ case CC_RIGHT_PARENTHESIS:
3159
+ mapped = "\\)";
3160
+ break;
3161
+ case CC_EQUAL:
3162
+ mapped = "\\=";
3163
+ break;
3164
+ case CC_EXCLAMATION:
3165
+ mapped = "\\!";
3166
+ break;
3167
+ case CC_PIPE:
3168
+ mapped = "\\|";
3169
+ break;
3170
+ case CC_QUESTION_MARK:
3171
+ mapped = ".";
3172
+ break;
3173
+ case CC_LEFT_BRACKET:
3174
+ mapped = "[";
3175
+ break;
3176
+ case CC_RIGHT_BRACKET:
3177
+ mapped = "]";
3178
+ break;
3179
+ case CC_LEFT_BRACE:
3180
+ inGroup = true;
3181
+ mapped = "(";
3182
+ break;
3183
+ case CC_RIGHT_BRACE:
3184
+ inGroup = false;
3185
+ mapped = ")";
3186
+ break;
3187
+ case CC_COMMA:
3188
+ mapped = inGroup ? "|" : "\\,";
3189
+ break;
3190
+ case CC_ASTERISK: {
3191
+ const atStart = i === 0;
3192
+ const afterSlash = !atStart && glob.charCodeAt(i - 1) === CC_SLASH;
3193
+ let starCount = 1;
3194
+ while (i + 1 < len && glob.charCodeAt(i + 1) === CC_ASTERISK) {
3195
+ starCount++;
3196
+ i++;
3197
+ }
3198
+ const atEnd = i + 1 === len;
3199
+ const beforeSlash = !atEnd && glob.charCodeAt(i + 1) === CC_SLASH;
3200
+ if (
3201
+ starCount > 1 &&
3202
+ (atStart || afterSlash) &&
3203
+ (atEnd || beforeSlash)
3204
+ ) {
3205
+ // Globstar segment, matches zero or more path segments
3206
+ mapped = "((?:[^/]*(?:\\/|$))*)";
3207
+ i++; // Move over the "/"
3208
+ } else {
3209
+ // Not a globstar, matches one path segment
3210
+ mapped = "([^/]*)";
3211
+ }
3212
+ break;
3213
+ }
3214
+ default:
3215
+ // Literal character, extend the current run
3216
+ continue;
3217
+ }
3218
+
3219
+ if (literalStart < tokenStart) {
3220
+ reStr += glob.slice(literalStart, tokenStart);
3221
+ }
3222
+ reStr += mapped;
3223
+ literalStart = i + 1;
3224
+ }
3225
+
3226
+ if (literalStart < len) {
3227
+ reStr += literalStart === 0 ? glob : glob.slice(literalStart);
3228
+ }
3229
+
3230
+ return reStr;
3231
+ };
3232
+
3233
+
3234
+ /***/ }),
3235
+
3236
+ /***/ 92:
3006
3237
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
3007
3238
 
3008
3239
  "use strict";
@@ -3015,7 +3246,7 @@ module.exports = (plan, limit) => {
3015
3246
  const { EventEmitter } = __nccwpck_require__(434);
3016
3247
  const fs = __nccwpck_require__(896);
3017
3248
  const path = __nccwpck_require__(928);
3018
- const reducePlan = __nccwpck_require__(240);
3249
+ const reducePlan = __nccwpck_require__(817);
3019
3250
 
3020
3251
  /** @typedef {import("fs").FSWatcher} FSWatcher */
3021
3252
  /** @typedef {import("./index").EventType} EventType */
@@ -3069,6 +3300,9 @@ function createEPERMError(filePath) {
3069
3300
  * @returns {(type: "rename" | "change", filename: string) => void} handler of change event
3070
3301
  */
3071
3302
  function createHandleChangeEvent(watcher, filePath, handleChangeEvent) {
3303
+ // path.basename(filePath) is invariant for the lifetime of the watcher,
3304
+ // so compute it once rather than on every dispatched event.
3305
+ const ownBasename = path.basename(filePath);
3072
3306
  return (type, filename) => {
3073
3307
  // TODO: After Node.js v22, fs.watch(dir) and deleting a dir will trigger the rename change event.
3074
3308
  // Here we just ignore it and keep the same behavior as before v22
@@ -3076,7 +3310,7 @@ function createHandleChangeEvent(watcher, filePath, handleChangeEvent) {
3076
3310
  if (
3077
3311
  type === "rename" &&
3078
3312
  path.isAbsolute(filename) &&
3079
- path.basename(filename) === path.basename(filePath)
3313
+ path.basename(filename) === ownBasename
3080
3314
  ) {
3081
3315
  if (!IS_OSX) {
3082
3316
  // Before v22, windows will throw EPERM error
@@ -3437,16 +3671,21 @@ module.exports.watch = (filePath) => {
3437
3671
  directWatcher.add(watcher);
3438
3672
  return watcher;
3439
3673
  }
3440
- let current = filePath;
3441
- for (;;) {
3442
- const recursiveWatcher = recursiveWatchers.get(current);
3443
- if (recursiveWatcher !== undefined) {
3444
- recursiveWatcher.add(filePath, watcher);
3445
- return watcher;
3674
+ // Only platforms with recursive fs.watch ever populate recursiveWatchers,
3675
+ // so skip the entire parent walk when the map is empty (always the case
3676
+ // on Linux and the common case before the watcher limit is reached).
3677
+ if (recursiveWatchers.size !== 0) {
3678
+ let current = filePath;
3679
+ for (;;) {
3680
+ const recursiveWatcher = recursiveWatchers.get(current);
3681
+ if (recursiveWatcher !== undefined) {
3682
+ recursiveWatcher.add(filePath, watcher);
3683
+ return watcher;
3684
+ }
3685
+ const parent = path.dirname(current);
3686
+ if (parent === current) break;
3687
+ current = parent;
3446
3688
  }
3447
- const parent = path.dirname(current);
3448
- if (parent === current) break;
3449
- current = parent;
3450
3689
  }
3451
3690
  // Queue up watcher for creation
3452
3691
  pendingWatchers.set(watcher, filePath);
@@ -3565,7 +3804,7 @@ module.exports = require("util");
3565
3804
  /******/ // startup
3566
3805
  /******/ // Load entry module and return exports
3567
3806
  /******/ // This entry module is referenced by other modules so it can't be inlined
3568
- /******/ var __webpack_exports__ = __nccwpck_require__(17);
3807
+ /******/ var __webpack_exports__ = __nccwpck_require__(934);
3569
3808
  /******/ module.exports = __webpack_exports__;
3570
3809
  /******/
3571
3810
  /******/ })()