Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript and <script type="module"></script> #13422

Closed
cyrilletuzi opened this issue Jan 11, 2017 · 67 comments
Closed

TypeScript and <script type="module"></script> #13422

cyrilletuzi opened this issue Jan 11, 2017 · 67 comments
Labels
Domain: ES Modules Needs Proposal Suggestion

Comments

@cyrilletuzi
Copy link

@cyrilletuzi cyrilletuzi commented Jan 11, 2017

A first implementation of <script type="module"><script> just landed in Safari Technology Preview 21, so I tried to use it on a TypeScript / Angular project, to finally get rid of system.js.

First issue, resolved but unconvenient : ES6 modules paths must include the '.js' extension. Fortunately, TS 2.0+ knows how to resolve import { Something } from './test.js' into import { Something } from './test.ts' while in dev. But it's a huge change to common practices and a lot of refactoring : official TypeScript docs, Angular, RxJS and so on have all encouraged until know to use the no extension form (import { Something } from './test').

Second issue, unresolved : TS provides many options to tell the compiler how to resolve paths like import { Component } from '@angular/core'. But it never transforms the imported path in the compiled files. '@angular/core' will stay '@angular/core' in the transpiled files. After investigation, I've read in many issues it is by design.

It's OK with loaders like system.js and others, which provide similar path mapping options. But with <script type="module"><script>, as far as I know, there is no configuration, so import { Component } from '@angular/core' will always fail.

I am missing something, or does that really mean that we won't be able to use the native loader with TypeScript projects ?

If I'm right, is it possible to add an option to force path transformation in compiled files ?

@aluanhaddad
Copy link
Contributor

@aluanhaddad aluanhaddad commented Jan 14, 2017

Just a comment. It seems ironic that you wish to

to finally get rid of system.js

When it is about the only option comes that close to what you seem to be trying to achieve.

But it's a huge change to common practices and a lot of refactoring : official TypeScript docs, Angular, RxJS and so on have all encouraged until know to use the no extension form (import { Something } from './test').

Personally, I think extensionless imports are far better, they make your code far more portable.

I suspect the native loader will eventually support configuration. Of course the specification is in major flux, but there is likely to be a configurable API to perform abstracted name resolution for imports like

import {Component} from '@angular/core';

because it is clearly needed and because the issue has been discussed at least to some extent. I've heard discussion of how to resolve

import $ from 'jquery';

You may find this repository interesting https://github.com/whatwg/loader/

If I'm right, is it possible to add an option to force path transformation in compiled files ?

No this is not possible at present.

@cyrilletuzi
Copy link
Author

@cyrilletuzi cyrilletuzi commented Jan 14, 2017

I don't see what's ironic in wanting to depend on native and standard features, instead of librairies...

@aluanhaddad
Copy link
Contributor

@aluanhaddad aluanhaddad commented Jan 14, 2017

