Skip to content
This repository was archived by the owner on Oct 18, 2023. It is now read-only.

Commit cc78627

Browse files
feat(typescript): support tsconfig paths (#22)
1 parent a35b8f9 commit cc78627

15 files changed

+96
-15
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Node.js `import` hook to instantaneously transform TypeScript to ESM on demand u
88
- Cached for performance boost
99
- Supports Node.js v12.20.0+
1010
- Handles `node:` import prefixes
11+
- Resolves `tsconfig.json` [`paths`](https://www.typescriptlang.org/tsconfig#paths)
1112

1213
> **Tip:**
1314
>

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
},
3333
"dependencies": {
3434
"@esbuild-kit/core-utils": "^1.3.1",
35-
"get-tsconfig": "^3.0.1"
35+
"get-tsconfig": "^4.0.0"
3636
},
3737
"devDependencies": {
3838
"@pvtnbr/eslint-config": "^0.22.0",
@@ -58,6 +58,7 @@
5858
"error",
5959
{
6060
"allow": [
61+
"test",
6162
"describe",
6263
"runTestSuite"
6364
]

pnpm-lock.yaml

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/loaders-deprecated.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import {
88
transformDynamicImport,
99
} from '@esbuild-kit/core-utils';
1010
import {
11-
tsconfigRaw,
1211
sourcemaps,
12+
tsconfigRaw,
1313
tsExtensionsPattern,
1414
getFormatFromExtension,
1515
type ModuleFormat,

src/loaders.ts

+31-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import path from 'path';
2+
import { pathToFileURL } from 'url';
23
import {
34
transform,
45
transformDynamicImport,
56
resolveTsPath,
67
} from '@esbuild-kit/core-utils';
78
import {
8-
tsconfigRaw,
99
sourcemaps,
10+
tsconfigRaw,
11+
tsconfigPathsMatcher,
1012
tsExtensionsPattern,
1113
getFormatFromExtension,
1214
type ModuleFormat,
@@ -77,11 +79,14 @@ async function tryDirectory(
7779
}
7880
}
7981

82+
const fileProtocol = 'file://';
83+
const isPathPattern = /^\.{0,2}\//;
84+
8085
export const resolve: resolve = async function (
8186
specifier,
8287
context,
8388
defaultResolve,
84-
resursiveCall,
89+
recursiveCall,
8590
) {
8691
// Added in v12.20.0
8792
// https://nodejs.org/api/esm.html#esm_node_imports
@@ -94,6 +99,27 @@ export const resolve: resolve = async function (
9499
return await tryDirectory(specifier, context, defaultResolve);
95100
}
96101

102+
const isPath = (
103+
specifier.startsWith(fileProtocol)
104+
|| isPathPattern.test(specifier)
105+
);
106+
107+
if (
108+
tsconfigPathsMatcher
109+
&& !isPath // bare specifier
110+
) {
111+
const possiblePaths = tsconfigPathsMatcher(specifier);
112+
for (const possiblePath of possiblePaths) {
113+
try {
114+
return await resolve(
115+
pathToFileURL(possiblePath).toString(),
116+
context,
117+
defaultResolve,
118+
);
119+
} catch {}
120+
}
121+
}
122+
97123
/**
98124
* Typescript gives .ts, .cts, or .mts priority over actual .js, .cjs, or .mjs extensions
99125
*/
@@ -117,7 +143,8 @@ export const resolve: resolve = async function (
117143
} catch (error) {
118144
if (
119145
(error instanceof Error)
120-
&& !resursiveCall
146+
&& isPath
147+
&& !recursiveCall
121148
) {
122149
if ((error as any).code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
123150
return await tryDirectory(specifier, context, defaultResolve);
@@ -140,7 +167,7 @@ export const resolve: resolve = async function (
140167

141168
let { format } = resolved;
142169

143-
if (resolved.url.startsWith('file:')) {
170+
if (resolved.url.startsWith(fileProtocol)) {
144171
format = getFormatFromExtension(resolved.url) ?? format;
145172

146173
if (!format) {

src/utils.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import path from 'path';
22
import { installSourceMapSupport } from '@esbuild-kit/core-utils';
3-
import getTsconfig from 'get-tsconfig';
3+
import { getTsconfig, createPathsMatcher } from 'get-tsconfig';
4+
5+
export const sourcemaps = installSourceMapSupport();
46

57
const tsconfig = getTsconfig();
6-
export const tsconfigRaw = tsconfig?.config;
78

8-
export const sourcemaps = installSourceMapSupport();
9+
export const tsconfigRaw = tsconfig?.config;
10+
export const tsconfigPathsMatcher = tsconfig && createPathsMatcher(tsconfig);
911

1012
export const tsExtensionsPattern = /\.([cm]?ts|[tj]sx)$/;
1113

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import value from 'resolve-target';
2+
3+
console.log(value);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import value from 'paths-exact-match';
2+
3+
console.log(value);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import value from 'p/nested-resolve-target';
2+
3+
console.log(value);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import value from 'nested-resolve-target/s';
2+
3+
console.log(value);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 'resolved';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default 'resolved';

tests/fixtures/package-module/tsconfig/tsconfig.json

+6
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,11 @@
33
"jsx": "react",
44
"jsxFactory": "console.log",
55
"jsxFragmentFactory": "null",
6+
"baseUrl": "./src",
7+
"paths": {
8+
"paths-exact-match": ["resolve-target"],
9+
"p/*": ["utils/*"],
10+
"*/s": ["utils/*"]
11+
},
612
}
713
}

tests/specs/typescript/tsconfig.ts

+32-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,42 @@ import { testSuite, expect } from 'manten';
22
import type { NodeApis } from '../../utils/node-with-loader';
33

44
export default testSuite(async ({ describe }, node: NodeApis) => {
5-
describe('tsconfig', ({ test }) => {
5+
describe('tsconfig', ({ test, describe }) => {
66
test('jsxFactory & jsxFragmentFactory', async () => {
7-
const nodeProcess = await node.load('./tsx.tsx', {
7+
const nodeProcess = await node.load('./src/tsx.tsx', {
88
cwd: './tsconfig',
99
});
1010
expect(nodeProcess.stdout).toBe('div null hello world\nnull null goodbye world');
1111
});
12+
13+
describe('paths', ({ test }) => {
14+
test('resolves baseUrl', async () => {
15+
const nodeProcess = await node.load('./src/base-url.ts', {
16+
cwd: './tsconfig',
17+
});
18+
expect(nodeProcess.stdout).toBe('resolved');
19+
});
20+
21+
test('resolves paths exact match', async () => {
22+
const nodeProcess = await node.load('./src/paths-exact-match.ts', {
23+
cwd: './tsconfig',
24+
});
25+
expect(nodeProcess.stdout).toBe('resolved');
26+
});
27+
28+
test('resolves paths prefix', async () => {
29+
const nodeProcess = await node.load('./src/paths-prefix-match.ts', {
30+
cwd: './tsconfig',
31+
});
32+
expect(nodeProcess.stdout).toBe('resolved');
33+
});
34+
35+
test('resolves paths suffix', async () => {
36+
const nodeProcess = await node.load('./src/paths-suffix-match.ts', {
37+
cwd: './tsconfig',
38+
});
39+
expect(nodeProcess.stdout).toBe('resolved');
40+
});
41+
});
1242
});
1343
});

0 commit comments

Comments
 (0)