diff --git a/.devcontainer.json b/.devcontainer.json index 5d09890..cfe1139 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -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", diff --git a/Testing/Tests/Paths/package-lock.json b/Testing/Tests/Paths/package-lock.json new file mode 100644 index 0000000..9dbec84 --- /dev/null +++ b/Testing/Tests/Paths/package-lock.json @@ -0,0 +1,4 @@ +{ + "name": "paths", + "lockfileVersion": 1 +} diff --git a/Testing/Tests/Paths/package.json b/Testing/Tests/Paths/package.json new file mode 100644 index 0000000..0d6029c --- /dev/null +++ b/Testing/Tests/Paths/package.json @@ -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" +} diff --git a/Testing/Tests/Paths/src/Module/HelloWorld/index.ts b/Testing/Tests/Paths/src/Module/HelloWorld/index.ts new file mode 100644 index 0000000..2a28799 --- /dev/null +++ b/Testing/Tests/Paths/src/Module/HelloWorld/index.ts @@ -0,0 +1,6 @@ +// Testing/Tests/src/Module/HelloWorld/index.ts +import { add } from '@paths/Utils/Math'; + +export async function testingSubPath(): Promise { + return add(1, 5); +} diff --git a/Testing/Tests/Paths/src/Utils/Math.ts b/Testing/Tests/Paths/src/Utils/Math.ts new file mode 100644 index 0000000..0c7eaff --- /dev/null +++ b/Testing/Tests/Paths/src/Utils/Math.ts @@ -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; +} diff --git a/Testing/Tests/Paths/src/index.ts b/Testing/Tests/Paths/src/index.ts new file mode 100644 index 0000000..0e18bb1 --- /dev/null +++ b/Testing/Tests/Paths/src/index.ts @@ -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 { + 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(); diff --git a/Testing/Tests/Paths/tsconfig.json b/Testing/Tests/Paths/tsconfig.json new file mode 100644 index 0000000..4690453 --- /dev/null +++ b/Testing/Tests/Paths/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true, + "baseUrl": "./src", + + "paths": { + "@paths/*": ["./*"] + } + } +} diff --git a/src/Utils.ts b/src/Utils.ts index 43bec8c..d1247d0 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -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)) { diff --git a/src/index.ts b/src/index.ts index 42ba2f3..370f574 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,22 +1,22 @@ // src/index.ts import { createRequire } from 'module'; -import { basename, dirname } from 'path'; -import ts from 'typescript'; +import { basename, dirname, resolve as resolvePath } from 'path'; +import ts, { CompilerOptions } from 'typescript'; import { fileURLToPath, pathToFileURL } from 'url'; import { findFiles } from './findFiles'; import { + DynamicInstantiateResponse, + GetFormatResponse, ModuleFormat, ResolveContext, ResolveResponse, Source, TransformContext, TransformResponse, - DynamicInstantiateResponse, - GetFormatResponse, } from './types'; import { getTSConfig } from './Utils'; -const rootModulePath = `${process.cwd()}/`; +const rootModulePath = process.cwd(); const baseURL = pathToFileURL(rootModulePath).href; const relativePathRegex = /^\.{1,2}[/]?/; @@ -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 { - const { parentURL = baseURL } = context; + let { parentURL = baseURL } = context; + + 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); @@ -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), { @@ -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(), { diff --git a/tsconfig.json b/tsconfig.json index 9c11547..bfa5f19 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,11 @@ { "compilerOptions": { - "target": "ESNext", + "target": "ES2019", "module": "ESNext", "moduleResolution": "Node", "allowSyntheticDefaultImports": true, + "declaration": true, "strict": true, "outDir": "out/dist",