$ show jonaskello/tsconfig-paths
This is a mature Node.js library that makes TypeScript's compile-time path aliases (tsconfig/jsconfig 'paths') work at runtime in plain Node. The code confirms the core claim: src/register.ts patches Module._resolveFilename to intercept module requests, resolves them through tsconfig path mappings, and falls back to Node's original resolution when no mapping matches. The architecture is a clean pipeline: config discovery and loading (tsconfig-loader.ts walks directories, parses JSON5 with comments/trailing commas, resolves 'extends' chains including array form and node_modules packages), mapping construction (mapping-entry.ts sorts patterns by longest prefix and resolves paths to absolute form once), candidate generation (try-path.ts builds file/extension/package.json-main/index probe lists), and matching (match-path-sync.ts and match-path-async.ts) over an injectable filesystem abstraction (filesystem.ts). The public API surface includes register(), loadConfig(), createMatchPath/createMatchPathAsync, and matchFromAbsolutePaths/matchFromAbsolutePathsAsync, plus the 'tsconfig-paths/register' require-hook for CLI usage (node -r tsconfig-paths/register). Data flows from tsconfig.json on disk -> parsed config -> absolute mapping entries -> probed physical paths on each module load. All filesystem operations (fileExists, readJson) are injectable, which makes the test suite pure and deterministic. Runtime dependencies are minimal (json5, minimist, strip-bom), with no network calls, daemons, or persistent state. Craftsmanship is high and the modules are small, single-purpose, and individually extractable. Non-obvious details include: pre-sorting mappings once to avoid per-request sorting; matchStar enforcing a one-character minimum star match; careful async recursion that avoids double-calling callbacks (a real bug that surfaced in tsconfig-paths-webpack-plugin); a suffix-regex that converts .js/.mjs/.cjs requests to .ts/.tsx probes; and a table-driven fixture file (src/__tests__/data/match-path-data.ts) whose 20+ vectors drive both the sync and async test suites, forcing behavioral parity. Code is conservative by design (ES5 target, CommonJS) to support old Node, and strict ESLint rules enforce explicit typing and style. The project is actively maintained (recent commits, an [Unreleased] changelog section, CI matrix on Node 14/16/18). However, the dev toolchain shows measurable drift: TypeScript 4.5, Jest 27, @types/node 6, GitHub Actions versions from 2019-2021, and 'engines' still claiming node >=6 (long EOL). The runtime logic itself remains current and sound; risk is low, but modernization of tooling is overdue. With 1906 stars and 114 open issues, this is the de facto standard for tsconfig paths resolution, relied on by ts-node, mocha setups, and the tsconfig-paths-webpack-plugin.
The LLM identified 5 files worth highlighting for reuse:
Builds an ordered probe list of physical filesystem paths for a requested module, mirroring Node's resolution order: exact file, extension probing, package.json main field, and index files. The suffix-regex logic correctly converts .js/.mjs/.cjs module requests into .ts/.tsx probes, a non-obvious need unique to TypeScript runtime interop, and matchStar enforces a one-character minimum star match. It imports only Node's 'path', the MappingEntry type, and one filesystem helper, making it directly extractable into any module-resolution or loader tool.
Precomputes absolute path mappings from tsconfig's baseUrl + paths, sorted by longest pattern prefix exactly once up front rather than on every lookup - a deliberate performance optimization for hot require paths. It also adds a TypeScript-compatible match-all rule when baseUrl exists and no '*' mapping is defined. A pure module depending only on Node's 'path', it serves as a clean reference implementation for any alias-resolution, routing, or pattern-matching system.
Loads tsconfig.json/jsconfig.json with JSON5 (tolerating comments and trailing commas), strips BOM via strip-bom, and resolves 'extends' chains including the newer array-of-strings form and package-style extends from node_modules. It correctly rebases baseUrl from extended configs to remain relative to the original config's directory - subtle config semantics that are easy to get wrong. Pure functions with injectable existsSync/readFileSync make this reusable for any tool needing TypeScript config resolution.
A table of 20+ module-resolution test vectors covering longest-prefix priority, package.json main/browser field fallback with priority lists, nested main fields (esnext.main), index file resolution, .js-request-to-.ts-file aliasing, baseUrl match-all toggle behavior, and negative cases. The same data drives both match-path-sync.test.ts and match-path-async.test.ts, forcing the two APIs to stay behaviorally identical. This is a reusable pattern for any library maintaining parallel sync/async implementations.
Patches Node's Module._resolveFilename to route requests through tsconfig path mappings while exempting core modules (using Module.builtinModules with a hardcoded historical fallback for older Node). It returns a cleanup function that restores the original resolver, demonstrating a hygienic interception pattern that does not leak global state. Useful as a template for any tool that must hook Node's module resolution - test runners, loaders, or bundlers - and its coupling is limited to two stable internal modules.