Sorry I am a dyed in the wool SystemJS user (actually I'm not sorry 😜). But the whole point of SystemJS is to get out of the way, when native loaders are available, but until then polyfill the loader specification (and provide other features such as CommonJS interop).
In other words it has built in obsolescence, by design, so it does not interfere with the future it seeks to enable today.

Seriously SystemJS is great. Not every framework uses it well, for example Angular 2, made poor use of it and is now removing it. Look at Aurelia as an example framework that, while not requiring SystemJS, uses it by default and plays to its strengths showing how elegant it can be.

@cyrilletuzi
Copy link
Author

@cyrilletuzi cyrilletuzi commented Jan 14, 2017

It's not a charge against SystemJS. Yes, it is a nice tool. But as you said it very well, it was designed as a temporary tool until the native loader is finally here.

So now the central part of it is there (<script type="module"></script>), but unusable in a real case : I can't see the point if we can just load a few local scripts, and no librairies. So I'm confused.

Hopefully configuration is coming. Or hopefully TypeScript will manage it.

@aluanhaddad
Copy link
Contributor

@aluanhaddad aluanhaddad commented Jan 15, 2017

@Avol-V
Copy link

@Avol-V Avol-V commented Jan 19, 2017

Personally, I think extensionless imports are far better, they make your code far more portable.

SystemJS didn't recommend to use default extension as well.

@mhegazy
Copy link
Contributor

@mhegazy mhegazy commented Jan 26, 2017

i am not sure I see why this is a TS issue. If you put the full file path in the module, it should work. Or putting it diffrentelly, how could you do the same with a plain .js file with the typescript compiler completely out of the picture?

@Avol-V
Copy link

@Avol-V Avol-V commented Jan 26, 2017

Whether TypeScript transforms paths or not, it could be an option to add extension to the names of modules (or it could convert .ts to .js).

@mhegazy
Copy link
Contributor

@mhegazy mhegazy commented Jan 26, 2017

import .. from "./foo.ts" is not allowed so it is either "./foo.js" or just "./foo". I would recommend using consistent extension and always specify .js.

@cyrilletuzi
Copy link
Author

@cyrilletuzi cyrilletuzi commented Jan 27, 2017

i am not sure I see why this is a TS issue. If you put the full file path in the module, it should work

As far as I know, TypeScript doesn't support absolute paths. So no, it doesn't work.

After a deeper thinking, I think the current behavior is really not normal. As a transpiler, TS should produce standard and ready-to-work JavaScript. TS also choose the very good design option to be "just" enhanced JS, ie. backward compatible with normal JS.

import { Something } from './something' is clearly not standard JS.

@mhegazy
Copy link
Contributor

@mhegazy mhegazy commented Jan 27, 2017

As far as I know, TypeScript doesn't support absolute paths. So no, it doesn't work.

what do you mean absolute path? can you share an example of an app with a module that is absolute? a CDN path should work with a path mapping entry.

import { Something } from './something' is clearly not standard JS.

We do not know what node will do yet. but if node considers this invalid filename, the compiler can flag it as an error. either ways, you should just write:

import { Something } from './something.js'

@cyrilletuzi
Copy link
Author

@cyrilletuzi cyrilletuzi commented Jan 27, 2017

We do not know what node will do yet. but if node considers this invalid filename

It's already an invalid filename for browsers, which is the main use case of TypeScript.

you should just write: import { Something } from './something.js'

I agree, that's what I do now to be future proof. Problem is, like I said on first message, that it's a huge change to common practices and a lot of refactoring : official TypeScript docs, Angular, RxJS and so on have all encouraged until know to use the no extension form (import { Something } from './test').

For absolute paths, I mean a code like this :

import { Component } from '/node_modules/@angular/core/index.js';

Otherwise, I need to do things like this :

import { Component } from '../../../node_modules/@angular/core/index.js';

First, it's a mess, second, it doesn't work if outDir is not at the same path level.

@cyrilletuzi
Copy link
Author

@cyrilletuzi cyrilletuzi commented Jan 27, 2017

you should just write: import { Something } from './something.js'

And most importantly, I can refactor my code, but I can't refactor librairies I use...

@mhegazy
Copy link
Contributor

@mhegazy mhegazy commented Jan 27, 2017

It's already an invalid filename for browsers, which is the main use case of TypeScript.

No it is not if you are using requireJS, Browserify, Webpack, SystemJs, etc.. i.e. any thing other than a browser that supports native ES6 modules, and as i far as i know, there are not many of these around today.

@mhegazy
Copy link
Contributor

@mhegazy mhegazy commented Jan 27, 2017

For absolute paths, I mean a code like this :

The loader spec does not really talk about how these absolute paths are allowed. When we have a clear description of that, allowing such should not be hard.

@cyrilletuzi
Copy link
Author

@cyrilletuzi cyrilletuzi commented Jan 28, 2017

there are not many of these around today

Implementations have started, it is now in Safari Technology Preview, in Edge Preview and it's coming soon in Chrome Canary. That was the starting point of this issue. TS is supposed to produce standard JS, not JS that need specific tools. Maybe it's too soon to decide, but for now common usage of TypeScript is incompatible with native JS.

@cyrilletuzi
Copy link
Author

@cyrilletuzi cyrilletuzi commented Jan 28, 2017

I don't understand why it's so difficult for this issue to be acknowledged.

TypeScript is supposed to be JS compatible, and it is also a transpiler. The point of a transpiler is to produce native JS. And it's not right anymore with <script type="module"></script>, so it's a serious issue.

OK, maybe there are still some points to be clarified (loader spec and Node choices are not final yet), but if navigators have started implementations (even Safari ^^), it's because they think the spec is now stable enough.

So now that it's possible in some beta versions to use <script type="module"></script>, it's impossible to make it work with a TypeScript project, like an Angular app, because :

  • absolute paths are required (I can use them in my code, but then dev tools like Intellisense don't work anymore as TS doesn't support them) ;
  • .js extension are required (I can use them in my code, but I can't rewrite Angular files where there is no .js extension anywhere, like in every current TypeScript projects as it was not the common usage, so all imports inside Angular fail).

And even if native loaders just started to appear, ES6 import is here for some time now, and it was always clear that the .js extension is required. Maybe I've missed something, but I've seen no sign that it could become optional.

That's precisely why tools like Systemjs offer a defaultJSExtension option from the beginning : because the .js extension has always been required. In the last Systemjs version (0.20) which aligns to the last loader spec, the global defaultJSExtension option has even been removed. Now you can just use it by package, meaning that you shouldn't rely on it, but it's just here to support compatibility with some packages that don't follow the standard, like TypeScript projects.

@mhegazy
Copy link
Contributor

@mhegazy mhegazy commented Feb 1, 2017

absolute paths are required (I can use them in my code, but then dev tools like Intellisense don't work anymore as TS doesn't support them) ;

this we can solve. and probably should. possibly with a flag to consider the / as baseUrl

.js extension are required (I can use them in my code, but I can't rewrite Angular files where there is no .js extension anywhere, like in every current TypeScript projects as it was not the common usage, so all imports inside Angular fail).

I would say this is an issue with the Angular code base, and not TS. there are going to be dependencies that do not use full URI, the TS compiler can not go touch all your node_modules to make them ES6 ready.

@Avol-V
Copy link

@Avol-V Avol-V commented Feb 2, 2017

I would say this is an issue with the Angular code base, and not TS.

But you should fix TypeScript documentation — currently there is no .js extension in examples of using modules.

And, for strict code convention, it can be an option of TypeScript to require .js extension in modules.

@cyrilletuzi
Copy link
Author

@cyrilletuzi cyrilletuzi commented Feb 2, 2017

I would say this is an issue with the Angular code base, and not TS.

It's not an Angular issue. It's a possibility, and the current common usage in all TypeScript projects, not just Angular. It's even the indicated way in official TypeScript documentation. And as valid TypeScript which is here to stay for compatibility reasons (next point), it should be managed and transpiled to valid JavaScript.

Of course TypeScript won't rewrite existing projects and files in my node_modules. But when I transpile my project, all my final transpiled files (my own and the ones imported from librairies) must be ES6 compliant.

And for current librairies to become ES6 compliant and ES6 ready-to-use, TypeScript must start to transpile to valid JS, which is not currently possible.

it can be an option of TypeScript to require .js extension in modules.

It won't happen as it would break compatibility. '.js' extension works only since TypeScript 2. As we can't revert things now, transpilation to valid JS must be managed.

@Avol-V
Copy link

@Avol-V Avol-V commented Feb 3, 2017

It won't happen as it would break compatibility. '.js' extension works only since TypeScript 2. As we can't revert things now, transpilation to valid JS must be managed.

It's true that we have a problem with compatibility and I think it can be an option to choose backward-compatible mode (with adding .js) and strict mode (with requiring .js).

Why we need that strict mode? The main idea of TypeScript is to be more strict than JavaScript, but currently in modules JavaScript is more strict than TypeScript. TypeScript should be as strict as JavaScript but with additional restrictions.

@cyrilletuzi
Copy link
Author

@cyrilletuzi cyrilletuzi commented Feb 9, 2017

Now in Firefox Nightly https://twitter.com/evilpies/status/829604616216125442

Meaning all major browsers are now implementing this. I think it's time for TypeScript to be ready.

@cyrilletuzi
Copy link
Author

@cyrilletuzi cyrilletuzi commented Feb 10, 2017

Another point : current TypeScript loaders (ts-loader, awesome-ts-loader and @ngtools/webpack) for webpack only work with extension-less imports. So if I need webpack for production, I won't be able to use <script type="module"> in development.

@mjackson
Copy link

@mjackson mjackson commented Apr 19, 2017

@cyrilletuzi Where did you read that support for <script type="module"> is coming to Chrome Canary?

Edit: Nevermind, found it https://www.chromestatus.com/feature/5365692190687232

@aluanhaddad
Copy link
Contributor

@aluanhaddad aluanhaddad commented Apr 19, 2017

@Avol-V

@aluanhaddad wrote:
Personally, I think extensionless imports are far better, they make your code far more portable.

SystemJS didn't recommend to use default extension as well.

That's true, but only for JavaScript code. SystemJS TypeScript transpilation doesn't work with a .js extension because there's no file on disk with that name.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation label May 24, 2017
@quantuminformation
Copy link

@quantuminformation quantuminformation commented Jun 16, 2017

Related to #16577

@Starcounter-Jack
Copy link

@Starcounter-Jack Starcounter-Jack commented Apr 16, 2020

  1. Tsc clearly resolves the module import. Meaning it is sound using Typescript semantics.
  2. Tsc generates ES6+.
  3. No environment resolves the resulting imports, meaning tsc generated unsound code according to ES6+ semantics.

This should clearly get fixed.

Tsc should either
a) provide a compilation error (tsc knows that the module it resolved to is not the module that the resulting code will resolve to); or
b) generate code that resolves to the very same module that the compiler resolved to

@Starcounter-Jack
Copy link

@Starcounter-Jack Starcounter-Jack commented Apr 16, 2020

I believe that the string literal syntax is fooling the compiler vendor here. The use of a string literal would normally dictate that this is a string, any string, that should be preserved during transpilation. It would not be a part of the language/compiler semantics.

In the Ecmascript language grammer this is a ModuleSpecifier. In the Ecmascript language specification, it comes with semantics. As such, the semantics should be mapped/translated as much as any other part of the two languages. That it looks like any string literal is irrelevant. It is not a value to preserve. It is a construct to be translated from Typescript semantics (i.e. in some cases, a reference to a typescript module) to Ecmascript semantics (i.e. a reference to a generated .js module).

To expain. A ModuleSpecifier is a reference to a Ecmascript module. In this case, an ES6 module that has been both generated by Typescript and referenced from Typescript. Pretend that the syntax and semantics of a ModuleSpecifier was vastly different in Typescript and Ecmascript. Pretend that it was not disguised as a string literal. Then ask your self how you should codegen from a code graph that had a reference to a module. It would then be obvious that the compiler/transpiler should not protect "the string".

The stomach feeling that to protect "the string" or the "file path" is right is a naive approach as both the the .js module and the Javascript import statement is generated by Typescript from a sound resolved semantic representation.

  1. It is easy to fix
  2. It is NOT a dirty fix
  3. Typescript should not resort to misguided gut feelings over soundness, integrity and correctness (we have Javascript semantics for that)

@tobias-huebner
Copy link

@tobias-huebner tobias-huebner commented Jul 17, 2020

I cant believe this is still an issue.

when you use
"baseUrl": ".", "paths": { "/*": ["./*"] },

you can do an absoulte module import like so:
`import ws from "/hey/connection";

but when adding the ".js" extension, all of the sudden the compiler no longer finds the connection.ts declaration and says:

Could not find a declaration file for module '/hey/connection'. '/home/tobi/Documents/JITcom/Code/Libs/Test_Browser/hey/connection.js' implicitly has an 'any' type.

using a relative path it all works fine.

Please fix this

@softwareCobbler
Copy link
Contributor

@softwareCobbler softwareCobbler commented Jul 17, 2020

import foo from "./bar.js" appears to work as one would hope, when bar is a TS file in the CWD. The output is a module that will run in the browser, and all the types get checked. Is this documented anywhere?

@weoreference
Copy link

@weoreference weoreference commented Jul 25, 2020

I have stumbled on this problem too! I can't believe a simple solution hasn't been (and probably will never be) officially provided : 0

@jameshfisher
Copy link

@jameshfisher jameshfisher commented Oct 1, 2020

Just started using TypeScript, and I'm extremely confused by this. I write import {foo} from './dep' in my TypeScript - fine, this is TypeScript semantics. But then tsc compiled this to the incorrect JavaScript import { foo } from './dep'. "That's weird", I thought, "What is my JavaScript interpreter supposed to do with this? OK, but there must be a compiler setting for it."

But now I'm on a GitHub issue from 3 years ago saying it's intentional, with no rationale, and there's no recommended way to fix it? WTF? It's just appending a file extension! The very same file extension that TypeScript already appended when it generated dep.js!

The TypeScript homepage states

TypeScript code is transformed into JavaScript code via the TypeScript compiler or Babel. This JavaScript is clean, simple code which runs anywhere JavaScript runs: In a browser, on Node.JS or in your apps.

No it doesn't! The browser does not run this code. Node.JS does not run this code. Find me an app that runs this code.

@martin-pabst
Copy link

@martin-pabst martin-pabst commented Oct 3, 2020

If you use Visual Studio Code you can configure it to automatically add the .js-ending when auto-importing modules. Go to settings, then type "module specifier" in the search field. You find option "typescript > Preferences: Import Module Specifier Ending" which you can set to ".js". This mitigates the problem a little bit...

@nuts-n-bits
Copy link

@nuts-n-bits nuts-n-bits commented Dec 15, 2020

Wow this is crazy. I wouldn't have thought an issue affecting me right now that seems to have a cut-and-dry solution to it from 3 years ago has not been resolved. Is really that few people are being affected by native import that this is not worth fixing?

@AurangAsif
Copy link

@AurangAsif AurangAsif commented Apr 3, 2021

Just ran into this issue and it surprises me that it hasn't been fixed. Workarounds are called workarounds for a reason - even if they work, they have their own problems.

@nuts-n-bits
Copy link

@nuts-n-bits nuts-n-bits commented Apr 3, 2021

Just ran into this issue and it surprises me that it hasn't been fixed. Workarounds are called workarounds for a reason - even if they work, they have their own problems.

Unfortunately I've found out that this bug is now considered "working as intended" and affirmatively closed by @RyanCavanaugh in a recent issue in January 2021. According to him there won't be a fix even behind a flag, so.

I'm not picking on Ryan though as I imagine this is not his decision alone, but so far I haven't seen a satisfying rationale, across 3 issues and hundreds of comments, as to why this fix is not worth doing. From Ryan it seems that complexity is a problem, and that you should write import specifiers how they're supposed to be during runtime. But I think his reasons pale in front of this argument. I would very much appreciate a response to the linked comment from the TS team.

@tjcrowder
Copy link

@tjcrowder tjcrowder commented May 7, 2021

Please consider reopening this and implementing it.

@antongolub
Copy link

@antongolub antongolub commented May 11, 2021

It seems that proper way fix will not be available soon, so I wrote a small js-script as an alternative to sed-based patcher mentioned above.
To save someone else's time:

@orta
Copy link
Contributor

@orta orta commented May 11, 2021

Given how the ecosystem has changed since 2017. To try summarize the TS position here in mid 2021:

  • TypeScript won't be changing your import specifiers for you (e.g. import {x} from "./x/y/z.ts" to import {x} from "./x/y/z.js" ) it breaks one of the founding guidelines of only erasing types (and TS would not be where it is today if it broke its own design rules) - you can write this in ESM code today and it will work. I do it.

  • An import like import { Component } from '@angular/core' only works today with node-style resolution strategies, but the import map spec for JS will solve that problem, and once it's settled and standardized TypeScript will support that too.

Re: original point about resolving TS in modules in html files- I've been doing some work on that in microsoft/vscode#121517

@cdalexndr
Copy link

@cdalexndr cdalexndr commented Jul 14, 2021

Instead of waiting X years for caniuse import maps to be implemented by all browsers, typescript could implement this itself.

For example, typescript already has something similar for lookup (paths), but it doesn't transform output js import paths:

    "paths": {
      "jquery": ["node_modules/jquery/dist/jquery"]
    }

Adding an import map feature, for example:

    "importMap": {
      "jquery": ["/node_modules/jquery/dist/jquery.js"]
    }

would allow to transform import $ from "jquery" to import $ from "/node_modules/jquery/dist/jquery.js".

@Fistynuts
Copy link

@Fistynuts Fistynuts commented Sep 15, 2021

  • TypeScript won't be changing your import specifiers for you (e.g. import {x} from "./x/y/z.ts" to import {x} from "./x/y/z.js" ) it breaks one of the founding guidelines of only erasing types (and TS would not be where it is today if it broke its own design rules) - you can write this in ESM code today and it will work. I do it.

The TypeScript compiler already rewrites import statements depending on the value of config.compilerOptions.module. The issue here is that when omitting a file extension from an import statement in a source file (as has been standard practice in front-end development for a long time and is prevalent through many third-party packages in that domain), the ES2015, ES6, ES2020 and ESNext settings cause the compiler to produce code that does not work on these target environments, despite tsc stating that there are no errors.

The problem isn't that our TypeScript code is wrong - it passes all compile and linting steps perfectly - it's that the compiler is doing the wrong thing when converting to JavaScript for these target module systems.

@srcspider
Copy link

@srcspider srcspider commented Nov 18, 2021

With regard to this issue, not only does Typescript use non-extension imports in it's own documentation on modules https://www.typescriptlang.org/docs/handbook/module-resolution.html the VS Code editor, which is also made by microsoft needs special settings to even work https://2ality.com/2021/06/typescript-esm-nodejs.html#visual-studio-code

I can understand sticking to a design principle. But I have to ask, WHO is this for? Who is actually benefiting from that design principle? Since it feels like, it's literally for nobody. Especially when nobody is asking to change default behavior, just a switch to append ".js" or ".mjs" when outputting (out of which there are crazier output settings already in the language). To be honest just the confusion aspect of this issue is reason enough to add a simple output


@ anyone stumbling here...

If you're trying to get your node program to work, just replace whenever you would write node with node --es-module-specifier-resolution=node (node v16.13.0). Think of it as the right of passage for the language; can't be mature language with out pointless mandatory flags (it's just like how you see everyone write perl -w, welcome to the club)

If you need it as a shell header, don't forget the -S this is the correct version:
#!/usr/bin/env -S node --es-module-specifier-resolution=node

@JAYD3V
Copy link

@JAYD3V JAYD3V commented Jan 12, 2022

After tediously reading 4+ years worth of comments on this thread I must say SrcSpider ended the thread on a good note for me. The thing that confuses is me, is I don't understand why no one wants to fix this issue. I don't contribute to TS, so I don't know the project all that well, but usually path resolution issues (from what I have seen) are solvable issues. At the worst case a flag could be added to change TS behavior for ES6 Module path resolution right? IDK, maybe I am out of my league, but the way it is as it stands to be extremely annoying. So annoying it makes me wonder if I am just wasting my time working to make sure I write everything in TS. The whole point of compiling to JavaScript (transpiling) is backwards compatibility, portability, and valid code (or valid ES6 JS in this case), right? Its 2022, and no one working on the project seems to even care.

@quantuminformation
Copy link

@quantuminformation quantuminformation commented Jan 12, 2022

@W3Dojo don't miss out part 2

#16577

12 hrs of reading

@jogibear9988
Copy link

@jogibear9988 jogibear9988 commented Jan 14, 2022

I've created a pull request wich wich solve this for raltive imports, see:

#47436

I've added a compiler switch:

   appendModuleExtension

@one-github
Copy link

@one-github one-github commented Jan 14, 2022

I've created a pull request wich wich solve this for raltive imports, see:

#47436

I've added a compiler switch:

   appendModuleExtension

They said it wasn't possible. Along came some guy who didn't know this and just did it. 🥳

@mitsukuri
Copy link

@mitsukuri mitsukuri commented Mar 22, 2022

According to my trusted sources, the rationale behind this stubborn refusal to actually make tsc emit javascript imports with, well, .js extension is as follows:

If implemented, Typescript newcomers will cease writhing in pangs, thus disrupting the steady supply of bad vibes that keep Ctulhu asleep, and it will make Him emerge from R'lyeh, scramble the letters in all occurrences of Microsoft in existence into Coots Firm and scuttle back into the Ancient City growling unspeakable curses; And the management clearly doesn't want that!

Let us all support the stoicism of those behind this unpopular decision and applaud their efforts to redeem the world!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: ES Modules Needs Proposal Suggestion
Projects
None yet
Development

No branches or pull requests