Typescript: Support ".mjs" input files

Created on 18 Oct 2018  Â·  72Comments  Â·  Source: microsoft/TypeScript

_From @Sti2nd on October 7, 2018 16:17_

import { timpaniSounds } from "./soundImport.mjs";

In the above example VS code will show all javascript files when writing "./", but not javascript module files. So I didn't see the above file in the list when trying to import it.
Not sure if this is a bug or a feature request.

_Copied from original issue: Microsoft/vscode#60103_

In Discussion Suggestion

Most helpful comment

Hey @weswigham, can you please bump the priority on this issue?

This is currently preventing anyone who uses TypeScript from building a custom service worker bundle with Workbox, since Workbox's source code is published as .mjs (to disambiguate the source modules from it's built, classic scripts, which have to be .js since browsers don't currently support loading modules in workers).

Workbox users can do this with Webpack, they can do it with Rollup, and they can do it with Parcel, but they can't do it with TypeScript, and many of our users are TypeScript users.

From the above discussion it sounds like you wanted to wait to see where node landed on .mjs, but regardless of what node does, the fact is this affecting real users today. Many Workbox users are filing issues for this or complaining about it on our Slack channel or on Twitter.

It's a real blocker for a lot of people, and it's not something we can change on our end since that would break people's existing builds. There are also lots of other modules (browser modules, not just node-specific modules) that are published to npm with .mjs source files, and this is an issue for any TypeScript users wanting to consume those as well.

I don't see any downside to TypeScript recognizing .mjs as a valid JavaScript file, and I think that would be the simplest way to resolve this problem real users are having today.

All 72 comments

I believe that #18442 tracks compiling typescript to mjs. Do we fully support working with mjs files in the editor?

We don't lookup or include .mjs files in any way at present, since it's going to be used as a flag in node that changes module resolution and the exact behavior it switches on (W.R.T. cjs interop) is still TBD, interpreting it would be putting the cart before the horse.

Since the other thread tracks .mjs output, I'll repurpose this one for tracking .mjs input.

I think it comes down to a standardization issue: javascript modules must have a standardized file extension.

It's causing troubles to us, and I believe everyone who are making modules targeting for both browser & node are affected.

In browser, we have to specify file name with extension:
import { SuperModule } from './SuperModule.mjs';

But vscode doesn't recognize .mjs, so we resort to changing extension to .esm.js
import { SuperModule } from './SuperModule.esm.js';

Then node doesn't recognize .esm.js as a module unless we specify a different loader which recognizes the extension:
'node --loader esmloader.js index.js'

This is surely uncomfortable since you have to specify a non-default loader.

Either node has to support .esm.js or vscode has to support .mjs. for short-term solution.

I personally think module javascript files gotta have a different extension since it's treated differently in both browser & node. Browsers require:
<script type='module' src='script.js' />
See, browsers also need to know it's a different type of javascript before loading. I think a different extension is rightful for the purpose.

I prefer .mjs since it's funny (Hello, Michael Jackson), short and right for the purpose.
I'm using .esm.js right now, but it's very cumbersome to write. And i have to specify type='module' in the browser anyway even if i already typed '.eeeessssmmmm.js'

I just hope es7/8 standard specify that javascript modules must have .mjs extension.

I crafted a patch which enables tsserver to recognise, parse and provide type hints for _.mjs_ files.

This might be useful for people who use the _.mjs_ file extensions for JavaScript files with identical module resolution semantics as CommonJS. Note that this patch does not implement any special behaviour with regard to ES modules and might not be spec-compliant. However, editor integrations which allow using custom-built tsserver implementations will greatly increase developer productivity because suddenly everything seems to work.

Thanks for the patch, @robertrossmann!

Btw, I just realized that tsserver recognizes jsx. Then, why not mjs? Both are not ES spec, but at least industry standards.

We don't lookup or include .mjs files in any way at present, since it's going to be used as a flag in node that changes module resolution and the exact behavior it switches on (W.R.T. cjs interop) is still TBD, interpreting it would be putting the cart before the horse.

As of last October, there’s agreement on the “minimal kernel”, which states that:

  • import statements will only support files with an .mjs extension, and will import only ES modules, for now.

    • In a later phase, the intention is to move forward with format databases to map extensions and support multiple use cases.

In other words, there’s no point in waiting. .mjs signals that the file is a JavaScript module, now (in the experimental implementation) and in the future (per the acceptance of the minimal kernel).

is there any stop-gap workaround that we are aware of?

Hey @weswigham, can you please bump the priority on this issue?

