fix: make TsconfigPathsPlugin work with sync file systems (#571)#572
Conversation
The plugin used async/await and Promise-wrapped file system calls, so its tapAsync handlers always deferred callback invocation by a Promise tick. That caused resolveSync to throw "fileSystem is not sync" whenever tsconfig was enabled, even with useSyncFileSystemCalls: true. Convert the plugin and util/fs#readJson to callback-based code so a synchronous file system stays synchronous all the way through, and resolveSync works as expected.
- Dedupe the two `apply()` tapAsync handlers into a small `tap` helper parameterised by hook name, target hook, field picker, and resolve handler. - Drop the dead `done` guard in `_loadTsconfigReferences` — `pending` cannot legitimately reach 0 twice. - Collapse the array-vs-scalar `extends` branches in `_loadTsconfig` into a single trampoline by normalising scalar to a 1-element list. - Drop the unused `cb` alias and the cast it required by marking `_loadTsconfig`'s callback non-optional. - Trim issue-referencing test names and a duplicate comment block.
🦋 Changeset detectedLatest commit: d106a4e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Merging this PR will degrade performance by 24.26%
Performance Changes
Comparing |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #572 +/- ##
==========================================
+ Coverage 96.51% 96.54% +0.03%
==========================================
Files 50 50
Lines 2895 2927 +32
Branches 911 922 +11
==========================================
+ Hits 2794 2826 +32
Misses 85 85
Partials 16 16
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
The previous simplification factored apply() through a `tap` closure that allocated a `pickField` arrow per hook and called it on every resolve, and extracted `_addFileDependencies` as a method. Both add small per-resolve overhead that shows up in CodSpeed instruction counts. Inline the two tapAsync handlers and the file-dependency loop back into the hot path. Also shorten the changeset to a one-liner.
Three small allocations were happening on every cold-path tsconfig load
that had no equivalent in main:
- the {stripComments: true} options literal passed to readJson — hoist
to a module-level READ_JSON_OPTIONS constant.
- the [extendedConfig] wrapper array on the scalar `extends` branch —
restore a separate scalar code path so the string is passed through
directly.
- the `finish` closure that built the TsconfigPathsMap — extract to a
module-level `buildTsconfigPathsMap` helper called from both the
references and no-references branches.
tsconfig-extends and tsconfig-paths benches each pick up roughly +30%
ops/s locally.
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## enhanced-resolve@5.21.1 ### Patch Changes - Allocation-free reductions on hot-path code: hoist `/#/g`, `/\$/g` and `/\\/g` to module-level constants and gate the corresponding `.replace` calls behind `includes(…)` so paths/queries/requests without the match char skip the regex state machine entirely (the common case); share a single `EMPTY_NO_MATCH` tuple instead of allocating `[[], null]` per "no match" / "no condition matched" return; switch `directMapping`'s `for...of` over `mappingTarget` and inner results to indexed loops to avoid iterator-object allocation per call; inline `isConditionalMapping` at its two hot-path call sites and merge the duplicate `default` / `conditionNames.has(condition)` branches in `computeConditionalMapping`; replace `invalidSegmentRegEx.exec(…) !== null` with `.test(…)` (no match-array allocation); drop the dead `deprecatedInvalidSegmentRegEx.test(…) !== null` clause in `ImportsFieldPlugin` (`.test` returns boolean; `true !== null` and `false !== null` are both true, so it was `&& true`); drop the redundant `relativePath.length === 0` guard before `!startsWith("./")` in `ExportsFieldPlugin` (the empty-string case is already covered). (by [@alexander-akait](https://github.com/alexander-akait) in [#558](#558)) - restore plugin compatibility for `[...resolveContext.stack]` iteration (by [@alexander-akait](https://github.com/alexander-akait) in [#569](#569)) - fix `TsconfigPathsPlugin` to support `resolveSync` with `useSyncFileSystemCalls` (by [@alexander-akait](https://github.com/alexander-akait) in [#572](#572)) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
The plugin used async/await and Promise-wrapped file system calls, so its
tapAsync handlers always deferred callback invocation by a Promise tick.
That caused resolveSync to throw "fileSystem is not sync" whenever
tsconfig was enabled, even with useSyncFileSystemCalls: true. Convert the
plugin and util/fs#readJson to callback-based code so a synchronous file
system stays synchronous all the way through, and resolveSync works as
expected.