-
Add experimental support for Node.js package maps through a new
packageMapoption, which takes the path of the configuration file (or afile:URL) or an already-parsedpackagesobject. When it is set, a bare specifier is resolved through the importing package'sdependenciestable and the target package's location is handed to the regular pipeline, instead of walkingnode_modules; relative and absolute requests andnode:builtins are unaffected. Because several package entries may share oneurl, the package a request resolved into is exposed aspackageIdon the result and can be passed back in ascontext.packageIdto resolve from that package unambiguously. Package maps are stability 1 (experimental) in Node.js, and this option tracks that specification and may change with it. (by @alexander-akait in #667) -
Explain an
exports/importsfield whose conditions wrap subpaths, instead of failing with a message that points at the request. A field shaped like{ "import": { ".": "./esm/index.js", "./*": "./esm/*.js" }, "require": "./build/bundle.js" }is not supported by Node.js: the subpaths inside a condition are read as condition names, so they match nothing, and every request into the package failed as"./foo" is not exported under the conditions [...]— which reads as though the package forgot to export./foo. Such a failure now names the offending keys and shows the arrangement that works, with the subpaths at the top level and the conditions nested inside them. Resolution itself is unchanged: the diagnosis runs only on a request that has already failed, so nothing that resolves today starts failing, and a successful resolve does no extra work. Errors raised while processing either field also name thepackage.jsonthey came from, which previously only appeared in the resolver log. (by @alexander-akait in #676) -
Generate the published type declarations with TypeScript instead of
webpack/tooling, which is no longer a dependency. Every name the package exported before is still exported, andtypes.d.tsis still the entry point, but the declarations themselves now live intypes/and are emitted bytscfrom the JSDoc inlib/. Two shapes follow the sources more closely than the previous generator did: the object form ofPluginno longer declaresthis: Resolveronapply(it is called asplugin.apply(resolver), sothisis the plugin), and the entries ofResolveContext.stackdeclarename: string | undefinedrather than an optionalname. Class fields that the old generator dropped, such as the cache backends onCachedInputFileSystem, are now part of the declarations. (by @alexander-akait in #675)
- Size the ancestor path and segment arrays that
getPathsCachedkeeps to what they actually hold: apush-built store keeps room for 17 entries while a path has a handful, and the cache holds these for the filesystem's lifetime. (by @alexander-akait in #681)
- Read a
file:URL the way Node does when parsing a request. (by @alexander-akait in #670)
- Accept
file:URL strings wherever an option or the context path takes a path. (by @alexander-akait in #668)
- Soft-fail when auto tsconfig discovery loads a broken config, and keep relative
extendspaths relative. (by @xiaoxiaojx in #645)
-
Keep the original request resolvable when
extensionAliaslists its own extension. (by @alexander-akait in #641) -
Fix string
restrictionsboundary checks: a restriction ending with a separator no longer rejects everything inside it, restrictions are normalized before they are compared, and a Windows path now matches the waypath.win32does, treating/and\as interchangeable and comparing case-insensitively, while\stays a filename character in a posix path. The same comparison backstsconfigpath matching. (by @alexander-akait in #643) -
Treat a UNC path (
\\server\share\…) as a Windows path, so it normalizes, joins and walks up withpath.win32semantics instead of being taken for a bare module request. (by @alexander-akait in #644)
- Resolve tsconfig
extendspackage specifiers from ancestor node_modules directories (workspace hoisting). (by @alexander-akait in #631)
- Batch cached filesystem callbacks from one synchronous burst into a single tick. (by @alexander-akait in #623)
- Reduce hot-path allocations in the resolve pipeline. (by @xiaoxiaojx in #615)
- Allow the
contextpath andrequestarguments ofresolve(andresolveSync/resolvePromise) to acceptfile:URLinstances, converting them to filesystem paths. Plain strings stay literal paths, matching Node'sfs. (by @alexander-akait in #607)
-
Make the resolver runtime-agnostic so it works in browsers, Deno and Bun as well as Node. File contents are decoded without assuming a Node
Buffer, and browser shims are provided for thepath,urlandgraceful-fsbuiltins (Node, Deno and Bun keep using the native ones) so the package bundles for the browser — supply your ownfileSystemthere. (by @alexander-akait in #606) -
Rename the positional
resolve/resolveSync/resolvePromiseparameters toparent/specifier(frompath/request) for ESM-aligned naming and document them in the README. Purely cosmetic — arguments are positional, so there is no behavior or API change. (by @alexander-akait in #611)
- Allow the path-like resolve options
roots,modules,alias/fallbacktargets,restrictions, andtsconfig(the config file,configFile,baseUrl, andreferences) to accept fileURLinstances (such asnew URL("./dir/", import.meta.url)), converting them to filesystem paths. Plain strings are still treated as literal paths, matching Node'sfs. (by @alexander-akait in #604)
- Fall back to the next
modulesentry when a packageexportstarget is filtered out byrestrictions, instead of throwing. (by @alexander-akait in #600)
- Fix
restrictionsbypass via an in-root symlink pointing outside the root. (by @alexander-akait in #595)
CachedInputFileSystem#purgeaccepts a second{ exact?: boolean }argument;exact: trueremoves only entries whose key matcheswhatexactly instead of any entry whose key starts withwhat. (by @alexander-akait in #591)
-
Speed up alias resolution on the hot path. (by @alexander-akait in #589)
AliasPlugin/TsconfigPathsPlugin: bucket compiled alias options by the first char code ofname, so resolves skip options whose name can't possibly match the request's first char. Gated to cases with 2+ distinct first chars so degenerate single-bucket lists (e.g. long alias chains) don't pay for theMap.get.TsconfigPathsPlugin: memoize_selectPathsDataForContext(map, requestPath)per map so the per-source-filecontextListscan only runs once per directory. Gated to maps with 2+ contexts so single-context tsconfigs aren't penalized by the cache lookup.
Biggest wins on alias-heavy configs (300+ entries):
huge-alias-miss+151%,huge-alias-list+126%,alias-first-char-miss+120%.
- Don't add configDir to modules when tsconfig has no baseUrl. (by @alexander-akait in
61f36fd)
- When
tsconfig: trueis used, walk up parent directories to findtsconfig.json, matching TypeScript's ownfindConfigFilebehavior. (by @xiaoxiaojx in #585)
- TsconfigPathsPlugin now falls through to normal module resolution when a
pathspattern matches but the mapped path does not exist, matching TypeScript's native resolution behavior. Previously, patterns like"@*"would block scoped npm packages (e.g.@sentry/react) from resolving vianode_modules. (by @xiaoxiaojx in #579)
-
Fix TsconfigPathsPlugin circular project references causing stack overflow, add support for extending from unscoped npm packages, and use
statinstead ofreadFilefor existence checks in extends resolution. (by @xiaoxiaojx in #575) -
perf: dedupe miss paths in
DirectoryExistsPlugin/FileExistsPluginand prune the per-resolveTsconfigPathsPlugincontext scan. (by @alexander-akait in #574) -
perf: drop a dead Map lookup in
findMatchand flattenAliasFieldPlugin's cache check. (by @alexander-akait in #574) -
perf: hot-path tweaks in
ImportsFieldPlugin,AliasUtils, andutil/entrypoints. (by @alexander-akait in #574) -
perf: cut per-resolve allocations in
Resolver.parse,loadDescriptionFile, andTsconfigPathsPlugin._selectPathsDataForContext. (by @alexander-akait in #574)
-
Allocation-free reductions on hot-path code: hoist
/#/g,/\$/gand/\\/gto module-level constants and gate the corresponding.replacecalls behindincludes(…)so paths/queries/requests without the match char skip the regex state machine entirely (the common case); share a singleEMPTY_NO_MATCHtuple instead of allocating[[], null]per "no match" / "no condition matched" return; switchdirectMapping'sfor...ofovermappingTargetand inner results to indexed loops to avoid iterator-object allocation per call; inlineisConditionalMappingat its two hot-path call sites and merge the duplicatedefault/conditionNames.has(condition)branches incomputeConditionalMapping; replaceinvalidSegmentRegEx.exec(…) !== nullwith.test(…)(no match-array allocation); drop the deaddeprecatedInvalidSegmentRegEx.test(…) !== nullclause inImportsFieldPlugin(.testreturns boolean;true !== nullandfalse !== nullare both true, so it was&& true); drop the redundantrelativePath.length === 0guard before!startsWith("./")inExportsFieldPlugin(the empty-string case is already covered). (by @alexander-akait in #558) -
restore plugin compatibility for
[...resolveContext.stack]iteration (by @alexander-akait in #569) -
fix
TsconfigPathsPluginto supportresolveSyncwithuseSyncFileSystemCalls(by @alexander-akait in #572)
-
Added promise API and support to resolve without
contextandresolveContext. (by @alexander-akait in #520) -
Add
extensionAliasForExportsoption. Whentrue,extensionAliasalso applies to paths resolved through thepackage.jsonexportsfield. Off by default to match Node.js; opt in for full TypeScript-resolver parity with packages that ship.tssources alongside the compiled.jsthey declare inexports. (by @alexander-akait in #554)
-
Properly handle DOS device paths (
\\?\…and\\.\…). (by @alexander-akait in #551) -
Prevent fallback to parent node_modules when the
exportsfield target file is not found. (by @xiaoxiaojx in #495) -
Imports field spec deviation: non-relative targets (e.g.
"#a": "#b") no longer re-enter imports resolution, aligning with the Node.js ESM spec wherePACKAGE_IMPORTS_RESOLVEdoes not recursively resolve#specifiers. (by @xiaoxiaojx in #503)Previously
{ "#a": "#b", "#b": "./the.js" }would chain-resolve#ato./the.js; now it correctly fails, matching Node.js behavior. -
Move
cachedJoin/cachedDirname/createCachedBasenamecaches from module-level globals to per-Resolver instances. This prevents unbounded memory growth in long-running processes — when a Resolver is garbage collected, its join/dirname/basename caches are released with it. (by @xiaoxiaojx in #507) -
Fixed when
tsconfig: trueis used (default config file) and notsconfig.jsonexists. (by @xiaoxiaojx in #502) -
Apply the
extensionAliasoption to theimportsfield to be align with typescript resolution. (by @alexander-akait in #549) -
Improved performance of the many plugins. (by @alexander-akait in #529)
-
Replace the
Set<string>-based resolver stack with a singly-linkedStackEntryclass that exposes a Set-compatible API. (by @xiaoxiaojx in #526)Each
doResolvecall now prepends a single linked-list node instead of cloning the entire Set, making stack push O(1) in time and memory. Recursion detection walks the linked list (O(n)), but because the stack is typically shallow this is much cheaper than cloning a Set per call. -
Cache the result of
stripJsonComments+JSON.parseinreadJsonusing aWeakMapkeyed by the raw file buffer. This avoids redundant comment-stripping and JSON parsing on every resolve call that reads tsconfig.json files (viastripComments: true), improving TsconfigPathsPlugin warm performance by ~20-35% depending on the depth of theextendschain. (by @xiaoxiaojx in #524) -
Avoid OOM in CachedInputFileSystem when duration is Infinity. (by @alexander-akait in #527)
-
Optimize
TsconfigPathsPluginand fix extends resolution bugs. (by @alexander-akait in #492) -
Improve resolver cache hit rate. (by @alexander-akait in #492)