This is currently preventing anyone who uses TypeScript from building a custom service worker bundle with Workbox, since Workbox's source code is published as .mjs (to disambiguate the source modules from it's built, classic scripts, which have to be .js since browsers don't currently support loading modules in workers).

Workbox users can do this with Webpack, they can do it with Rollup, and they can do it with Parcel, but they can't do it with TypeScript, and many of our users are TypeScript users.

From the above discussion it sounds like you wanted to wait to see where node landed on .mjs, but regardless of what node does, the fact is this affecting real users today. Many Workbox users are filing issues for this or complaining about it on our Slack channel or on Twitter.

It's a real blocker for a lot of people, and it's not something we can change on our end since that would break people's existing builds. There are also lots of other modules (browser modules, not just node-specific modules) that are published to npm with .mjs source files, and this is an issue for any TypeScript users wanting to consume those as well.

I don't see any downside to TypeScript recognizing .mjs as a valid JavaScript file, and I think that would be the simplest way to resolve this problem real users are having today.

Aside from the actual TS inputs, click-through in VS Code (e.g. ctrl-click on import 'foo.mjs') does not work. If I change it to import 'foo.js', VS Code correctly understands the file and correctly jumps to definition. Since the browser ignores the file extension and only requires the correct MIME-type, I am unable to author my code in .mjs (or whatever extension I would prefer) with VS Code, while it runs just fine in the browser.

Is there any news about this? I'd really enjoy having intellisense working with es modules saved as .mjs :)

Any progress? Given that ECMAScript modules will soon be unflagged on Node.js, it would be great to have first-class support for them in TypeScript:

  • Relative import specifiers ending with .ts are rewritten to a configurable extension (.mjs, .js, …).
  • The same extension is also used when generating files.

So node 13.2 has been released with .mjs support without flag: https://github.com/nodejs/node/releases/tag/v13.2.0

Like others, I've arrived here when trying to get .mjs working in VS Code. If somebody has the time, it would be useful to get some insight into why this is a typescript issue, not a vscode issue — i.e. is it because it's a dependency for processing?

Our language service only loads files of known extensions (barring intervention from the editor), as extensions have a bearing on how module resolution is supposed to work. We do not currently recognize .mjs or any associated resolution rules, ergo, we do not load .mjs.

To anyone using .mjs: How do you stand it? Why do you not use .js and the new type: module field node supports? How do you handle jsx? Do you use .mjsx or something?

Why do you not use .js and the new type: module field node supports?

Sometimes it’s not our choice, I work on some open source projects who choose to use .mjs for whatever reason. I’m guessing now that node supports it without a flag we will only see more of this as time goes by.

Is this something you’re planning to support?

You can read several reasons in https://v8.dev/features/modules#mjs It is also used in the MDN guide on JavaScript modules: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules

Note that the browser does not look at the file extension. It is up to the developers discretion to choose their file extension. Since there are valid reasons for choosing .mjs, I would conclude that the root issue is that TypeScript has a hard-coded list of valid file extensions. Instead, TypeScript should allow any file extension to be provided in an import and properly type-check. In turn, that would fix code navigation in VS Code.

How do you stand it?

This kind of snark is frustrating for people who’d love to use .mjs via TypeScript (see all the thumbs up among the comments).

Why do you not use .js and the new type: module field node supports?

  • Node.js:

    • Less to configure when writing hybrid ES/CJS packages.

    • It’s also easier to distinguish module formats in such cases.

  • Browsers: easier to mix script files and ES module files

How do you handle jsx? Do you use .mjsx or something?

I’m writing all my React in TypeScript. But for plain JavaScript, .mjsx may indeed be useful.


Suggestions:

  • Support for module specifiers with filename extensions would generally be beneficial: ES modules require them and not having them in TS increases the disconnect between compilation source and compilation target.
  • My favorite solution would be (quoting my previous comment):

    • Relative import specifiers ending with .ts are rewritten to a configurable extension (.mjs, .js, .cjs, …).

    • The same extension is also used when generating files.

@TimvdLippe The mdn/v8 rationale was born from the perceived need for the extension in node, or at least that's how the discussions years ago seemed like they went. (Most all of those docs originally used .js) The recent drive in that direction came from a select group of influencers who believed modules in .js files in node would never happen, and wanted (well-meaningly) to encourage the ecosystem to move past it, so browsers could start seeing real world usage of modules. In doing so they unwitting created other major ecosystem issues (like editor support, for example), but such is life.

Instead, TypeScript should allow any file extension to be provided in an import and properly type-check.

