microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.77k stars 12.46k forks source link

allow voluntary .ts suffix for import paths #37582

Closed timreichen closed 1 year ago

timreichen commented 4 years ago

Search Terms

.ts suffix imports extension

Suggestion

Typescript doesn't recognize file imports with .ts suffix. Allow voluntary .ts to be added to import paths.

Use Cases

It seems right to be able to use a correct path to a file without magic resolution. This would help to align with deno which uses mandatory suffixes o files.

Examples

let import a from "path/to/a.ts" behave the same as import a from "path/to/a"

Checklist

My suggestion meets these guidelines:

Pyrolistical commented 2 years ago

One way to move forward without breaking everybody's existing code is to deprecate tsc and fork it into 2 commands:

  1. typescript type-checker tstc: only does type checking
  2. typescript bundler tsb: bundles js/ts files into one or many esm files

Both commands are esm compliant and require .ts extension.

Keep shipping tsc with existing behaviour but with a scary deprecated warning that tsc will be removed next version. After it is removed leave a placeholder tsc that links to an article on how to migrate to tstc/tsb.

Why break into 2 commands? Because that's what the community already did. tsc was so slow at emitting js from ts that babel-ts, esbuild and swc were created. VS Code lang server only needs tstc.

This would also allow the typescript team to keep their position on not rewriting javascript as part of "emitting" as now tsb is doing bundling which its job is to rewrite all the files.

andrewbranch commented 2 years ago

Why on earth would people want to use us as a bundler when esbuild and swc are fast, popular, and high-quality? We are more than happy for people to use these tools. The way to “move forward without breaking everybody’s code” is to add a new setting to --moduleResolution. But sure, technically, reimagining the scope of the entire project, deprecating everything that existed previously, leaving millions of users who do normal builds with tsc behind on that deprecated path, and writing a new bundler from the ground up that will be vastly slower than its competition is also one possible solution. 😄

RyanCavanaugh commented 2 years ago

To put it another way, we have finite time. We can either:

If someone wants to make an argument in favor of the latter I guess I'd want to hear it simply for intellectual curiosity... but we're going to do the former.

ritschwumm commented 2 years ago

@RyanCavanaugh it seems our experiences with typescript differ.

i'm quite happy (actually, very happy) with the type system, and the editor features.

what i'm not happy with is the UX of tsc. in 30 years of professional software development, i've never before spent so much time on understanding how all the options and variants of module resolution and compiler output interact with the already complicated node package ecosystem.

i think i can assume that all this is not a problem for you at all: you probably have seen all these options being built and evolve, and can remember their purpose, and what they do.

for someone who only uses tsc, as opposed to actively participating in its development, the picture is quite different. so please, please at least acknowledge that even when you do no see any problems with the status quo, other people might. imho, typescript could be a lot friendlier to "the rest of us".

RyanCavanaugh commented 2 years ago

The alternative world where we make a bundler is where those options get more complex, because we'd be folding in all of this configuration space into tsc's options.

Pyrolistical commented 2 years ago

@RyanCavanaugh I agree, the TypeScript team should focus on new features and not implementing a bundler.

Given that, maybe consider deprecating emitting from tsc? We already have 3 independent projects for emitting/bundling from the community. And probably even more if https://github.com/tc39/proposal-type-annotations lands.

TypeScript has won our hearts and minds. It is being embraced by the community. Perhaps it is time to hand over parts of TypeScript to the community as it is now mature enough to self-sustain.

rauschma commented 2 years ago

The use case I’m seeing is supporting TypeScript on Node.js – e.g. via loader hooks: https://github.com/lukeed/tsm

Maybe that functionality will eventually even be built into Node.js. If the filename extensions .ts, .tsx etc. were allowed, static type-checking would work at runtime. Node.js would simply strip away the type information (plus maybe a few simple transpilations).

inca commented 2 years ago

