Repository navigation
Feedback on extension resolution #323
Description
Activity
- addedmodules-agendaTo be discussed in a meetingTo be discussed in a meetingsurveysRelates to things where people to you what you don't want to but need to hearRelates to things where people to you what you don't want to but need to hear
on Apr 28, 2019 Is there any information available on why file extension resolution was disabled?
- Reacted by Charles Samborski
UPDATE: I think this is actually the biggest problem: #323 (comment) , #352
The below, while not super pleasant, could be worked around with smarter transpilers relatively easily.
Before, it was possible to write ES modules (or TypeScript modules), and publish both CommonJS and ES modules with a simple module transformation via babel (I've actually started doing this already, so my modules can be treeshaken).
Now we can't write:
import {foo} from './bar'; console.log(foo);
We have to write:
import {foo} from './bar.mjs'; console.log(foo);
Which means that now any transpiler also needs to rewrite the actual import path. I don't think any currently have this functionality, because until now this wasn't an issue.
This is very different from how the ecosystem has worked so far. For example, TypeScript and Webpack both work just fine with the extension-less module resolution. I would guess rollup does too.
I thought the previous design made much more sense.
When I
importsomething, naturally I expect to get an ES module, preferring the.mjsextension, probably falling back on a.jsCommonJS module exported as an ES module.If I
requiresomething, naturally I expect to get a CommonJS module, preferring the.jsextension which can't really be changed for backwards compatibility, probably falling back on loading a.mjsES module exported as a CommonJS module.I get that some people don't like the new file extension, but I don't think it makes sense to create new problems and add ambiguity just to keep it.
Reacted by Jordan Harband, Michaël Zasso, Charles Samborski, Raj Chandra, Emmanuel Meric de Bellefon, Wasuwat Limsuparhat and Taylor Beeston- removedmodules-agendaTo be discussed in a meetingTo be discussed in a meeting
on May 9, 2019 Some feedback in favor of not having extension searching on by default
Twitter poll about compat: 65% of 1295 people favor Browser compat
@jhnns on twitter: https://twitter.com/Jhnnns/status/1003201464716726272
@mhart on twitter: https://twitter.com/hichaelmart/status/1039529625100185600
@brianleroux on twitter: https://twitter.com/brianleroux/status/1039653429272952832Announcement tweet for PR had 150k impressions and no negative feedback
Obviously none of this is scientific... but thought I could offer some balance to the feedback
Again tho, enabling extension resolution does not in any way prevent “browser compat” - this is purely a question of whether you want the preferences of one group (browser compat folks) to oppress another (back compat folks) by making the feature off by default - because on by default causes no damage, but off by default does.
Reacted by Mathias Bynens, Pauan and Brett ZamirIs browser compatibility really even possible? As soon as you import another module, isn't browser compatibility lost?
import {something} from 'some-module'; console.log(something);
In node, that would have to resolve to something in
node_modulesright? You're not expected to writeimport {something} from 'node_modules/some-module/index.mjs';right?Am I missing something here?
134 remaining items
@weswigham I fully understand the reasoning and I'm not by any means wanting to inform TypeScript what to do. I'm simply stating my opinion that seems to be shared by others that this behaviour could be seen as unintuitive and there are possible alternative designs that could be seen to be more intuitive. For example, treating relative specifiers only (as defined in HTML) as permitting extension mapping through configuration could be one mechanism. The reason I'm stating this opinion is only to counter your assertion that Node.js put TypeScript in this situation, when we must look at the fact that TypeScript is making design decisions here, and the blame cannot be directed to Node.js, when making those design decisions without adequate user education is the underlying reason for confusion.
Reacted by PauanWe are refraining from designing something to cover up what the runtime does not do, but users still want. That is very different than designing something in conflict with the runtime. The runtime doesn't support any kind of extension mapping thing, nor does it have a great need to. We do not want to be extending the resolver at compile time.
Reacted by danilo neves cruzWe do not want to be extending the resolver at compile time.
I must still admit I don't understand why. Also, isn't
pathsalready an extension? TypeScript also has to integrate with the Node resolver for type lookups - is that not a resolver extension too?Also, in theory a file mapping is not a resolver extension it is a file system remapping.
Let it be known that people who do not use typescript also dislike the lack of extension resolution.
Reacted by Jordan Harband and danilo neves cruzReacted by Guy Bedford and Wasuwat LimsuparhatI must still admit I don't understand why. Also, isn't paths already an extension?
- It only provides a way to look up type information - it provides
.d.tslocations for.jsfiles, since they (used to) often be held in a folder separate from normal dependencies; moreover this has no effects on paths actually in your code, or on runtime behavior. - It was designed to be used with
amdmodules, notcjsoresm; it's from a time wherebowerwas still in active use. Some people use it nowadays for local monorepo development, because the local package linkages there can be very nonstandard, and the monorepo layout/tool in use may not know how to handle TS types on its own. (Though normally just building them into your packages just works)
TypeScript also has to integrate with the Node resolver for type lookups - is that not a resolver extension too?
No, we reimplement the resolver wholesale; we run in browser runtimes where node's resolver isn't available, but we'll still analyze code intended for
node. That's a small part of why we care so much, we have to maintain a parallel implementation of the thing, and not just the current version of it, but most recent past versions, too (and then provide flags for old/new behavior, depending - that's why we've been trying to wait for all the new stuffexportsto stabilize, so it can all be behind one setting; so you could consider that support "extensions", but it'd feel disingenuous to say caring more about back compat then node itself qualifies as such). The more differences there are between past and current versions, the more confused our users get.- It only provides a way to look up type information - it provides
Putting these two arguments aside, the main argument to consider is that there is a file system mapping happening. The module at
file:///path/to/project/src/file.tsis being mapped into a module atfile:///path/to/project/dist/file.js(orfile.mjs). Because the extension of the file is changing, the linkage between the modules before and after the transform is changing. The invariant that holds in all module systems is that relative paths with a file extension are always supported. Thus a file with a.tsextension can be remapped to a file with a.jsor.mjsextension as part of the file remapping process so long as it is a relative specifier. All of this happens without any resolver extension being necessary and is the same type of mapping performed by standard build tools like RollupJS and esbuild code splitting.All of this happens without any resolver extension being necessary and is the same type of mapping performed by standard build tools like RollupJS and esbuild code splitting.
Hold it there; those tools bundle their own resolver into the bundle. They no longer care about extensions at all - webpack even goes so far as just just use IDs to refer to each module anywhere it's precalculated the links. Point is, we don't bundle a big runtime like that.
I specifically did not mention Webpack - I'm referring to code splitting outputs from RollupJS and esbuild, which only rely on relative specifiers to exact file extensions as the basic primitive (with externals of course).
--es-module-specifier-resolutionflag is unlikely works. Is it okay we put another field inpackage.jsonfor this ?It seems to me that the real failure here wasn't in our inability to reach a consensus on this issue, as it's clear that there are diverging opinions that simply won't be reconciled; our failure was in not coming up with a definitive way to resolve this question. We created the --es-module-specifier-resolution flag with the expectation that user feedback would be clear enough and plentiful enough to tell us which way to go; but I think it's safe to say that that hasn't happened.
Because it is safe to say that nobody use native ESM in node. None of my projects at work or private can be converted. Most of the tooling lack proper support: berry, typescript, webpack, eslint, jest. Support is either partial or would require massive amount of work with little to no benefit. Lots of people have transpillers in theirs toolchain that compile ESM to CommonJS and are by large unaware. That why you do not see a lot of feedback. It is more visible in corresponding tools repositories where people are harassing maintainers.
I think many people like me just wait for things to "Just work". I am observing ESM progress in node for the last 3 years now from initial work in node-eps. This is moving incredibly slowly because of changes in resolution. You cannot expect adoption if a an migration is to change every relative import statement and every index.js. That why sooner or later all tools will be forced to support re-writing paths.
node.js was so late in the ESM party but want to change everybody assumptions about resolution.
Reacted by Jordan Harband and danilo neves cruzI'm sorry, but I don't find any of these arguments persuasive; nor do I find this ongoing debate a productive use of our time. @ljharb at least opened this issue to try to collect feedback from across the web although it's been hijacked; and @rauschma opened a Twitter poll:
Excluding the “just show me the results” folks, that’s 39% for mandatory extensions, 30% for automatic resolution, and 31% that don’t care; from a sample of 294 votes.
I think efforts like this poll are the way forward to try to build support for any potential change, if one is desired.
people who prefer providing extensions can provide them regardless of what the default here is. what i see from that poll is 30% of devs saying they miss a functionality, that's pretty huge.
Reacted by Wesley Wigham, Jordan Harband, danilo neves cruz, Pavel Muset, Santi Albo, Wasuwat Limsuparhat and Abhijeet SinghReacted by Jordan HarbandSeriously; if 30% of your users said they wanted something that didn't affect what the rest of your users could do, I don't know why you wouldn't consider it...
Reacted by Jordan Harband, danilo neves cruz, Pavel Muset and Abhijeet SinghExcluding the “just show me the results” folks, that’s 39% for mandatory extensions, 30% for automatic resolution, and 31% that don’t care; from a sample of 294 votes.
Again, another poll that is just misleading. Workaround only applies to external dependencies. Not only you suggested workaround is bad because lead to fragmentation but do not address core issue.
As result your node.js application written in typescript can end up with something like this
import module form 'main-module-package'; // with single main import cjsSubmodule from 'main-module-cjs/submodule' //cjs modules still search for extension ? import submoduleMapping from 'es-module-package/submodule'; // with subpath mapping import submoduleNoMapping from 'es-module-package/submodule.js'; // without subpath mapping import base form '../base/index.js' // index.js as no longer supported import local from './local-module.js' // relative imports need to have extension
Now in my typescript code I need to know how modules can be imported. For relative imports I need to import using transpilled extension and for existing code I need to refactor or import index.js.
Reacted by Jordan Harband, Pavel Muset and Wasuwat Limsuparhat

It seems like a good idea to capture somewhere the feedback we receive on extension resolution.