Extensions carry semantic meaning to TS - we can't accept arbitrary extensions without choosing what "arbitrary extension" semantically means (json document? declaration file? typescript source? javascript source? commonjs module? esm module? native module?). Your local file system is more like a full browser + webserver stack than just a browser as far as we're concerned - anyone who says "browsers don't care about extensions" is ever so slightly misleading you. Yes, the _browser_ might not read your extensions, but your _webserver_ will, and then _that_ will inform your browser of filetype information (in the form of MIMEs), _usually derived from the extension_. We, in the editor, need to use similar logic, to one degree or another, to that whole stack, not just the browser component. Moreover, our default resolution scheme is meant to imitate _node_, and specifically node's commonjs resolver, not browsers. Assuming that it can handle other resolvers' behaviors as a matter of course is... well, it's a lot.

Complicating all this is this: For truly arbitrary extensions, node of today will load unrecognized extensions as commonjs javascript files (barring custom require.extensions loaders), while a webserver will recognize textual file content (assuming the extension is otherwise unrecognized in its database), report a text mime, and the browser will load it as a module _or_ a script (as text is a potentially valid module and script), depending on what API loaded it. These two competing defaults are irreconcilable for us, so it's better, in our opinion, to simply not recognize unknown extensions. Plus, you probably don't want us doing silly things like trying to read your Makefile as js, or your externs.h. File extensions do carry a _lot_ of meaning. We choose that people be explicit with those meanings, so we don't make (as many) mistakes when checking your code.

@rauschma

This kind of snark is frustrating

I moreso meant "how do you stand it" in the context of "how do you handle all the extra configuration all of your tooling needs to make that work, if it can be configured at all". I, personally, would have prioritized by editor experience over the asks of any other tooling or convention (especially when those tools ostensibly handle any extension), so wouldn't have even considered using a different extension until it was supported in the editor. What was so compelling about the features changing your extensions gave you? What was unlocked by it?

ES modules require them and not having them in TS increases the disconnect between compilation source and compilation target.

We allow you to say foo/bar.js when referring to foo/bar.ts already for pretty much this reason (and is sort-of why moduleResolution: classic mode still exists, which doesn't support node_modules, which, as you know, browsers also don't support). New extensions or no, that won't change...

Less to configure when writing hybrid ES/CJS packages.

Our leaning, right now (as of last Friday's design meeting), is _not to support_ checking this mode of operation, because of the incredible complexity in it (with respect to new extensions to support and hybrid emit modes to rig up). (So we'll only support loading either all esm-targeting-modules or all cjs-targeting-modules, as we do today.) You'll want to open a new issue asking for support of hybrid node-cjs and node-esm projects~

I moreso meant "how do you stand it" in the context of "how do you handle all the extra configuration all of your tooling needs to make that work, if it can be configured at all".

OK, got it.

I, personally, would have prioritized by editor experience over the asks of any other tooling or convention (especially when those tools ostensibly handle any extension), so wouldn't have even considered using a different extension until it was supported in the editor.

At the moment I can’t really use .mjs – given that neither TS nor VSCode support it. But I’d love to be able to use it in the future.

What was so compelling about the features changing your extensions gave you? What was unlocked by it?

See answer in my previous comment. I don’t know what else to tell you.

We allow you to say foo/bar.js

Ah, good. I think I got confused by VS Code’s error message for .mjs.

Our leaning, right now (as of last Friday's design meeting), is not to support checking this mode of operation, because of the incredible complexity in it (with respect to new extensions to support and hybrid emit modes to rig up). (So we'll only support loading either all esm-targeting-modules or all cjs-targeting-modules, as we do today.) You'll want to open a new issue asking for support of hybrid node-cjs and node-esm projects~

That’s OK, I think. I’d handle these cases via two different configurations and only create the CommonJS files when uploading the Node.js package. (I don’t know if that’s going to work, but that’s what I would try to do.)

AFAICT, optionally writing files with .mjs and otherwise treating .mjs as if it were ESM-based .js is all that is needed. But I may be overlooking something.

People are talking about .mjs like it's some sort of arbitrary preference; in many situations, particularly relating to native ESM that has now shipped in Node.js it's not. The package type: 'module' feature to get .js extensions to load as ESM is a bit of a hack to appease a certain crowd, it has real downsides (.js files containing CJS config for dev tools will be mistaken for ESM) and I doubt it will be used universally.

Having actually used CJS .js and ESM .mjs files side by side by side in real projects and packages for a while now, the distinction is great. You can lint each separately with their correct mode, e.g. banning require() in ESM files and banning import in CJS:

https://github.com/jaydenseric/eslint-config-env/blob/cd3f596d4571c0e4f89cd7ec7e13c42b3ecdcbca/index.js#L244

Having actually used CJS .js and ESM .mjs files side by side by side in real projects and packages for a while now, the distinction is great.

I completely agree.