Ugh, tsc + noEmit, bundlers, revolutions. Just tell me, what do I need to use to compile a bunch of TypeScript files from src into a bunch of JavaScript files at out? So that things like Node.js could execute stuff from out and things like Deno can execute stuff from src (and also so that bundlers could also understand the imports the way they are written in src, which is still what the current issue is about).

@rauschma Your comment just landed, spot on.

paralin commented 2 years ago

@inca esbuild ./src/main.ts --outdir ./out --bundle

donmccurdy commented 2 years ago

@paralin Deno resolves imports at runtime using paths with .ts extensions, which tsc doesn't allow. So the command above will transpile source TS to JS, but source TS files are not compatible with Deno. As a library maintainer, this makes it harder to write portable packages in TypeScript.

RyanCavanaugh commented 2 years ago

Deno and NodeJS are different runtimes and there's in general not a way to write a program for one runtime and have it happen to execute against a different runtime. From the TS perspective, switching runtime targets is the job of a bundler.

paralin commented 2 years ago

@donmccurdy I see what you're saying. Being able to import the .ts suffix in the /pre-bundled/ code would definitely disambiguate a lot of situations. For example, when using a file like "thing.pb.ts" importing "thing.pb" doesn't work and "thing.pb.js" works, but confuses a lot of developers to the point that they file an issue complaining, and doesn't work in typescript < 4.7.x.

@RyanCavanaugh there's really no reason why deno and node.js shouldn't be able to execute the same code without changes, besides the limits put in place (for dubious reasons) by the developers of the compiler.

RyanCavanaugh commented 2 years ago

I agree that deno and node.js should support identical module resolution schemes

donmccurdy commented 2 years ago

From the TS perspective, switching runtime targets is the job of a bundler.

I'm OK with this. But at the same time, resolving imports in executable code is the job of the runtime. The Deno runtime has made a reasonable choice for their needs in using .ts files as executable code, and resolving explicit .ts paths. TS is making assumptions about what type of modules the runtime might be able to load. I can import from image.png without error, configuring TS to trust that something (in the runtime or the bundler) will resolve that import. But TS does not extend that same benefit of the doubt to imports with .ts extensions. I'm happy to use a third-party bundler to compile my code to JS, but it's frustrating that we need a VSCode extension to hide errors about .ts extensions, when Deno is a significant runtime environment for the JS/TS ecosystem.

andrewbranch commented 2 years ago

The discourse today make me think that maybe some people interpreted my earlier message and/or the state of the labels to mean that we have no idea how to address this issue or even that we still need to be convinced that it deserves action. I am not milestoning it yet because I don’t yet feel confident enough to make a public prediction about when I can land something, but I have a good grasp on the success criteria and constraints, and I’m gathering data and discussing a few concrete approaches with people on the team. Discussion is always welcome, but it would make my work easier if it becomes more focused as we narrow in on a course of action. This is not a catchall issue about the UX of tsc or the state of modules in JS, nor is it at a stage where ideas like “deprecate emit” are going to be considered, nor will expressing frustration at the current state of TypeScript make a solution appear any sooner.

The scope of this issue is that there are many module resolvers built into bundlers and non-Node runtimes where importing files by .ts extensions makes sense, and TypeScript does not have module resolution modes or options to support this. It should. Related, but out of scope, is the idea that you should always be able to write and check a TypeScript program targeting an arbitrary set of module resolvers.

rauschma commented 2 years ago

The most important use cases for the filename extension .ts that I can think of are:

They all:

I don’t think you need .ts for bare specifiers and full URIs – but I may be missing something.

Jamesernator commented 2 years ago

I don’t think you need .ts for bare specifiers and full URIs – but I may be missing something.

Deno does use .ts in full URLs for fetching from on the web including its own standard library (not to be confused with the global Deno object, which the standard library layers on top of).

izaakschroeder commented 2 years ago

For what it's worth this crude hack (+ yarn patch typescript) makes tsc accept .ts imports:

