-
-
Notifications
You must be signed in to change notification settings - Fork 529
/
file-extensions.ts
150 lines (135 loc) · 4.9 KB
/
file-extensions.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
145
146
147
148
149
150
import type * as _ts from 'typescript';
import type { RegisterOptions } from '.';
import { versionGteLt } from './util';
/**
* Centralized specification of how we deal with file extensions based on
* project options:
* which ones we do/don't support, in what situations, etc. These rules drive
* logic elsewhere.
* @internal
* */
export type Extensions = ReturnType<typeof getExtensions>;
const nodeEquivalents = new Map<string, string>([
['.ts', '.js'],
['.tsx', '.js'],
['.jsx', '.js'],
['.mts', '.mjs'],
['.cts', '.cjs'],
]);
// All extensions understood by vanilla node
const vanillaNodeExtensions: readonly string[] = [
'.js',
'.json',
'.node',
'.mjs',
'.cjs',
];
// Extensions added by vanilla node's require() if you omit them:
// js, json, node
// Extensions added by vanilla node if you omit them with --experimental-specifier-resolution=node
// js, json, node, mjs
// Extensions added by ESM codepath's legacy package.json "main" resolver
// js, json, node (not mjs!)
const nodeDoesNotUnderstand: readonly string[] = [
'.ts',
'.tsx',
'.jsx',
'.cts',
'.mts',
];
/**
* [MUST_UPDATE_FOR_NEW_FILE_EXTENSIONS]
* @internal
*/
export function getExtensions(
config: _ts.ParsedCommandLine,
options: RegisterOptions,
tsVersion: string
) {
// TS 4.5 is first version to understand .cts, .mts, .cjs, and .mjs extensions
const tsSupportsMtsCtsExts = versionGteLt(tsVersion, '4.5.0');
const requiresHigherTypescriptVersion: string[] = [];
if (!tsSupportsMtsCtsExts)
requiresHigherTypescriptVersion.push('.cts', '.cjs', '.mts', '.mjs');
const allPossibleExtensionsSortedByPreference = Array.from(
new Set([
...(options.preferTsExts ? nodeDoesNotUnderstand : []),
...vanillaNodeExtensions,
...nodeDoesNotUnderstand,
])
);
const compiledJsUnsorted: string[] = ['.ts'];
const compiledJsxUnsorted: string[] = [];
if (config.options.jsx) compiledJsxUnsorted.push('.tsx');
if (tsSupportsMtsCtsExts) compiledJsUnsorted.push('.mts', '.cts');
if (config.options.allowJs) {
compiledJsUnsorted.push('.js');
if (config.options.jsx) compiledJsxUnsorted.push('.jsx');
if (tsSupportsMtsCtsExts) compiledJsUnsorted.push('.mjs', '.cjs');
}
const compiledUnsorted = [...compiledJsUnsorted, ...compiledJsxUnsorted];
const compiled = allPossibleExtensionsSortedByPreference.filter((ext) =>
compiledUnsorted.includes(ext)
);
const compiledNodeDoesNotUnderstand = nodeDoesNotUnderstand.filter((ext) =>
compiled.includes(ext)
);
/**
* TS's resolver can resolve foo.js to foo.ts, by replacing .js extension with several source extensions.
* IMPORTANT: Must preserve ordering according to preferTsExts!
* Must include the .js/.mjs/.cjs extension in the array!
* This affects resolution behavior!
* [MUST_UPDATE_FOR_NEW_FILE_EXTENSIONS]
*/
const r = allPossibleExtensionsSortedByPreference.filter((ext) =>
[...compiledUnsorted, '.js', '.mjs', '.cjs', '.mts', '.cts'].includes(ext)
);
const replacementsForJs = r.filter((ext) =>
['.js', '.jsx', '.ts', '.tsx'].includes(ext)
);
const replacementsForJsx = r.filter((ext) => ['.jsx', '.tsx'].includes(ext));
const replacementsForMjs = r.filter((ext) => ['.mjs', '.mts'].includes(ext));
const replacementsForCjs = r.filter((ext) => ['.cjs', '.cts'].includes(ext));
const replacementsForJsOrMjs = r.filter((ext) =>
['.js', '.jsx', '.ts', '.tsx', '.mjs', '.mts'].includes(ext)
);
// Node allows omitting .js or .mjs extension in certain situations (CJS, ESM w/experimental flag)
// So anything that compiles to .js or .mjs can also be omitted.
const experimentalSpecifierResolutionAddsIfOmitted = Array.from(
new Set([...replacementsForJsOrMjs, '.json', '.node'])
);
// Same as above, except node curiuosly doesn't do .mjs here
const legacyMainResolveAddsIfOmitted = Array.from(
new Set([...replacementsForJs, '.json', '.node'])
);
return {
/** All file extensions we transform, ordered by resolution preference according to preferTsExts */
compiled,
/** Resolved extensions that vanilla node will not understand; we should handle them */
nodeDoesNotUnderstand,
/** Like the above, but only the ones we're compiling */
compiledNodeDoesNotUnderstand,
/**
* Mapping from extensions understood by tsc to the equivalent for node,
* as far as getFormat is concerned.
*/
nodeEquivalents,
/**
* Extensions that we can support if the user upgrades their typescript version.
* Used when raising hints.
*/
requiresHigherTypescriptVersion,
/**
* --experimental-specifier-resolution=node will add these extensions.
*/
experimentalSpecifierResolutionAddsIfOmitted,
/**
* ESM loader will add these extensions to package.json "main" field
*/
legacyMainResolveAddsIfOmitted,
replacementsForMjs,
replacementsForCjs,
replacementsForJsx,
replacementsForJs,
};
}