If you've ever tried actually building a web application that uses real ES modules (along with a <script nomodule> fallback) it becomes immediately clear how helpful the distinction can be. Not only can you tell immediately by looking at a filename whether it's a module script or a classic script, but you can also configure your tooling and infrastructure to do that as well:

For example:

  • Serving different cache-control headers based on .js vs .mjs
  • Configuring your service worker to handle .js and .mjs files differently.

It's also the case that many web APIs don't yet support native modules (e.g. Service Worker), so until that changes, web developers will have to deploy JavaScript in module and non-module formats—and being able to easily differentiate them by filename is _extremely_ useful (as many people in this thread have said...over and over again).

@weswigham you laid out the clear argument above that extensions carry semantic meaning:

Extensions carry semantic meaning to TS - we can't accept arbitrary extensions without choosing what "arbitrary extension" semantically means (json document? declaration file? typescript source? javascript source? commonjs module? esm module? native module?). Your local file system is more like a full browser + webserver stack than just a browser as far as we're concerned - anyone who says "browsers don't care about extensions" is ever so slightly misleading you. Yes, the _browser_ might not read your extensions, but your _webserver_ will, and then _that_ will inform your browser of filetype information (in the form of MIMEs), _usually derived from the extension_

But then your conclusion seems to be that the semantic meaning carried by .mjs is unimportant (and the rationale seems to be based on folk's personal feelings toward .mjs).

I hope we can prevent this issue from becoming a war of ideologies or personal preferences. If node applies semantic meaning to .mjs files, then I don't see an argument against TS supporting it.

But then your conclusion seems to be that the semantic meaning carried by .mjs is unimportant (and the rationale seems to be based on folk's personal feelings toward .mjs).

My conclusion isn't that .mjs is meaningless - it's that it's implied semantic meaning doesn't mesh well with what we already accept, and we're moving cautiously regarding it. Say we added the ability to read in both .mjs and .js files as input... Whap happens to each? Do we have to transpile both? Do they behave differently? If they don't, then why have both? Is .js only for commonjs modules? It can't be, since there exists esm written with a .js extension. Is .mjs only for es modules? It can't be, because there exists esm written that's intended to be compiled to cjs with a js extension in the output. (Rather than left as-is). It's not synonymous with .js (where anything goes provided it's some kind of JS and intent must be encoded elsewhere) and its meaning is overloaded already - it has semantic baggage. We need to shake out which resolver modes it matters in and what workflows we want to support, to have a consistent story in developing both modules for transpilation to cjs, and modules that _arent_ transpiled. Plus the potentially common scenario of wanting both kinds of output (esm and cjs) from a single input. Patching the existing node module resolution to support mjs amounts to supporting a mixed-mode world (akin to node itself), where some inputs are es modules, and some as cjs modules, and they are handled differently. That's something that we have a rough sketch of, if daunting to us in scope. (Since to maintain full expressivity across both extensions, we'd need to add support and handling for way more than just one new extension). Moreover, how do we encode all these preferences into declaration output? Do we alter declaration file resolution to support more chains of extensions? (foo.mts.d.ts) Do we add a source pragma? Do we hope and pray that there aren't name conflicts and just continue dropping extensions and erasing input formats as we do today? (Spoiler: in the mixed mode type resolver, we can't, as if we don't, then we can't get accurate resolution failures involving declaration files) And then on the input side there's the question of how jsx extensions should layer that adds yet more complexity... Ungh.

Plus, like, lemme ask you this. A question driving at the heart if the original request: If you have an input.mts file, and you have a import "./input.mjs" in that file, what should happen when you compile targeting cjs modules? If we output .mjs, node'll refuse to load it as cjs (because that extension is esm), however if we output as .js, the input won't work (and we categorically _will not_ rewrite your imports). Should we error on the import? This implies that resolver behavior needs to be tied to target module format, assuming it affects extension, and not _just_ the moduleResolution setting. Do note that with implicit extensions, this was much cleaner for us - we could say "just write './input'" and brush the problem under the rug; but without that ability, solving those problems is much more important.

There are answers to these questions and designs that could work (the questions are mostly rhetorical), but the complexity of "how do we emit modules" grows very quickly, and has been growing yet more, - which is why we're very much trying to see just how little we can get by with, for now. That's why I'm asking. What, _exactly_ is forcing everyone to use the mjs extension, what does it get them, and how does it fit into their workflows. What's it's real common use, in practice, rather than in theory. Where _can't_ TS's current model be adapted to work, or the project adapted to work with TS.

Plus, since all the new resolver features aren't even in node yet - if/when the currently flagged features unflag (which will probably be before January's end), we'll need to update much related to this yet again. That we're moving slowly here is very intentional - what we have today can work in many cases (or can be made to work if your project structure is flexible), and is compatible with what we've already been doing (ofc). We have the ability to muck things up quite a bit with the wrong design. We've seen plenty of patches/PRs that just make mjs extensions an output option or an input option, but none go so far as to answer semantic questions like these on the implications of the change.

Plus plus, it's the holidays now, stuff's slower~ Happy (impending) Thanksgiving to everyone in the USA.

TL;DR: If the fact that I keep replying to people here doesn't clue everyone in: we're looking at it - despite the surface level simplicity of the requests, there's actually a lot going on.

I would like to provide some clues about _What, exactly is forcing everyone to use the mjs extension_ question.

I believe that people watching this thread can be categorised into several groups:

  • TypeScript users hoping to _consume_ .mjs files in their .ts files
  • TypeScript users hoping to _compile/target_ an ESM output (.mjs) out of their .ts files
  • VS Code users working on Node.js projects using .mjs files to use ES modules in Node.js today, no TypeScript compilation involved

Personally I belong into that last group. All I really want is for VS Code to get ES modules supported at the LSP level, which, I think, means to:

  • recognise that an .mjs file is an ES module and treat it accordingly
  • support Node's current module resolution mechanism for ES modules and scream loudly if an _import_ path cannot be resolved using that mechanism
  • parse the module and provide type hints when used in other modules

Full stop after that (barring I did not forget something important). I understand that in the long term some TypeScript users might want to also emit ESM files or even consume ES modules in their TS files but for me that's a stretch goal at this time given the complexity involved. Right now, however, VS Code lacks support for a major Node.js feature and its cornerstone feature, the intellisense features, do not work at all for anything with .mjs extension in its filename. Personally, getting this supported at the LSP level so non-TS users can start using ES modules in VS Code would be a huge win for the community.

My current usage for ES modules is that I write primarily .mjs files and compile them down to commonJS targets with Babel. The problem with that setup is that the ES modules I write implicitly use CommonJS module resolution mechanism, not the Node's ESM resolution, but I am willing to ditch that and refactor the hell out of it as soon as VS Code gets ESM support into it. Other tools will follow suit soon, I am sure.

My current usage for ES modules is that I write primarily .mjs files and compile them down to commonJS targets with Babel.

If you're compiling to commonjs, why would you not use the commonjs resolver? We're not going to intervene at runtime and provide a mapping layer between the two resolvers, so you're relying on one resolver being a strict subset of the other - moreover, what benefit do you gain by having your input follow the subset resolver if you're always compiling to a target using the commonjs resolver?

@weswigham I would like to ditch the Babel compilation part altogether now that Node.js supports ES modules natively. I only mentioned my current usage to explain my workflow at this time, not the intended workflow in the future.

@weswigham As @robertrossmann pointed out, this issue is about the issue in VS Code where code traversal of JavaScript does not work. The points you mentioned about TypeScript itself are valid, but these are tracked in #18442

Admittedly, we should discuss issues with VS Code code traversal in VS Code, but this issue was explicitly moved to the TypeScript repository. The fact that VS Code uses TypeScript for its intellisense is an implementation detail to the end-user imo.

I want the following to work in VS Code (note that I don't use TypeScript at all):

index.js

import { Foo } from './foo.js';
import { Bar } from './bar.mjs';

foo.js

export const Foo = 5;

bar.mjs

export const Bar = 42;

If you ctrl + hover over the imports in index.js, you can see that VS Code detects that foo.js is indeed an exporting module and click-through works as expected. However, the same is not true for bar.mjs. VS Code does not allow click-through, does not provide intellisense and general type warnings. (Note that I wrote the file bar.mjs here, but it could be a third_party project that I want to use that ships .mjs and thus I have no control over its file extension choices)

I understand that the implications for the TypeScript language semantics result in a complex solution that requires a lot of maintenance work. If that is the case, maybe VS Code should use a different LSP backend for providing JavaScript compatibility, as "TypeScript as a language" then creates unfortunate limitations on "TypeScript as a LSP backend for intellisense of JavaScript projects".

Let's not forget that this issue has been created as an VS Code issue.

For example I'm not interested in compiling .mjs files to TypeScript, I just would like intellisense to work properly with these

I see this issue was mentioned in the meeting notes of 22 November, which spurred additional discussion in this issue on November 27th.

Could this issue be included in a new design meeting, but then focused on the "TypeScript as a language server requires an allowlist of file extensions, which is problematic for VS Code as an editor for generic JavaScript files loaded in a browser, where file extensions don't matter"? Any update on the status of this issue would be greatly appreciated, thanks!

Just a small note on the use of the .mjs extension in codebases.

Although the file extension might not have any bearing on how the file is served to or interpreted by the browser, it is important when managing a codebase which spans client and server code. Here's a note from Google on the subject:

You may have noticed we’re using the .mjs file extension for modules. On the Web, the file extension doesn’t really matter, as long as the file is served with the JavaScript MIME type text/javascript. The browser knows it’s a module because of the type attribute on the script element.

Still, we recommend using the .mjs extension for modules, for two reasons:

  1. During development, the .mjs extension makes it crystal clear to you and anyone else looking at your project that the file is a module as opposed to a classic script. (It’s not always possible to tell just by looking at the code.) As mentioned before, modules are treated differently than classic scripts, so the difference is hugely important!
  2. It ensures that your file is parsed as a module by runtimes such as Node.js and d8, and build tools such as Babel. While these environments and tools each have proprietary ways via configuration to interpret files with other extensions as modules, the .mjs extension is the cross-compatible way to ensure that files are treated as modules.

Just thought it worth raising, as the use of .mjs isn't just a cosmetic preference.

Please note that there are many people at Google who disagree with that note. .mjs is almost never necessary, and definitely causes problems with lots of tools. I personally strongly recommend against it.

I believe that people watching this thread can be categorised into several groups:

  • TypeScript users hoping to consume .mjs files in their .ts files

I'm in this camp. Is there a way to import { Foo } from './Foo.mjs' in a .ts file? That would allow gradually migrating ECMAScript Modules code to TypeScript.

@justinfagnani i promote exactly the opsit you at google should switch now everything to MJS! and i would even prefer if you switch to mjs + jsdocs and check that with typescript and not ts it self but we will all see where we end up i think the force of @guybedford me and others who are pushing the new standards will win at the end because its logic! Sure thanks to some oldschool people we will need much patience but thats a none blocker at all with codemods and existing Tool Chains lol its not so much overhead to compile away not needed tooling like ts.

We have now a shared standard module system and it will get used as its simply working. No Tooling that exists will block that.

I remember that you want to try to force every one into tooling and go away from standards this can't be the Future.

Future

I can see a lot of guides about how to fork a ts projects and maintain that fork automated :).
the performance of testing and the expirence as also iteration overall speed is simply better with using mjs directly in a project and no additional build chain for that. it ends up in directly import able code in the browser and other environments.

Please note that there are many people at Google who disagree with that note. .mjs is almost never necessary, and definitely causes problems with lots of tools. I personally strongly recommend against it.

Thanks for the input @justinfagnani — it's useful to get a more diverse range of perspectives on these sorts of things.

I think where this all becomes frustrating is when some tools and technologies are promoting a progression to more cohesive working practices, but other tools put a block on their adoption. I wouldn't want to pick sides, it's just an observation from the frontlines, and as a seasoned developer.

A real-world example: I have some JS modules, using a .js extension so that they play nicely with VS Code linting, refactoring, etc. But when I come to write some benchmarking tests, which will run through Node, I have to jump through hoops, because Node expects my modules to have an .mjs extension for modules to be recognised. I don't really want to run them through a preprocessor, because I want to test native ECMAScript syntax.

As a developer, I'm left to make a decision: use .mjs but lose the convenience of my dev tool; or use .js but put a hacky config, a preprocessor or some polyfills in place to make Node play nicely. As a selfish, bratty developer: I just want VS Code to play nicely with .mjs with zero config on my part.

Really just throwing this out there as another niche perspective. Hope it's useful in some way.

@philp Have you tried "type": "module"? https://nodejs.org/api/esm.html#esm_package_json_type_field

But I agree that being able to use .mjs is important when working with Node.js. Especially in projects that have a mix of JS script files (.js), CommonJS files (.cjs), and ES modules (.mjs).

I've decided to write short comment in top of every .js file to describe whether it's CommonJs or ES module, so for quick peeks I don't have to analyse syntax first.

It feels odd please don't judge me

@philp for you i have a extra solution put "type": "module" into your package.json and node will handle .js as .mjs maybe it helps you this also works with sub directorys you can put a package.json with that content into all dirs with .js files that should be .mjs

the current loader implamentation reads always package.json relativ to the imported file

@piotr-cz usage of top level import or export signals esm already really well

and about the .mjs .js extensions can any one in here tell me a reason why its a good idea to have 2 diffrent processed languages share the same extension ?

why is there a .ts ??? why did they not share the .js extension when that is such a great idea?

@frank-dspeed
yes unless there is no import/ require in the file just export/ module.exports somewhere in the middle or at the bottom

@rauschma Yes, that type config is something I've tried. Unfortunately, I've been unsuccessful in getting it to work when there are dependencies which implement CommonJS. It's frustrating, and I've kind of given up.

At the risk of appearing to be snarky: lots of chat here about the rights and wrongs of .mjs since Oct `18, but it feels like nothing much will really happen any time soon. The use of transpilation has caused a few difficult knots in the soup of module standards. Would love to find time to contribute, but looking at 16+ month long "quickfix" PRs does not encourage me.

That sounds really sour, but I honestly want to find a way to contribute, rather than discuss the pros and cons of a (seemingly?) established standard.

@philp I think that with the adoption of esm in node, more and more people will find out about this problem and build pressure to have this issue resolved.

If you submit solution, you'll get support at least from most developers in this issue.

@piotr-cz i do at present a fork of typescript that emits and handels .mjs as js and it works for my usecase as my main goal is to fix typescript for vscode and use it only for typechecking of jsdoc typedef.

later goals are production of codemods to follow the development here and produce pure .mjs builds of typescript it self. that will get typedef via jsdoc and so be more fast fix and iter able.

Any updates on this issue?

@wxs77577 a update is not even planned i think they want to block transition for the vscode users that are using typescript + jsdoc comments for type interfaces checked with typescript because that will stop usage of the .ts extension.

i created an example of the current issue so it can be easier replicated.

https://github.com/frank-dspeed/example-typechecking-mjs

I just want to ctrl-click on the filename in 'import from "./a.mjs"' and it's not working. Is this a embrace, extend, exterminate issue to bring people over to typescript?

I just want to ctrl-space on an identifier imported from a .mjs file and have intellisense to auto-complete...

Chrome DevTools just ran into this again. Acorn (https://github.com/acornjs/acorn) ships a acorn.mjs distribution, but we can't import it in TS. I am not sure what option we have, other than to choose between Acorn or TS with regards to ES modules.

@TimvdLippe the issue with Acorn might be something else.

Importing it works fine if with "module": "commonjs" in my tsconfig, and breaks with "module": "esnext". But when I go into the acorn package and remove the legacy modules, rename the .mjs files to .js, and set "module": "dist/acorn.js",, tsc _still_ fails. So it appears that the .mjs extension might not be the cause in this case.

@justinfagnani To clarify, we are using Acorn in the browser. Therefore we are not transpiling to commonjs, but ship ES modules instead. Your other points might still be valid. I haven't started renaming the files or modifying the package yet, for legal implications. If that is required, I would have to ask for legal advice on that, I suppose.

@TimvdLippe I was only renaming files to investigate the issue, not as a suggested workaround. My point is that renaming the files _didn't_ work, so the issue is probably not the .mjs extension.

Looks like acorn ships its own index.d.ts file - meaning we'd never even look at any underlying source files anyway, assuming you're importing via package name, and not, like, directly reaching into the bowels of the package to directly import the distributed mjs file yourself (in which case.... maybe ask that they add a acorn.mjs.d.ts (which does work) to actually provide types for such a usage?).

We import by URL, not by name. I was not aware of the acorn.mjs.d.ts solution, so I will try that out tomorrow!

BTW, another use-case to include in this issue is generating .d.ts files from existing code. Currently there doesn't seem to be a way to do this other than by renaming *.mjs to *.js.

I finally had time to test it out and I can confirm that the acorn.mjs.d.ts file worked: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2218015

It would be great if documentation could be updated to explicitly call out this possibility, as I reckon other developers will run into similar issues.

@TimvdLippe it works only partial you still can not jump to the implementation so when you click on your module you always get the d.ts file which is useless

it works only partial you still can not jump to the implementation so when you click on your module you always get the d.ts file which is useless

You'd need a declaration map shipped with the .d.ts for that; which you're _probably_ not going to get for a hand-authored .d.ts file.

@frank-dspeed With the .mjs.d.ts file in place, Intellisense and TypeScript checking in VS Code is working. That is iiuc the issue described in OP.

However, given the large amount of responses to this issue and the general confusion/lack of knowledge about .mjs.d.ts, I think additional documentation is required to explain how to make TS/VS Code work with .mjs. For DevTools at least, we were able to make it work.

For those following along about the .mjs.d.ts: we had a bit of a back-and-forth in Acorn on how to actually making this work without having the need to fully duplicate all types: https://github.com/acornjs/acorn/pull/954 It turns out that this was sufficient:

acorn.mjs.d.ts:

import * as acorn from "./acorn";
export = acorn;

This now works for ESM builds, even though adorn.d.ts is typed as commonJS module. It also doesn't require any change to the tsconfig (which we had a couple of attempts of that did require tsconfig changes).

I am a JavaScript developer who does not use TypeScript, (and is not interested in using TypeScript for front-end code. Sorry).

I do however love VS Code. ❤️

But it really bugs me that there is no import completion for .mjs files.

I see that the all GitHub issues/requests for VS Code to support import completion for .mjs files, get folded into the TypeScript project, and not addressed as part of VS Code itself. I suspect there's a good reason for that, however...

What I would really like to know is whether it's the TypeScript community's intention to only support TypeScript compatible projects, or does it plan to support also Vanilla Web Development too?

As time goes on, it feels as if VS Code is really only focused on TypeScript. It's perfectly fine to do one thing well, but it would be valuable to know what the long term intentions are.

If the TypeScript community are not interested in supporting Vanilla Web Development, please make it clear so that we can find or build alternative tools.

Thanks.

@F1LT3R I have the exact same feeling as you. I love VSCode from the bottom of my heart, but now and then I stumble upon a TS problem that I think should be fixed with JS in mind but only TS workarounds get proposed instead.

An unrelated (but similar) TS/JS problem in VSCode I don't have a fix after 4 years is https://github.com/microsoft/TypeScript/issues/5863#issuecomment-445235236

@F1LT3R @NemoStein Can't agree more with you both, it totally boggles me that VSCode (❤️) that sells itself as an "all language IDE" focuses only on TS and is keeping it's users to either retain code updates to stay compatible, or to work on a broken IDE on such basic features ..

I just updates my entire codebase to .mjs & node 14.4 and now have to revert to using esm for compatibility issues

@hmenzagh do not worry it will get even more bad in future microsoft did buy github & npm so the NodeJS Ecosystem is dead already

As an update, .mjs support for ESM is shipping both in Node.js 12 and 14 unflagged - still experimental, but unflagged. It would be truly wonderful to see TS support it ❤️

@bnb you did not read that issue right? There are Internal Blockers maybe typescript will never support it

@mathiasbynens is the person who knows the specs best from all of us here i think the following is blocking TypeScript now.

Blockers

  • TL import now allow to import cjs? I think yes. So typescript needs to tell acron how to parse the file.
  • TL await now allows usage of import() that also returns CJS or ESM So typescipt needs to tell acron how to parse the file.

The Patch is the best result we could get the last 3 years. its from @robertrossmann and i did similar stuff to try to get the current version working and it took me over 1 week dedicated work to come to the conclusion that this needs a much bigger project team.

I crafted a patch which enables tsserver to recognise, parse and provide type hints for _.mjs_ files.

This might be useful for people who use the _.mjs_ file extensions for JavaScript files with identical module resolution semantics as CommonJS. Note that this patch does not implement any special behaviour with regard to ES modules and might not be spec-compliant. However, editor integrations which allow using custom-built tsserver implementations will greatly increase developer productivity because suddenly everything seems to work.

@bnb maybe you as a intern of Microsoft can make some one who cares aware of the situation.

I’ve opened PR https://github.com/microsoft/TypeScript/pull/39840 to implement this.

If some one does want to test #40068 i have made a patched version ready to use https://github.com/stealify/typescript

npm i -g vscode-typescript@https://github.com/stealify/typescript
# Getting global path
npm root -g

Hit f1 on keyboard open user settings and set

"typescript.tsdk": "{your_global_npm_path}/vscode-typescript/lib"

restart is not needed.

@weswigham I did now Deep Looked into everything and even examined the complet typescript source it took me Weeks

  • TypeScript should support .cjs and .mjs it should handle them as it would handle.js no Diffrence!
  • TypeScript should not rewrite the require or import statments in fact that is the design decision that i agree with

TypeScript already is able to use .js as ESModule and as CJS Module so this change will not stop anything from working while it would expose all the IntelliSense features inside the editor and compiler…

It makes no diffrence to add that 2 extra file types we simply preserv file attributes as with JSX do you agree? Then i prepare a PR.

we can optional also Introduce AllowCjs, AllowMjs to turn that interop off if some one has trouble with it but i do not see any a Module already Represents a fileName without extension and sure this can lead to None Working builds but so can the usage of .js already.

Overall it is a win to process the Two additional Extensions.

@weswigham i have prepared a fully working version of the described scenario

https://github.com/microsoft/TypeScript/pull/40068

Was this page helpful?
0 / 5 - 0 ratings

Related issues

uber5001 picture uber5001  Â·  3Comments

blendsdk picture blendsdk  Â·  3Comments

wmaurer picture wmaurer  Â·  3Comments

Roam-Cooper picture Roam-Cooper  Â·  3Comments

fwanicka picture fwanicka  Â·  3Comments