--- a/lib/tsc.js
+++ b/lib/tsc.js
@@ -34960,6 +34960,7 @@ var ts;
     }
     var Extensions;
     (function (Extensions) {
+        Extensions[Extensions["None"] = 6] = "None";
         Extensions[Extensions["TypeScript"] = 0] = "TypeScript";
         Extensions[Extensions["JavaScript"] = 1] = "JavaScript";
         Extensions[Extensions["Json"] = 2] = "Json";
@@ -35823,7 +35824,7 @@ var ts;
             requestContainingDirectory: containingDirectory,
             reportDiagnostic: function (diag) { return void diagnostics.push(diag); },
         };
-        var result = ts.forEach(extensions, function (ext) { return tryResolve(ext); });
+        var result = ts.forEach([Extensions.None].concat(extensions), function (ext) { return tryResolve(ext); });
         return createResolvedModuleWithFailedLookupLocations((_a = result === null || result === void 0 ? void 0 : result.value) === null || _a === void 0 ? void 0 : _a.resolved, (_b = result === null || result === void 0 ? void 0 : result.value) === null || _b === void 0 ? void 0 : _b.isExternalLibraryImport, failedLookupLocations, affectingLocations, diagnostics, state.resultFromCache);
         function tryResolve(extensions) {
             var loader = function (extensions, candidate, onlyRecordFailures, state) { return nodeLoadModuleByRelativeName(extensions, candidate, onlyRecordFailures, state, true); };
@@ -35948,6 +35949,12 @@ var ts;
             var extension = extensionLess ? candidate.substring(extensionLess.length) : "";
             return (extensionLess === undefined && extensions === Extensions.Json) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, extension, onlyRecordFailures, state);
         }
+        if (extensions === Extensions.None) {
+           var path = tryFile(candidate, onlyRecordFailures, state);
+           var extensionless = ts.removeFileExtension(candidate);
+           var extension = candidate.substring(extensionless.length);
+           return path === undefined ? undefined : { path: path, ext: extension };
+        }
         if (!(state.features & NodeResolutionFeatures.EsmMode)) {
             var resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, "", onlyRecordFailures, state);
             if (resolvedByAddingExtension) {
@@ -36226,6 +36235,8 @@ var ts;
         var packageFile;
         if (jsonContent) {
             switch (extensions) {
+                case Extensions.None:
+                    break;
                 case Extensions.JavaScript:
                 case Extensions.Json:
                 case Extensions.TsOnly:
@@ -42572,16 +42583,7 @@ var ts;
                     var moduleResolutionKind = ts.getEmitModuleResolutionKind(compilerOptions);
                     var resolutionIsNode16OrNext = moduleResolutionKind === ts.ModuleResolutionKind.Node16 ||
                         moduleResolutionKind === ts.ModuleResolutionKind.NodeNext;
-                    if (tsExtension) {
-                        var diag = ts.Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead;
-                        var importSourceWithoutExtension = ts.removeExtension(moduleReference, tsExtension);
-                        var replacedImportSource = importSourceWithoutExtension;
-                        if (moduleKind >= ts.ModuleKind.ES2015) {
-                            replacedImportSource += tsExtension === ".mts" ? ".mjs" : tsExtension === ".cts" ? ".cjs" : ".js";
-                        }
-                        error(errorNode, diag, tsExtension, replacedImportSource);
-                    }
-                    else if (!compilerOptions.resolveJsonModule &&
+                    if (!compilerOptions.resolveJsonModule &&
                         ts.fileExtensionIs(moduleReference, ".json") &&
                         ts.getEmitModuleResolutionKind(compilerOptions) !== ts.ModuleResolutionKind.Classic &&
                         ts.hasJsonModuleEmitEnabled(compilerOptions)) {
frank-dspeed commented 2 years ago

the main problem is resolve it self: some will prefer to resolve .js => .ts and some will want .ts => .js that depends total on usage so there is no good default for modules without any specifier.

just my 5 cent. but i would suggest droping bareSpecifierSupport for typescript as typescript is filebased only . that would make the most sense at present.

while there also is a option to use bareSpecifiers only but that would need engine support the only way to get the engine to take bare specifiers would be a import map via package.json or import maps nativ.

so again most elegant option that all involved support is file: protocol and so file specifiers.

src/files/a.js wants to import b from 'b' with node module resolution both will stat locking for b in ./node_modules/b or /node_modules/b or package.json map for node and tsconfig alias map for typescript

the main problem is there could be many places where a user defines package mappings

The only way to let typescript do its job would be to hook it into the specifier resolver of the host runtime or even a custom one

i propose implementing a resolve hook into typescript that is configure able via tsconfig.json

how to we get b to be in node_modules?

ctjlewis commented 2 years ago

Presumably these imports will not be rewritten, and so we will still not be able to build valid ESM programs without doing the "fake .js" hack that this team somehow thought was a sustainable solution.

Refusal to support specifier rewrites in TS is the biggest obstacle to ESM adoption at the moment.

Not only is ensuring valid TS programs build to valid ESM programs not being prioritized, but we're going deeper down the rabbit hole, where now you can have .ts specifiers that also won't resolve at runtime in addition to extension less TS-native relative specifiers that won't resolve at runtime.

We might as well just say TypeScript is its own language rather than a superset, given that I can produce arbitrarily many examples of valid ESM TypeScript programs that will not compile to valid ES modules.

TS's main goal is to compile to ES and we've seemingly just abandoned that. Absolutely astounding design choices.

See issue from 2 years ago: https://github.com/microsoft/TypeScript/issues/42151

paralin commented 2 years ago

TS's main goal is to compile to ES and we've seemingly just abandoned that

What? I don't agree. Does anyone run TypeScript without compiling to ES first?

ctjlewis commented 2 years ago

What? I don't agree. Does anyone run TypeScript without compiling to ES first?

You don't agree with what? Yes, many people do run TS without compiling, e.g. Deno (and now Bun). The .ts import specifier caters to that.

Did you read what I wrote about valid TS-ESM programs not compiling to valid ESM programs?

Go put "type": "module" in your package.json, set "module": "esnext" in tsconfig, and see how your import "./extensionLessRelativeImport" statement behaves at runtime in the emitted program.

My point is that we are supposed to be able to compile to ES like you're saying, yet between the ESM issues and this feature we're clearly shifting away from the superset idea. Partially because of poor design choices by design-makers, partially because of this team's unwillingness to receive feedback regarding module resolution.

TypeScript today is not an ECMAScript superset as we have shown. It would take TS adding rewrites for extensionless relative imports or dropping compiler support for this to change.

RyanCavanaugh commented 2 years ago

@frank-dspeed please keep discussion on-topic

thetutlage commented 2 years ago

Does this mean that a file using .ts extension in a relative import will retain the same extension after it gets compiled to JavaScript?

frank-dspeed commented 2 years ago

@thetutlage nope it means that there is a .ts even allowed before compiling it to js. Today Typescript is not aware of file extensions in its core it treats everything as Module or Script that are the 2 types that Typescript understands and a Module has no concept of a fileextension as a Module is not related to a file.

so internal: MyModule.ts is the Same as MyModule.js as Both are simply MyModule hope that makes sense.

and this talks about hornoring the extension which is not really possible as i pointed out above :) so it is a looping problem

andrewbranch commented 2 years ago

@thetutlage yes, so the plan is to only allow .ts extensions in relative imports when --noEmit is set.

MicahZoltu commented 2 years ago

Why would you refuse to emit with a .ts extension? Emitting no extension is already broken (won't run in any environment other than legacy NodeJS), so emitting .ts wouldn't make things worse.

Allowing emit of an untransformed .ts would at least make it easier for external tooling to transform paths, as it can just look for any file with .ts suffix and transform it rather than trying to guess whether a suffixless import was a TS file or an actual file without a suffix.

On top of that, if I am writing code that is meant to run in either deno or ts-node or ES Modules, I would want to write it with .ts extensions (which IMO is the correct way to write imports of TS files), and then have a post-compile transformer script that fixes the broken emitted code, rather than having to write code that doesn't work in deno or ts-node and having to require some special integration in those tools that fixes the broken un-emitted code).

frank-dspeed commented 2 years ago

@MicahZoltu this is correct and is a sympthom of the root cause that .js is not able to check it self when it references existing d.ts files the root cause is a circle of deependencys it is like with RHEL we got the same high level situation here.

So we need to apply the same solutions here: FEDORA is the Experimental RHEL release CENTOS gets drived from RHEL and CENTOS is needed to get Packages Into Fedora and RHEL . so you need to stream back and for from REHL to CENTOS thats why they made now CentOS Stream.

Typescript has the same problem .ts is the Experimental .js Release which is then the link to .d.ts and we need to stream back and for between .js .d.ts to get .ts again.

The real solution would be tackling all the JS .d.ts isssues that would enable direct authoring in .js as .js is the only run able real format not .ts

while there exists workarounds to make .ts run it would be the right thing to execute the ECMAScript Annotation proposal that would be done via finishing the babel integration and then update the proposal as soon as the babel work is done i will push my changes about that proposal into the Oracle Java Stack (GraalJS) this will enable the propsal then in all JDK Versions and all JS / ECMAScript integrations on the Java Platform so the proposal is then use able already by Millions out of the box.

Next step create a small typescript tool that only detects none annotation based typescript features integrate that into babel and typescript it self to give usefull warnings when such typescript features are in the code.

We Should never forget where typescript comes from it is a Facade Impelementation on top of ECMAScript designed to get Compiled away.

josh-hemphill commented 2 years ago

Why would you refuse to emit with a .ts extension? Emitting no extension is already broken (won't run in any environment other than legacy NodeJS), so emitting .ts wouldn't make things worse.

--noEmit doesn't emit without extensions... It instructs tsc to only type check and not emit any code at all. Meaning you can use .ts in your IDE/editor and when you actually need to compile you just have to have something change the suffixes before passing to tsc (e.g. a loader or some other tool).

frank-dspeed commented 2 years ago

@josh-hemphill i guess you have forgotten the secund importent case that typescript gets used for .d.ts generation --noEmit is a project setting and applys when you send the project to tsc that is correct but when you handle the files via diffrent tooling --noEmit does not matter so you can see it simple as indicator that the project gets handled external

MicahZoltu commented 2 years ago

--noEmit doesn't emit without extensions... It instructs tsc to only type check and not emit any code at all. Meaning you can use .ts in your IDE/editor and when you actually need to compile you just have to have something change the suffixes before passing to tsc (e.g. a loader or some other tool).

I understand what --no-emit does, what I don't understand is why you would have the compiler generate an error on emit if an emitted file would have a relative path import that ends in .ts. Given that .ts imports will be resolvable by the compiler prior to the emit phase (after implementing this), you must be going out of your way to have it fail at emit time, and I don't understand why you would do that.

Previously, the argument that @RyanCavanaugh has made is that the TS compiler shouldn't be transforming import paths, so all you would have to do here is continue to follow that reasoning and emit what the user said (e.g., ./path/to/file.ts).

frank-dspeed commented 2 years ago

@MicahZoltu let me explain the internals again in general TS does not Handle any Suffix anything is a Script or Module without a extension JS TS ECMAScript all have no notion of files in general out of Engine view a Runtime does implement that kind of features.

in general a .ts extension when they should stay internal leads to a lot of confusion about what to resolve and what is authorativ.

This got internal solved via stripping the file extensions and joining all the files so for example if .js .d.ts exist they are for TS a single Module when .ts exists also this is the single authorativ module

there exists no internal way at present to tell TS what is Authorativ it has a hardcoded priority .ts => .js .mjs .cjs => d.ts d.mts d.cts

import {} from 'my.ts'  // => "my" (module) so generates my.js my.d.ts
import {} from 'my.js'  // => "my" (module) so lookup my.d.ts

and if you wonder why all that is that way it is much more deep at the root for example you can create a global.d.ts and define there via declare key word modules again no matter in which files they are . so you see TS turns the files into a total diffrent view the Module View it has internal no concept of Module => File

this relation gets made when tsc read the modules or scripts from files it is a additional layer on top of the existing internal

MicahZoltu commented 2 years ago

@frank-dspeed Currently, if you do import 'my.js' in TS it will emit import 'my.js', and if you do import 'my' it will emit import 'my'. This behavior tells me that the compiler/emitter does not fully strip the extension because it is able to emit a file with the same extension as you have written in the TS import statement.

For the context of this issue, I don't particularly care about what the compiler is doing internally, I'm only asking that it doesn't needlessly error at emit time when the imported file has a .ts suffix.

frank-dspeed commented 2 years ago

@MicahZoltu what should it do so when it gets a import with .ts should it emit .js ? that would mean path rewriting so we need --noEmit.

the only way .ts can be perserved is simply do not compile it to js and as soon as that is done you can have .ts

if you then still want to compile to js you need to use external tooling like rollup as this is happy to rewrite that specifiers for you. hope that makes some sense for you i hate the situation overall my self but this is not the only problem and they are all related some how its about module resolution at all.

MicahZoltu commented 2 years ago

@MicahZoltu what should it do so when it gets a import with .ts should it emit .js ? that would mean path rewriting so we need --noEmit.

For the context of this issue, it should emit import 'my.ts' since TSC has a policy of never rewriting import paths. This is the behavior proposed in #38149.

I would like for the compiler team to drop that policy and emit import 'my.js', but that is out of scope of this issue. My latest argument for this can be seen at https://github.com/microsoft/TypeScript/issues/49083#issuecomment-1228160389

inca commented 2 years ago

Watching this thread, I have a constant urge to contribute my piece of reasoning, along with once again reminding everyone of greater goals and ideals — but honestly, everything is already said, all the possible reasoning already articulated, more than once. I don't think anyone can contribute anything else — the decision is made. 😢

andrewbranch commented 2 years ago

Allowing emit of an untransformed .ts would at least make it easier for external tooling to transform paths, as it can just look for any file with .ts suffix and transform it rather than trying to guess whether a suffixless import was a TS file or an actual file without a suffix.

@MicahZoltu we are actually open to this. @robpalme has made this request because at Bloomberg they have a custom runtime resolver that handles it. But we are intentionally going to put a bit of friction (an extra flag or something) in this path and not advertise it much because it essentially arms a big footgun. As you said, the emit will not run in the typical places where we expect JavaScript to run. We want to make sure folks understand what they’re getting into if they opt into that, rather than just saying “I like the way my code looks with .ts extensions” and then being surprised when it crashes in Node. I simplified this stance in my earlier comment because it’s still a bit up in the air; there is relevant discussion in #50152.

I would like for the compiler team to drop that policy and emit import 'my.js', but that is out of scope of this issue.

I have a constant urge to contribute my piece of reasoning, along with once again reminding everyone of greater goals and ideals — but honestly, everything is already said

I’m afraid this is going to sound sarcastic, but it’s 100% sincere—I can’t tell you how much I appreciate this exercise of restraint.

thetutlage commented 2 years ago

I personally think that allowing the .ts extension is not a great choice. It just makes the official compiler tsc emit code that cannot run the most of the default JavaScript runtimes.

nicolo-ribaudo commented 2 years ago

@thetutlage The current proposal is to allow .ts when type-cheking, but not when emitting, so tsc would not emit code that does not run. @andrewbranch Mentioned that they might allow emitting it, but only with explicit opt-in (and at that point, if you ask "hey, let me emit invalid code!", then you know that you have to do something to make it runnable).

thetutlage commented 2 years ago

@nicolo-ribaudo Thanks for the clarification.

 The current proposal is to allow .ts when type-cheking, but not when emitting, so tsc would not emit code that does not run

Will tsc fail or will it transform to .js?

nicolo-ribaudo commented 2 years ago

It will fail.

It doesn't transform it to .js because TSC only transform TS-specific parts and does not touch JS-specific parts (other than transpiling to equivalent code in a Babel-like way). As far as the JavaScript spec says, import "foo.ts" is valid JavaScript* and thus TS will not transform it.

* Valid JavaScript that doesn't work with the default behavior of Node.js modules. It works, for example, in browsers or with custom Node.js resolvers.

frank-dspeed commented 2 years ago

@nicolo-ribaudo tc39 member here bare specifiers are also part of the spec and the spec is exactly the problem we did a failure as we defined resolve and load to happen inside the Runtime and Not Inside the Engine. Many tc39 members agree in that that specifier lookup in the Runtime was a mistake. the ECMAScript spec defines imports as specifiers and urls so everything is valid. Thats is not the question. The failure got even replicated via Tc52 to dart lang.

I hope we do not introduce the same stuff in the next lang that we prepare.

to be more exact the spec specifies in a simple form that usage of the import keyword causes calls into the runtime and that can define relative unspecified behavior. The only Real view out of Engine is the Global Context there exists nothing else while you can scope via for example inside engine

// Here is global stuff ....
{
  // here is scoped stuff
}
// [indirect eval() call](https://es5.github.io/#x10.4.2), e.g. 
// 0 === global context
(0,eval)('console.log(this)').

‟Ex igne vita”

MicahZoltu commented 2 years ago

If there is an option to allow emitting of .ts, then that satisfies me for the sake of this issue.

frank-dspeed commented 2 years ago

i guess this can be bundled now with

this introduces the next problem:

Relation

Typescript is forced to handle conditional imports that enables conditional import typescript or types so when #47931 gets implemented and used .ts emit can always save be done as we 100% know we target typescript it self

tracker1 commented 1 year ago

I find it funny... TS will read import foo from "myFile" go through the trouble of checking for (.tsx, .ts, .jsx, .js) then actually load that first match, and proceed to potentially transform that second file, but actually supporting an explicit file extension, or changing the import statement to match the about to be transformed explicit file output (that is different from input), is too far.

frank-dspeed commented 1 year ago

@tracker1 the reason is historical and it is not a bad one at all only this implementation is done bad the reasoning and concepts are in general well it is a bit like with the ECMASpec it self it is easy to write a good spec but implementation and definition do then end up in something diffrent.

The ground concept is the classic resolve algo which is you reference myname.js and it will lookup myname.ts as also myname.d.ts

that is the base then the assumption comes in that you can not reference .ts files as this would mean to change existing code eg make it point to .js.

when we now want to reference .ts we broke the core concept of TS which is to be a facade over JS that is optional.

but now as we used all that we come to the conclusion that referencing .ts would be beneficial if we apply external tooling as that saves us from lookups which are error proune.

And now to solve this there are 2 major ways to go and both are none appealing for ts and mirosoft.

The Both ways to go are:

But i identified many incremental pathes to update

vixalien commented 1 year ago

I hope this also allows enforcing the use of extensions as an option so that imports without extensions aren't allowed

andrewbranch commented 1 year ago

It does not. You might want to track #50153, and if you’re concerned with Deno, see my advice at https://github.com/microsoft/TypeScript/pull/51669#issuecomment-1349917006.

karlhorky commented 1 year ago

enforcing the use of extensions as an option so that imports without extensions aren't allowed

To approach this problem from another angle, you may want to consider eslint-plugin-node with the node/file-extension-in-import option:

https://github.com/mysticatea/eslint-plugin-node/blob/master/docs/rules/file-extension-in-import.md

This is what we've been using to enforce .js extensions in our TypeScript code (because we're using ESM with Node.js, which requires the extensions). Haven't tried it yet with .ts extensions.

andrewbranch commented 1 year ago

If you are compiling TS code to JS code that will run in Node, you should be using --module nodenext (which implies --moduleResolution nodenext), which requires extensions in precisely the places that Node does. No lint rule should be necessary if your reason for enforcing extensions is that you want your code to run in Node. That is built into TypeScript, and has been for about a year. #51669 is made for bundlers, and there is not a single bundler I know of (and I tested a bunch) that ever requires extensions on imports, so likewise it is not a requirement of the module resolution mode.