-
Notifications
You must be signed in to change notification settings - Fork 44
Proposal: make .js extension default for es6 imports #444
Comments
In a browser, extensions mean nothing. In node, The only thing that unambiguously means ESM is However, the other issue is, the importer has no say over the format of the module. Only the module author can possibly know how the module should be parsed, so any decision made about that needs to come from the module's code, its filename, or its closest package.json - never from "whether it's imported or required", for example. |
One of the other reasons to require explicit import sites is to make sure the tree is statically analyzable. Allowing a single dynamic entry point, such as only import maps are already shipping in chrome behind a flag! With export maps + explicit specifiers we can be guaranteed that we can generate an import map for an ESM tree and it will be import map compatible. If individual specifiers can be dynamic potentially a extensionaless file or a file with a |
It has when it comes to defaults. You expect to import .ts from .ts files. .css from .css files, .scss from .scss files etc, .rb from .rb files and .java from .java files. The same way file imported from ES6 module is by default expected to be ES6 and one imported from CJS module is expected to be CJS. This is not about what imported module is, but what it is expected to be and be found.
import-map supports "Packages with trailing slashes" which is compatible with this idea. Could you explain where exactly is the issue generating statically an import-map if .js is default extension? |
An alternative solution might be to allow some pattern matching in the exports map: {
"exports": {
"regex:(\/.*)(\.js)?": "/$1.js"
}
} |
One of the design constraints of |
I don't think this would require regexps, something simple like this would suffice:
Which automatically adds
This looks like by-design flaw that could be solved by something as simple as above option. |
@sheerun keep in mind that import maps would only support adding file extension resolution for exported entry points. This would not support relative imports e.g. I'm not a big fan of attempting to shoe horn this into import maps for a couple reasons
While I appreciate your attempts to solve this I don't think this proposed solution is going to work |
It least I can try :) I appreciate the feedback because I don't see the whole picture. Even if group won't accept this into import-maps or node this discussion will be good reference
Indeed it seems that adding
I think defining additional behavior, like default extension that covers both relative imports and "trailing slashes" imports, is not that big deal.
This covers as much of the problem as possible given the "
For me it seems adding default extension is as much complex as "trailing slashes" imports which is already a part of import-maps
This is still O(1) |
On extra note I don't argue for extension-less import because simply because I and others don't like adding |
I think there's a citation missing that those 70k modules are actually valid ESM. It would imply that none of them uses things like |
@jkrems Might be true, I won't argue about that EDIT: the same way is hard to argue about uncounted number of applications that already use extension-less relative imports or extension-less trailing slash imports. Without ability to configure default extension they need to be converted and create mess in internal pull-requests |
If a project is serious about using native modules (beyond from bundler sugar), I assume that they’ll have to refactor to some degree. That’s one of the great things about .mjs: It allows to go through the code and migrate files incrementally. Add missing file extensions, remove bundler/compiler artifacts, and then mark the files that are meant to be actual ESM with .mjs. My base assumption is that any file with .js isn’t an actual valid ESM file. |
I believe this is false assumption and depends on project. Migrating by renaming files one-by-one to .mjs is one way to migrate. The other way is to be glad that practically no changes in code are necessary to support node's ES modules and move on. |
I've done some analysis on product code and also have some passing awareness of use of import syntax in OSS projects. The number of non-trivial projects that used The world is full of code like this: https://github.com/zeit/now/blob/master/packages/now-next/src/dev-server.ts#L14 ( |
I hope this won't be a reason to reject this proposal |
You could also say the same about a project using I think pretty soon migrating won’t be such a chore. There will be tools to add extensions to specifiers, and to lint ESM files looking for references to |
I'm aware of the role of
This proposal allows for third possibility that me and many others would prefer:
I hope you understand |
If I’m understanding your motivations correctly, ultimately you want a very similar thing to The canonical issue for extension resolution is #268. For all the reasons listed there, many members of this group prefer explicit extensions by default ( I think there is a consensus for allowing custom loaders to define whatever resolution algorithms people want. Once the loaders API is more mature, you should be able to write a loader that implements your proposal. |
Correct. It is so public and private ES6 packages that are already mostly use valid imports/exports can migrate by just adding single line in package.json, instead of renaming all files and changing each import . Indeed it really feels like uphill battle but I hope I give at least some valid arguments to reconsider, or maybe some extra perspective from one more package creator. It's puzzling that node group opposes so strongly to extension-less imports which every other language supports. |
As far as I’m aware, no other JavaScript runtime (browsers, deno, v8, etc.) supports extensionless import specifiers. It’s a little more complicated in the case of browsers because specifier resolution is handled by the server and servers can be configured in limitless ways, but in general “ Yes this adds short-term pain for migrating apps written using Node-style resolution, but we need to make decisions based on what’s best in the long term. |
I mean literally other programming languages |
Just to clarify: When adding
I think there's two important pieces here:
|
Can you name another language that has multiple competing formats as well
as various runtimes to worry about compat?
Also afaik C / C++ requires full path. So I think it might be better to
make explicit examples rather then broad staments. Go / rust don't require
full specifiers but have a completely different model for modules.
…On Mon, Nov 25, 2019, 6:15 PM Adam Stankiewicz ***@***.***> wrote:
I mean literally other programming languages
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#444?email_source=notifications&email_token=AADZYVZRCYAYZJYITVU7LFLQVRMB5A5CNFSM4JQOM3AKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEFEEIRA#issuecomment-558384196>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AADZYV4ZXXW3QLGY3NQBYATQVRMB5ANCNFSM4JQOM3AA>
.
|
Indeed I should say most other languages, not every other language. Allowing extension-less imports doesn't prevent imports with extensions that are useful in JS. Extension-less import from given language strongly suggests that imported thing should be the same format, that's why I'm suggesting making I could argue further but I think there's no point at this point. I feel like you're nitpicking me. I've already given most important reasons and proposed a way to solve the most important stumbling block at WICG/import-maps#194 (which was promptly closed before discussion finished) |
Is there any discussion of why this cannot be considered? Is it purely to deter package authors from using the |
Browsers do not require extensions in script src. Browsers will happily load and execute javascript from something like <script src="./test"></script> IMO writing typescript and adding js to all your imports is not sensible regardless of what actual standard says. |
As we've already unflagged the new implementation, I think this can be closed. The short answer is that we can't change how Node treats |
@chyzwar I think what we are all struggling with (I did too) was tooling that did not align with the spec (or tried to influence/coerce it but failed)! To be honest, it took me a lot of time to understand all the moving parts myself, but as I did, I came to appreciate there is pros/cons on both sides of the debate, but being conforming by default is a must… sounds good? |
Most of the tooling I use still do not support type="module" and exports field. Typescript, jest, webpack and eslint would simply break. Existing code written in ESM (babel, typescript) is also not compatible without extensive changes. My biggest problem is that package with type="module" is not consistent. When importing from entry points defined with exports you do not need extension but everything else requires an extension. Extensions do not provide any benefit when using webpack or when building node.js application. Adding .js in your typescript code is just awful leaky abstraction. I am observing ESM saga for the last three years and choices made here is just mind-bending. Node would happily break an ESM spec for trivial things like self-reference specifiers but it is adamant about extensions even when that break existing idiomatic node.js code. |
Self-reference specifiers in no way “break” the spec; the spec explicitly imposes no constraints on specifiers. There was no existing idiomatic node ESM code prior to node 13.3, the first version ESM was unflagged. |
Since this is not supported by a browser it would make node modules incompatible with browser. Browser compatibility was raised as an argument against extensionless imports. It would make sense in what terms you define the following goals:
Otherwise, the whole discussion is just moving goalposts.
This is not what idiomatic means. It means following existing conventions. For the last 4 years, most JS developers would write code transpired with babel and typescript using ESM with node resolution algorithm. There is probably more JS code using ESM than CommonJS. Most of us assume extensionless imports and index.js. We all have this dream that one day ESM modules would be universally supported and we just switch some config flags. What you guys are doing is leading to a similar situation as Python 2 -> Python 3. |
@chyzwar bare specifiers (like There was no existing convention for native ESM prior to node 13.3; the convention prior was to use babel or typescript. You can continue using those with no change whatsoever; but if you want to use native ESM, then new conventions apply. |
This issue is still the top entry on Google That means that it's still a hot discussion. For me, it's enough to ask whether it's possible to reopen it. Actually, after reading the discussion it wasn't clear for me why the issue was closed. Thank you! |
Being the top entry on google for a very very specific five-term search query isn't particularly notable. |
I waited so long to finally utilize Error [ERR_MODULE_NOT_FOUND]: Cannot find module '…/my-module' imported from …/another-module
Did you mean to import ./my-module.js? I guess you have a point. And I guess I'm stuck with |
@dionysiusmarquis neither the browser nor node will support a relative ESM import without including the full path for the file. If you want to use something shorter you will have to look at package.exports and import maps. |
I‘m currently trying to implement |
@dionysiusmarquis — hold up, wait a minute
are you saying there are packages out there which are using |
IIRC the way that snowpack solves the issue of libraries that are almost-ESM, is to bundle them and/or use prebundled versions. Would this be an option here? The theory is that library code changes less often, so bundling/transforming the code isn't a huge hit during development. That way, you can still run the application code directly but non-ESM dependencies (e.g. ones without valid relative imports) are compiled to ESM. |
@chase-moskal Yes, you are right. That doesn't make sense. |
@dionysiusmarquis thank you for the context. I notice you replaced You could attempt to run node with the I think that Jan's suggestion might be a good place to start... caching dependencies on build and not rebuilding them when the application changes. |
If it adds to the discussion at all, this took me nearly 2 hours to debug an issue with testing an npm package using ESM with import mappings. It is exhausting figuring out what particular flags, Instead of impossibly long flags, it'd be really cool to have an option, similar to |
by standard, ESM doesn't guess any name, so the extension indicates explicitly the file you are going to import, instead of checking if it's directory, or a file itself, or something else which makes not much sense. importing modules via package names though, is well supported and works well in both CJS and ESM as long as the module is packaged in a dual-module way or it's published as type: module, and no issue whatsoever should ever happen. it'd be great if developers would actively help modules owners to update the way they export/package stuff so that we can happily ever move forward with |
I know that that's the case now. The annoyance for me is that, a feature that frankly should be idiot-proof has about several varying parts; 3 different file extensions, but can be extensionless in some cases, requires flags in other cases, JSON properties, build tools for using certain features, as well as the extra complexity of default and non-default import types How this is so convoluted in absolutely any case is beyond me. It should be easy, and straight-forward |
Here's another convolution I'm experiencing from having attempted to move away from using the |
@nick-bull that is what always happens when a package's entry point is transpiled using babel - it exposes the |
Thank you very much @ljharb, I initially did the latter thinking that couldn't possibly be the solution. And with that I revert back to es5, using es6 for imports/exports is just too much work for way too little returns |
For those using the esm package as a workaround for issues with migrating to native modules: note that it doesn't support the latest node.js features anymore (the last release was in May 2019). Wallaby has a fork of it with newer features, but I actually discovered that using SWC is just as fast as Just mentioning this as an option. Obviously native modules come with many advantages, and would be preferable especially for new projects. |
Just to be clear: this proposal is not to make
--es-module-specifier-resolution=node
a defaultBackstory
Recently there have been a lengthy discussion I've started in order to make
package.json
of ES6 modules more simple. Probably the biggest takeaway from this is that without extensionless imports in some form, in order to create backward-compatible packages to support subpath imports likeimport first from 'lodash/first'
they need to create long list of entrypoints inexports
field ofpackage.json
which needs to be passed around everywhere.This issue combines with obvious status quo that: many developers are used to, many existing applications already use, and many transcompilers (like typescript) application boilerplates (like create-react-app) generate extensionless imports in code: #323 (comment)
Probably the biggest "cons" against extensionless imports I've heard so far is that they are slow as they require checking few locations in filesystem in order to determine where the file is which is relevant if we want to use HTTP/2 streaming imports of non-transpiled JS files. But this seems an issue with
--es-module-specifier-resolution=node
which is only one solution to this problem.Proposed solution(s)
Solution one:
If extensionless file is imported, assume
.js
extension.import App from "./App"
always imports "./App.js"import App from "./App.js"
always imports "./App.js"import App from "./App.mjs"
always imports "./App.mjs"import App from "./App.cjs"
always imports "./App.cjs"This seems performant, accounts for status quo, and also seems to solve issue of complicated package.json for modules that support importing subpaths if
exports
is made stable.For example this could be package.json of
lodash
package (please note thatexports
is not a part of this proposal, it is already a proposed and implemented thing)require('lodash/first')
in old node loads CJS filelodash/first.js
with old resolution rulesrequire('lodash/first')
in new node loads CJS filelodash/first.js
because ofrequire
inexports
(or old resolution rules if you decide to not change this behavior)import first from 'lodash/first'
loads ES6 filelodash/src/first.js
because ofdefault
inexports
combined with the new resolution rule (default .js extension)Solution two:
If extensionless file is imported, assume imported extension has the same extension as current file.
This means that:
.js
files wil import.js
by default.mjs
files wil import.mjs
by default.jsx
files wil import.jsx
by default.cjs
files wil import.cjs
by defaultetc. etd... Seems less opinionated but also more complicated
The text was updated successfully, but these errors were encountered: