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(paths): Allow usage of TSConfig Paths #76

Merged
merged 8 commits into from
May 26, 2020
6 changes: 5 additions & 1 deletion .devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
"SHELL": "/bin/sh"
},

"extensions": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"],
"extensions": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"eamodio.gitlens"
],

"remoteUser": "node",

Expand Down
4 changes: 4 additions & 0 deletions Testing/Tests/Paths/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions Testing/Tests/Paths/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "paths",
"type": "module",
"main": "./src/index.ts",
"NODE_OPTIONS": "--harmony-top-level-await",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node --loader ../../../out/dist/index.js --experimental-specifier-resolution=node --experimental-import-meta-resolve --harmony-optional-chaining --harmony-top-level-await src/index.ts"
},
"author": "",
"license": "ISC"
}
6 changes: 6 additions & 0 deletions Testing/Tests/Paths/src/Module/HelloWorld/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Testing/Tests/src/Module/HelloWorld/index.ts
import { add } from '@paths/Utils/Math';

export async function testingSubPath(): Promise<number> {
return add(1, 5);
}
13 changes: 13 additions & 0 deletions Testing/Tests/Paths/src/Utils/Math.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Testing/Tests/Lab/src/Utils/Math.ts

export function add(x: number, y: number): number {
console.debug(`Adding ${x} + ${y}`);

return x + y;
}

export function divide(x: number, y: number): number {
console.debug(`Dividing ${x} / ${y}`);

return x / y;
}
22 changes: 22 additions & 0 deletions Testing/Tests/Paths/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Testing/Tests/Lab/src/index.ts
import { add, divide } from '@paths/Utils/Math';
import { strictEqual } from 'assert';

export async function startApp(): Promise<void> {
console.debug('Starting Application');

const sum = add(1, 1);
strictEqual(sum, 2);

const divideResult = divide(2, 2);
strictEqual(divideResult, 1);

const { testingSubPath } = await import('@paths/Module/HelloWorld');

const addSub = await testingSubPath();
strictEqual(addSub, 6);

console.debug('Done');
}

startApp();
13 changes: 13 additions & 0 deletions Testing/Tests/Paths/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true,
"baseUrl": "./src",

"paths": {
"@paths/*": ["./*"]
}
}
}
1 change: 0 additions & 1 deletion src/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { isAbsolute as isAbsolutePath, dirname as pathDirname } from 'path';
let tsConfigCache: CompilerOptions;

export function getTSConfig(modulePath: string): CompilerOptions {
if (tsConfigCache) return tsConfigCache;
const tsConfigPath = ts.findConfigFile(modulePath, ts.sys.fileExists);

if (!tsConfigPath || !isAbsolutePath(tsConfigPath)) {
Expand Down
37 changes: 32 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// src/index.ts
import { createRequire } from 'module';
import { basename, dirname } from 'path';
import ts from 'typescript';
import { basename, dirname, resolve as resolvePath, relative } from 'path';
import ts, { CompilerOptions } from 'typescript';
import { fileURLToPath, pathToFileURL } from 'url';
import { findFiles } from './findFiles';
import {
Expand All @@ -16,7 +16,7 @@ import {
} from './types';
import { getTSConfig } from './Utils';

const rootModulePath = `${process.cwd()}/`;
const rootModulePath = process.cwd();
const baseURL = pathToFileURL(rootModulePath).href;

const relativePathRegex = /^\.{1,2}[/]?/;
Expand All @@ -26,13 +26,36 @@ const hasExtensionRegex = /\.\w+$/;
const extensions = ['.ts', '.tsx'];
const extensionsRegex = new RegExp(`\\${extensions.join('|\\')}`);

let TSConfig: CompilerOptions;

// Custom resolver to allow `.ts` and `.tsx` extensions, along with finding files if no extension is provided.
export async function resolve(
specifier: string,
context: ResolveContext,
defaultResolve: Function,
): Promise<ResolveResponse> {
const { parentURL = baseURL } = context;
let { parentURL = baseURL } = context;
EntraptaJ marked this conversation as resolved.
Show resolved Hide resolved

let forceRelative = false;
if (TSConfig?.paths) {
for (const tsPath of Object.keys(TSConfig.paths)) {
const tsPathKey = tsPath.replace('/*', '');
if (specifier.startsWith(tsPathKey)) {
const pathSpecifier = TSConfig.paths[tsPath][0].replace(
'/*',
specifier.split(tsPathKey)[1],
);

forceRelative = true;

parentURL = `${
pathToFileURL(resolvePath(baseURL, TSConfig.baseUrl!)).href
}/`;

specifier = pathSpecifier;
}
}
}

const resolvedUrl = new URL(specifier, parentURL);
const fileName = basename(resolvedUrl.pathname);
Expand All @@ -49,7 +72,10 @@ export async function resolve(
/**
* If no extension is passed and is a relative import then let's try to find a `.ts` or `.tsx` file at the path
*/
if (relativePathRegex.test(specifier) && !hasExtensionRegex.test(fileName)) {
if (
(relativePathRegex.test(specifier) || forceRelative) &&
!hasExtensionRegex.test(fileName)
) {
const filePath = fileURLToPath(resolvedUrl);

const file = await findFiles(dirname(filePath), {
Expand Down Expand Up @@ -161,6 +187,7 @@ export async function transformSource(

// Load the closest `tsconfig.json` to the source file
const tsConfig = getTSConfig(dirname(sourceFilePath));
TSConfig = tsConfig;

// Transpile the source code that Node passed to us.
const transpiledModule = ts.transpileModule(source.toString(), {
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"compilerOptions": {
"target": "ESNext",
"target": "ES2019",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So as it turns out Node.JS 13.9 only has nullish coalescing and optional chaining working with an optional harmony V8 flags.

"module": "ESNext",
"moduleResolution": "Node",

"allowSyntheticDefaultImports": true,
"declaration": true,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will make it easier for people to extend TS-ESNode in their own scripts. Recently realized that most of my modules don't have declaration enabled, oops.


"strict": true,
"outDir": "out/dist",
Expand Down