Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(register): resolve .js to .ts in ts-node/register #1361

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 58 additions & 9 deletions src/cjs-resolve-filename-hook.ts
@@ -1,5 +1,6 @@
import type Module = require('module');
import type { Service } from '.';
import { isRelativeSpecifier } from './util';

/** @internal */
export type ModuleConstructorWithInternals = typeof Module & {
Expand All @@ -19,6 +20,10 @@ interface ModuleResolveFilenameOptions {

/**
* @internal
*
* If any features of this service require patching Module._resolveFilename,
* then install our hook. Logic within the hook conditionally implements
* multiple resolver behaviors.
*/
export function installCommonjsResolveHookIfNecessary(tsNodeService: Service) {
const Module = require('module') as ModuleConstructorWithInternals;
Expand All @@ -35,7 +40,7 @@ export function installCommonjsResolveHookIfNecessary(tsNodeService: Service) {
options?: ModuleResolveFilenameOptions,
...rest: any[]
): string {
if (!tsNodeService.enabled())
function defer(this: any) {
return originalResolveFilename.call(
this,
request,
Expand All @@ -44,16 +49,60 @@ export function installCommonjsResolveHookIfNecessary(tsNodeService: Service) {
options,
...rest
);
}
if (!tsNodeService.enabled()) return defer();

// Map from emit to source extensions
if (
!isMain &&
canReplaceJsWithTsExt(tsNodeService, request, parent?.filename)
) {
try {
return originalResolveFilename.call(
this,
request.slice(0, -3),
parent,
isMain,
options,
...rest
);
} catch (e) {
const mainFile = defer();
if (mainFile.endsWith('.js')) {
//re-resolve with configured extension preference
return originalResolveFilename.call(
this,
mainFile.slice(0, -3),
parent,
isMain,
options,
...rest
);
}
return mainFile;
}
}
// 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
);
return defer();
}
}

function canReplaceJsWithTsExt(
service: Service,
request: string,
parentPath?: string
) {
if (!parentPath || service.ignored(parentPath)) return false;
if (isRelativeSpecifier(request) && request.slice(-3) === '.js') {
if (!parentPath) return true;
const paths = require.main?.paths || [];
// This logic is intending to exclude node_modules
for (let i = 0; i < paths.length; i++) {
if (parentPath.startsWith(paths[i])) {
return false;
}
}
return true;
}
}
7 changes: 7 additions & 0 deletions src/index.ts
Expand Up @@ -383,6 +383,13 @@ export interface CreateOptions {
* @default false
*/
preferTsExts?: boolean;

/**
* Enable experimental resolver features, explained in full here:
* TODO link to an issue? or a docs page?
*
*/
experimentalResolverFeatures?: boolean;
}

export type ModuleTypes = Record<string, 'cjs' | 'esm' | 'package'>;
Expand Down
10 changes: 10 additions & 0 deletions src/util.ts
Expand Up @@ -156,3 +156,13 @@ export function getBasePathForProjectLocalDependencyResolution(
// and we attempt to resolve relative specifiers. By the time we resolve relative specifiers,
// should have configFilePath, so not reach this codepath.
}

export function isRelativeSpecifier(specifier: string) {
if (specifier[0] === '.') {
if (specifier.length === 1 || specifier[1] === '/') return true;
if (specifier[1] === '.') {
if (specifier.length === 2 || specifier[2] === '/') return true;
}
}
return false;
}