diff --git a/src/cjs/api/module-extensions.ts b/src/cjs/api/module-extensions.ts index fdfff137..ab72b59a 100644 --- a/src/cjs/api/module-extensions.ts +++ b/src/cjs/api/module-extensions.ts @@ -87,42 +87,38 @@ const transformer = ( module._compile(code, cleanFilePath); }; -[ - /** - * Handles .cjs, .cts, .mts & any explicitly specified extension that doesn't match any loaders - * - * Any file requested with an explicit extension will be loaded using the .js loader: - * https://github.com/nodejs/node/blob/e339e9c5d71b72fd09e6abd38b10678e0c592ae7/lib/internal/modules/cjs/loader.js#L430 - */ - '.js', +/** + * Handles .cjs, .cts, .mts & any explicitly specified extension that doesn't match any loaders + * + * Any file requested with an explicit extension will be loaded using the .js loader: + * https://github.com/nodejs/node/blob/e339e9c5d71b72fd09e6abd38b10678e0c592ae7/lib/internal/modules/cjs/loader.js#L430 + */ +extensions['.js'] = transformer; - /** - * Loaders for implicitly resolvable extensions - * https://github.com/nodejs/node/blob/v12.16.0/lib/internal/modules/cjs/loader.js#L1166 - */ +[ '.ts', '.tsx', '.jsx', -].forEach((extension) => { - extensions[extension] = transformer; -}); - -/** - * Loaders for explicitly resolvable extensions - * (basically just .mjs because CJS loader has a special handler for it) - * - * Loaders for extensions .cjs, .cts, & .mts don't need to be - * registered because they're explicitly specified and unknown - * extensions (incl .cjs) fallsback to using the '.js' loader: - * https://github.com/nodejs/node/blob/v18.4.0/lib/internal/modules/cjs/loader.js#L430 - * - * That said, it's actually ".js" and ".mjs" that get special treatment - * rather than ".cjs" (it might as well be ".random-ext") - */ -Object.defineProperty(extensions, '.mjs', { - value: transformer, - // Prevent Object.keys from detecting these extensions - // when CJS loader iterates over the possible extensions - enumerable: false, + /** + * Loaders for extensions .cjs, .cts, & .mts don't need to be + * registered because they're explicitly specified. And unknown + * extensions (incl .cjs) fallsback to using the '.js' loader: + * https://github.com/nodejs/node/blob/v18.4.0/lib/internal/modules/cjs/loader.js#L430 + * + * That said, it's actually ".js" and ".mjs" that get special treatment + * rather than ".cjs" (it might as well be ".random-ext") + */ + '.mjs', +].forEach((extension) => { + Object.defineProperty(extensions, extension, { + value: transformer, + + /** + * Prevent Object.keys from detecting these extensions + * when CJS loader iterates over the possible extensions + * https://github.com/nodejs/node/blob/v22.2.0/lib/internal/modules/cjs/loader.js#L609 + */ + enumerable: false, + }); }); diff --git a/src/cjs/api/module-resolve-filename.ts b/src/cjs/api/module-resolve-filename.ts index cbc06d43..4fa3938c 100644 --- a/src/cjs/api/module-resolve-filename.ts +++ b/src/cjs/api/module-resolve-filename.ts @@ -8,6 +8,8 @@ import { tsconfigPathsMatcher, allowJs } from '../../utils/tsconfig.js'; type ResolveFilename = typeof Module._resolveFilename; +type SimpleResolve = (request: string) => string; + const nodeModulesPath = `${path.sep}node_modules${path.sep}`; export const interopCjsExports = ( @@ -39,11 +41,9 @@ export const interopCjsExports = ( * Typescript gives .ts, .cts, or .mts priority over actual .js, .cjs, or .mjs extensions */ const resolveTsFilename = ( - nextResolve: ResolveFilename, + resolve: SimpleResolve, request: string, parent: Module.Parent, - isMain: boolean, - options?: Record, ) => { if ( !(parent?.filename && tsExtensionsPattern.test(parent.filename)) @@ -59,12 +59,7 @@ const resolveTsFilename = ( for (const tryTsPath of tsPath) { try { - return nextResolve( - tryTsPath, - parent, - isMain, - options, - ); + return resolve(tryTsPath); } catch (error) { const { code } = error as NodeError; if ( @@ -77,6 +72,19 @@ const resolveTsFilename = ( } }; +const extensions = ['.ts', '.tsx', '.jsx'] as const; + +const tryExtensions = ( + resolve: SimpleResolve, + request: string, +) => { + for (const extension of extensions) { + try { + return resolve(request + extension); + } catch {} + } +}; + export const createResolveFilename = ( nextResolve: ResolveFilename, ): ResolveFilename => ( @@ -99,39 +107,66 @@ export const createResolveFilename = ( request = fileURLToPath(request); } + const resolve: SimpleResolve = request_ => nextResolve( + request_, + parent, + isMain, + options, + ); + // Resolve TS path alias if ( tsconfigPathsMatcher - // bare specifier - && !isRelativePath(request) + // bare specifier + && !isRelativePath(request) - // Dependency paths should not be resolved using tsconfig.json - && !parent?.filename?.includes(nodeModulesPath) + // Dependency paths should not be resolved using tsconfig.json + && !parent?.filename?.includes(nodeModulesPath) ) { const possiblePaths = tsconfigPathsMatcher(request); for (const possiblePath of possiblePaths) { - const tsFilename = resolveTsFilename(nextResolve, possiblePath, parent, isMain, options); + const tsFilename = resolveTsFilename(resolve, possiblePath, parent); if (tsFilename) { return tsFilename + query; } try { - return nextResolve( - possiblePath, - parent, - isMain, - options, - ) + query; - } catch {} + return resolve(possiblePath) + query; + } catch { + /** + * Try order: + * https://github.com/nodejs/node/blob/v22.2.0/lib/internal/modules/cjs/loader.js#L410-L413 + */ + const resolved = ( + tryExtensions(resolve, possiblePath) + || tryExtensions(resolve, path.resolve(possiblePath, 'index')) + ); + if (resolved) { + return resolved + query; + } + } } } - const tsFilename = resolveTsFilename(nextResolve, request, parent, isMain, options); + // If extension exists + const tsFilename = resolveTsFilename(resolve, request, parent); if (tsFilename) { return tsFilename + query; } - return nextResolve(request, parent, isMain, options) + query; + try { + return resolve(request) + query; + } catch (error) { + const resolved = ( + tryExtensions(resolve, request) + || tryExtensions(resolve, path.resolve(request, 'index')) + ); + if (resolved) { + return resolved + query; + } + + throw error; + } };