forked from TypeStrong/ts-node
-
Notifications
You must be signed in to change notification settings - Fork 0
/
esm.ts
144 lines (120 loc) · 4.26 KB
/
esm.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import { getExtensions, Service } from './index';
import {
parse as parseUrl,
format as formatUrl,
UrlWithStringQuery,
fileURLToPath,
pathToFileURL,
} from 'url';
import { extname } from 'path';
import * as assert from 'assert';
import { normalizeSlashes } from './util';
const {
createResolve,
} = require('../dist-raw/node-esm-resolve-implementation');
// Note: On Windows, URLs look like this: file:///D:/dev/@TypeStrong/ts-node-examples/foo.ts
export function createEsmHooks(tsNodeService: Service) {
// Custom implementation that considers additional file extensions and automatically adds file extensions
const nodeResolveImplementation = createResolve({
...getExtensions(tsNodeService.config),
preferTsExts: tsNodeService.options.preferTsExts,
});
return { resolve, getFormat, transformSource };
function isFileUrlOrNodeStyleSpecifier(parsed: UrlWithStringQuery) {
// We only understand file:// URLs, but in node, the specifier can be a node-style `./foo` or `foo`
const { protocol } = parsed;
return protocol === null || protocol === 'file:';
}
async function resolve(
specifier: string,
context: { parentURL: string },
defaultResolve: typeof resolve
): Promise<{ url: string }> {
const defer = async () => {
const r = await defaultResolve(specifier, context, defaultResolve);
return r;
};
const parsed = parseUrl(specifier);
const { pathname, protocol, hostname } = parsed;
if (!isFileUrlOrNodeStyleSpecifier(parsed)) {
return defer();
}
if (protocol !== null && protocol !== 'file:') {
return defer();
}
// Malformed file:// URL? We should always see `null` or `''`
if (hostname) {
// TODO file://./foo sets `hostname` to `'.'`. Perhaps we should special-case this.
return defer();
}
// pathname is the path to be resolved
return nodeResolveImplementation.defaultResolve(
specifier,
context,
defaultResolve
);
}
type Format = 'builtin' | 'commonjs' | 'dynamic' | 'json' | 'module' | 'wasm';
async function getFormat(
url: string,
context: {},
defaultGetFormat: typeof getFormat
): Promise<{ format: Format }> {
const defer = (overrideUrl: string = url) =>
defaultGetFormat(overrideUrl, context, defaultGetFormat);
const parsed = parseUrl(url);
if (!isFileUrlOrNodeStyleSpecifier(parsed)) {
return defer();
}
const { pathname } = parsed;
assert(
pathname !== null,
'ESM getFormat() hook: URL should never have null pathname'
);
const nativePath = fileURLToPath(url);
// If file has .ts, .tsx, or .jsx extension, then ask node how it would treat this file if it were .js
const ext = extname(nativePath);
let nodeSays: { format: Format };
if (ext !== '.js' && !tsNodeService.ignored(nativePath)) {
nodeSays = await defer(formatUrl(pathToFileURL(nativePath + '.js')));
} else {
nodeSays = await defer();
}
// For files compiled by ts-node that node believes are either CJS or ESM, check if we should override that classification
if (
!tsNodeService.ignored(nativePath) &&
(nodeSays.format === 'commonjs' || nodeSays.format === 'module')
) {
const { moduleType } = tsNodeService.moduleTypeClassifier.classifyModule(
normalizeSlashes(nativePath)
);
if (moduleType === 'cjs') {
return { format: 'commonjs' };
} else if (moduleType === 'esm') {
return { format: 'module' };
}
}
return nodeSays;
}
async function transformSource(
source: string | Buffer,
context: { url: string; format: Format },
defaultTransformSource: typeof transformSource
): Promise<{ source: string | Buffer }> {
const defer = () =>
defaultTransformSource(source, context, defaultTransformSource);
const sourceAsString =
typeof source === 'string' ? source : source.toString('utf8');
const { url } = context;
const parsed = parseUrl(url);
if (!isFileUrlOrNodeStyleSpecifier(parsed)) {
return defer();
}
const nativePath = fileURLToPath(url);
if (tsNodeService.ignored(nativePath)) {
return defer();
}
const emittedJs = tsNodeService.compile(sourceAsString, nativePath);
return { source: emittedJs };
}
}