Description
This is a dedicated thread for the issue of file extensions in import
specifiers in TypeScript code. Refs: nodejs/node#53725 (comment), #208 (comment), nodejs/node#53725 (comment), nodejs/node#53725 (comment), among others.
Current proposal: Users write full filenames with extensions in their specifiers, similar to ES module JavaScript: import './file.ts'
. Also, .ts
extensions are required in require
calls, similar to the .cjs
extension in CommonJS JavaScript: require('./file.ts')
.
- This can be type-checked by
tsc
via theallowImportingTsExtensions
tsconfig.json
option. - This aligns with ES module JavaScript and Deno.
tsc
cannot compile such TypeScript files into JavaScript (today) but many other build tools can. However, there’s not necessarily a need to compile TypeScript files into JavaScript when they can be run directly. The only benefit that a build step would provide would be for startup time performance (it would be slightly faster to evaluate JavaScript directly rather than stripping types from TypeScript and then evaluating it) and users concerned about this should arguably be using a full-featured bundler such as Vite rather than a one-to-one transpiler such astsc
.
Extension searching alternative: Users would leave off extensions, similar to CommonJS: import './file'
. This is a very common way for TypeScript code to be written today, because most TypeScript code is transpiled down to CommonJS JavaScript where such specifiers are permitted.
- This can’t be supported in
require
statements, as it would be a semver-major breaking change. - It would mean that we can’t add new file formats in the future without them also being breaking changes.
- If this is implemented by the same pass that replaces types with whitespace, where the extension is added before the source is evaluated, then we have the same problems introduced by supporting transforms: line and column numbers would shift and we would need to add support for source maps, slowing down execution.
- If this is implemented in Node’s resolution step, then resolution rules for ES module JavaScript would differ based on whether the importing module was a JavaScript file or a TypeScript file. This would be hard to document and explain, and would be a difficult edge case for userland customization hooks to need to support.
Replace .js
alternative: Users would write import '/file.js'
to refer to file.ts
. This is what tsc
recommends for users using tsc
as their build tool, as it compiles .ts
files to .js
files one-to-one and never rewrites import specifiers.
- This is counterintuitive, as there are no
.js
files generated as part of running files viastrip-types
. - What if both
file.js
andfile.ts
exist? None of the various options (error, load one file or the other) are obviously correct or consistent. - Rewriting
file.ts
tofile.ts
is arguably something that’s logically handled by the build tool or bundler, as that’s the tool generating thefile.js
file to which the new import specifier would refer. It’s hard to see how it’s Node’s responsibility to make up for a shortcoming in a TypeScript build tool. - If this is implemented in Node’s resolution step, then resolution rules for ES module JavaScript would differ based on whether the importing module was a JavaScript file or a TypeScript file. This would be hard to document and explain, and would be a difficult edge case for userland customization hooks to need to support.