diff --git a/src/cjs-resolve-filename-hook.ts b/src/cjs-resolve-filename-hook.ts new file mode 100644 index 000000000..9c6f66b02 --- /dev/null +++ b/src/cjs-resolve-filename-hook.ts @@ -0,0 +1,59 @@ +import type Module = require('module'); +import type { Service } from '.'; + +/** @internal */ +export type ModuleConstructorWithInternals = typeof Module & { + _resolveFilename( + request: string, + parent?: Module, + isMain?: boolean, + options?: ModuleResolveFilenameOptions, + ...rest: any[] + ): string; + _preloadModules(requests?: string[]): void; +}; + +interface ModuleResolveFilenameOptions { + paths?: Array; +} + +/** + * @internal + */ +export function installCommonjsResolveHookIfNecessary(tsNodeService: Service) { + const Module = require('module') as ModuleConstructorWithInternals; + const originalResolveFilename = Module._resolveFilename; + const shouldInstallHook = tsNodeService.options.experimentalResolverFeatures; + if (shouldInstallHook) { + Module._resolveFilename = _resolveFilename; + } + function _resolveFilename( + this: any, + request: string, + parent?: Module, + isMain?: boolean, + options?: ModuleResolveFilenameOptions, + ...rest: any[] + ): string { + if (!tsNodeService.enabled()) + return originalResolveFilename.call( + this, + request, + parent, + isMain, + options, + ...rest + ); + + // This is a stub to support other pull requests that will be merged in the near future + // Right now, it does nothing. + return originalResolveFilename.call( + this, + request, + parent, + isMain, + options, + ...rest + ); + } +} diff --git a/src/configuration.ts b/src/configuration.ts index 4ac594155..b536926dd 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -277,6 +277,7 @@ function filterRecognizedTsConfigTsNodeOptions(jsonObject: any): { moduleTypes, experimentalReplAwait, swc, + experimentalResolverFeatures, ...unrecognized } = jsonObject as TsConfigOptions; const filteredTsConfigOptions = { @@ -300,6 +301,7 @@ function filterRecognizedTsConfigTsNodeOptions(jsonObject: any): { scopeDir, moduleTypes, swc, + experimentalResolverFeatures, }; // Use the typechecker to make sure this implementation has the correct set of properties const catchExtraneousProps: keyof TsConfigOptions = diff --git a/src/index.ts b/src/index.ts index 613775083..9f85e4bfd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,10 @@ import { } from './module-type-classifier'; import { createResolverFunctions } from './resolver-functions'; import type { createEsmHooks as createEsmHooksFn } from './esm'; +import { + installCommonjsResolveHookIfNecessary, + ModuleConstructorWithInternals, +} from './cjs-resolve-filename-hook'; export { TSCommon }; export { @@ -379,6 +383,15 @@ export interface RegisterOptions extends CreateOptions { * @default false */ preferTsExts?: boolean; + + /** + * Enable experimental features that re-map imports and require calls to support: + * `baseUrl`, `paths`, `rootDirs`, `.js` to `.ts` file extension mappings, + * `outDir` to `rootDir` mappings for composite projects and monorepos. + * + * For details, see https://github.com/TypeStrong/ts-node/issues/1514 + */ + experimentalResolverFeatures?: boolean; } /** @@ -546,8 +559,12 @@ export function register( originalJsHandler ); + installCommonjsResolveHookIfNecessary(service); + // Require specified modules before start-up. - (Module as any)._preloadModules(service.options.require); + (Module as ModuleConstructorWithInternals)._preloadModules( + service.options.require + ); return service; } diff --git a/website/docs/options.md b/website/docs/options.md index 38c9247d9..848b0a75f 100644 --- a/website/docs/options.md +++ b/website/docs/options.md @@ -57,6 +57,7 @@ _Environment variables, where available, are in `ALL_CAPS`_ - `moduleType` Override the module type of certain files, ignoring the `package.json` `"type"` field. See [Module type overrides](./module-type-overrides.md) for details.
*Default:* obeys `package.json` `"type"` and `tsconfig.json` `"module"`
*Can only be specified via `tsconfig.json` or API.* - `TS_NODE_HISTORY` Path to history file for REPL
*Default:* `~/.ts_node_repl_history`
- `--noExperimentalReplAwait` Disable top-level await in REPL. Equivalent to node's [`--no-experimental-repl-await`](https://nodejs.org/api/cli.html#cli_no_experimental_repl_await)
*Default:* Enabled if TypeScript version is 3.8 or higher and target is ES2018 or higher.
*Environment:* `TS_NODE_EXPERIMENTAL_REPL_AWAIT` set `false` to disable +- `experimentalResolverFeatures` Enable experimental features that re-map imports and require calls to support: `baseUrl`, `paths`, `rootDirs`, `.js` to `.ts` file extension mappings, `outDir` to `rootDir` mappings for composite projects and monorepos. For details, see [#1514](https://github.com/TypeStrong/ts-node/issues/1514)
*Default:* `false`
*Can only be specified via `tsconfig.json` or API.* ## API