Skip to content
This repository has been archived by the owner on Sep 2, 2023. It is now read-only.

Proposal: make .js extension default for es6 imports #444

Closed
sheerun opened this issue Nov 22, 2019 · 51 comments
Closed

Proposal: make .js extension default for es6 imports #444

sheerun opened this issue Nov 22, 2019 · 51 comments

Comments

@sheerun
Copy link

sheerun commented Nov 22, 2019

Just to be clear: this proposal is not to make --es-module-specifier-resolution=node a default

Backstory

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 like import first from 'lodash/first' they need to create long list of entrypoints in exports field of package.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 that exports is not a part of this proposal, it is already a proposed and implemented thing)

{
  "main": "index.js",
  "exports": {
    "require": "./",
    "default": "./src/"
  }
}
  • require('lodash/first') in old node loads CJS file lodash/first.js with old resolution rules
  • require('lodash/first') in new node loads CJS file lodash/first.js because of require in exports (or old resolution rules if you decide to not change this behavior)
  • import first from 'lodash/first' loads ES6 file lodash/src/first.js because of default in exports 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:

  • extensionless imports in .js files wil import .js by default
  • extensionless imports in .mjs files wil import .mjs by default
  • extensionless imports in .jsx files wil import .jsx by default
  • extensionless imports in .cjs files wil import .cjs by default

etc. etd... Seems less opinionated but also more complicated

@ljharb
Copy link
Member

ljharb commented Nov 22, 2019

In a browser, extensions mean nothing.

In node, .js means CJS, unless a package.json has "type": "module".

The only thing that unambiguously means ESM is .mjs, and as such, if we were to privilege just one extension, I'd say it has to be that one (unless it's a type module package, i suppose).

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.

@MylesBorins
Copy link
Contributor

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 .js would violate that contract. This is important if we want to make authoring import-map, a feature for browsers that will allow supporting bare specifiers such as import 'lodash' out of the box.

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 .js extension code could not be shared without do static analysis of the code itself and dynamically updating the import map for cases where no file extension was used... that or transpilation.

@sheerun
Copy link
Author

sheerun commented Nov 23, 2019

However, the other issue is, the importer has no say over the format of the module.

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.

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 .js would violate that contract.

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?

@Jamesernator
Copy link

An alternative solution might be to allow some pattern matching in the exports map:

{
  "exports": {
    "regex:(\/.*)(\.js)?": "/$1.js"
  }
}

@jkrems
Copy link
Contributor

jkrems commented Nov 25, 2019

One of the design constraints of exports was that it's possible to transform the data into a static resolution map (import map). So we would have to be reasonable certain that import maps are willing to add support for pattern matching which IIRC isn't the case.

@coreyfarrell
Copy link
Member

Ref WICG/import-maps#7

@sheerun
Copy link
Author

sheerun commented Nov 25, 2019

I don't think this would require regexps, something simple like this would suffice:

{
  "imports": {
    "lodash": "/node_modules/lodash-es/lodash.js",
    "lodash/": "/node_modules/lodash-es/"
  },
  "extension": "js"
}

Which automatically adds .js extension if non is provided and import is not found as exact match.

Although this example shows how it is possible to allow extension-less imports with import maps, it's not necessarily desirable. Doing so bloats the import map, and makes the package's interface less simple—both for humans and for tooling.

This looks like by-design flaw that could be solved by something as simple as above option.

@MylesBorins
Copy link
Contributor

@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. import './feature'

I'm not a big fan of attempting to shoe horn this into import maps for a couple reasons

  • it is not a full solution of the problem, only partial
  • it introduces a bunch of complexity for the manifest into what is otherwise a 1:1 mapping
  • it introduces a bunch of complexity for the runtime for what is otherwise a O(1) operation (lookup)

While I appreciate your attempts to solve this I don't think this proposed solution is going to work

@sheerun
Copy link
Author

sheerun commented Nov 25, 2019

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

This would not support relative imports e.g. import './feature'

Indeed it seems that adding .js suffix (or js extension) needs to be defined for relative imports as well. It seems that import-map already controls behavior of relative imports by providing base path to resolve paths (https://github.com/WICG/import-maps#bare-specifiers-for-javascript-modules)

In the case of relative-URL-like addresses, they are resolved relative to the import map's base URL

I think defining additional behavior, like default extension that covers both relative imports and "trailing slashes" imports, is not that big deal.

it is not a full solution of the problem, only partial

This covers as much of the problem as possible given the "--es-module-specifier-resolution=node is not browser-compatible and computationally expensive" argument. If not, what are use cases that it doesn't cover?

it introduces a bunch of complexity for the manifest into what is otherwise a 1:1 mapping

For me it seems adding default extension is as much complex as "trailing slashes" imports which is already a part of import-maps

it introduces a bunch of complexity for the runtime for what is otherwise a O(1) operation (lookup)

This is still O(1)

@sheerun
Copy link
Author

sheerun commented Nov 25, 2019

On extra note I don't argue for extension-less import because simply because I and others don't like adding .js extension to each import (which is already an argument on top of already mentioned ones). It is also because there exist at least 70000 of otherwise perfectly good ES6 packages in the wild that already use extension-less imports and .js extensions.

@jkrems
Copy link
Contributor

jkrems commented Nov 25, 2019

It is also because there exist at least 70000 of otherwise perfectly good ES6 packages in the wild that already use extension-less imports and .js extensions.

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 __dirname, none are relying on __esModule magic for imports of CJS dependencies, etc.. Afaik file extension isn't the only thing that stops most of today's babelscript/webpack modules from being runnable as actual native ES modules. From my experience .js is usually associated with non-ESM files, some of which may be using import syntax but most of them won't work in any actual ESM runtime. Because nobody ever tested those files in an actual ESM runtime.

@sheerun
Copy link
Author

sheerun commented Nov 25, 2019

@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

@jkrems
Copy link
Contributor

jkrems commented Nov 25, 2019

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.

@sheerun
Copy link
Author

sheerun commented Nov 25, 2019

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.

@jkrems
Copy link
Contributor

jkrems commented Nov 25, 2019

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 import in .js that were actually valid ESM all the way through was low enough that so far the rule of thumb hasn't let me down.

The world is full of code like this: https://github.com/zeit/now/blob/master/packages/now-next/src/dev-server.ts#L14 (.ts but that was just a file I happened to have open). Works great in the current compilation pipeline. Would break immediately and in ways that may be hard to fix if somebody tried to run this code as an actual module. So yeah, if a project is using .js and has module syntax, I personally wouldn't expect it to work as ESM. Your mileage may vary but that has been my experience so far.

@sheerun
Copy link
Author

sheerun commented Nov 25, 2019

I hope this won't be a reason to reject this proposal

@GeoffreyBooth
Copy link
Member

So yeah, if a project is using .js and has module syntax, I personally wouldn't expect it to work as ESM.

You could also say the same about a project using .js and module syntax and lacking "type": "module". Or put another way, .js and module syntax and "type": "module" should make you expect the code to work in Node.

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 require and the CommonJS psuedo-globals, etc.

@sheerun
Copy link
Author

sheerun commented Nov 25, 2019

I'm aware of the role of "type": "module" and .mjs in migrating applications and projects. This has nothing to do with this proposal as it won't prevent such ways to migrate:

  • Projects can manually rename .js to .mjs one by one and manually change import App from './App' to import App from './App.mjs' in each individual migrated file, if they desire
  • Projects can migrate make all code in app ES6-compatible and add "type": "module" to package.json while at the same time renaming all files to .mjs and chaning all extension-less imports to .mjs

This proposal allows for third possibility that me and many others would prefer:

  • Projects can still migrate make all code in app ES6-compatible and add "type": "module" to package.json without renaming all files to .mjs or changing any existing ES6 imports

I hope you understand

@GeoffreyBooth
Copy link
Member

GeoffreyBooth commented Nov 25, 2019

I hope this won't be a reason to reject this proposal

If I’m understanding your motivations correctly, ultimately you want a very similar thing to --es-module-specifier-resolution=node: you want to be able to leave off extensions in import specifiers, e.g. import './file' rather than import './file.js'. It’s just that you want to use a different algorithm than --es-module-specifier-resolution=node, and you want it enabled for everyone by default. Is this correct?

The canonical issue for extension resolution is #268. For all the reasons listed there, many members of this group prefer explicit extensions by default (import './file.js'). So that’s already the uphill battle your proposal faces, in that there isn’t consensus or a majority for implicit extensions in general; but on top of that, there’s probably even less of a minority for an automatic extension resolution algorithm that differs from CommonJS.

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.

@sheerun
Copy link
Author

sheerun commented Nov 25, 2019

It’s just that you want to use a different algorithm than --es-module-specifier-resolution=node, and you want it enabled for everyone by default. Is this correct?

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.

@GeoffreyBooth
Copy link
Member

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 “node-style resolution” is a thing specific to Node. As a major reason (if not the major reason) for Node supporting ES module syntax is because it’s the JavaScript standard, it’s important that we support it in ways that improves interoperability across the ecosystem. Even "exports", though it seems on its face like yet another Node-specific thing, has an equivalent moving forward in browsers.

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.

@sheerun
Copy link
Author

sheerun commented Nov 25, 2019

I mean literally other programming languages

@jkrems
Copy link
Contributor

jkrems commented Nov 25, 2019

instead of renaming all files and changing each import .

Just to clarify: When adding type: module, nothing forces a file rename. Changing each import is most likely running a single script since this is very straight-forward to automate.

It's puzzling that node group opposes so strongly to extension-less imports which every other language supports.

I think there's two important pieces here:

  1. Not every language adds extensions. I'm not even sure I would say "most do". C++ doesn't. PHP doesn't. Other languages decouple the name of a "module"/unit from the file system. E.g. they load a module by its name, not necessarily by a filename. E.g. Go or Rust do not load files so "add extensions" isn't really meaningful. Same goes for Java which loads "classes", not files.
  2. Most languages only support loading a single kind of source. Importing JSON, HTML, WASM, CommonJS, ESM, [...] is fundamentally different from "every file ends in .xyz anyhow". JS and node do have different, somewhat equal source file types. I'm not sure I know of a language that is faced with the same scenario so the comparison is flawed.

@MylesBorins
Copy link
Contributor

MylesBorins commented Nov 25, 2019 via email

@sheerun
Copy link
Author

sheerun commented Nov 25, 2019

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 .js extension (or the same extension) a default.

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)

@azz
Copy link

azz commented Nov 26, 2019

Just to be clear: this proposal is not to make --es-module-specifier-resolution=node a default

Is there any discussion of why this cannot be considered? Is it purely to deter package authors from using the .js extension?

@chyzwar
Copy link

chyzwar commented Dec 29, 2019

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 “node-style resolution” is a thing specific to Node. As a major reason (if not the major reason) for Node supporting ES module syntax is because it’s the JavaScript standard, it’s important that we support it in ways that improves interoperability across the ecosystem. Even "exports", though it seems on its face like yet another Node-specific thing, has an equivalent moving forward in browsers.

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.

@GeoffreyBooth
Copy link
Member

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 .js by default without it being a huge breaking change. package.json type: module achieves the same desired result without a breaking change. We're encouraging all packages to include type so that the default can be switched someday in the future.

@SMotaal
Copy link

SMotaal commented Jan 2, 2020

@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?

@chyzwar
Copy link

chyzwar commented Feb 2, 2020

@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)!

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.

@ljharb
Copy link
Member

ljharb commented Feb 2, 2020

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.

@chyzwar
Copy link

chyzwar commented Feb 8, 2020

Self-reference specifiers in no way “break” the spec; the spec explicitly imposes no constraints on specifiers.

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:

ESM and CommonJS Interoperability
Node.js and Browser interoperability

Otherwise, the whole discussion is just moving goalposts.

There was no existing idiomatic node ESM code prior to node 13.3, the first version ESM was unflagged.

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.

@ljharb
Copy link
Member

ljharb commented Feb 8, 2020

@chyzwar bare specifiers (like 'react') already don't work in browsers; self-reference is no different - and import maps provides a browser-compatible solution for all of them.

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.

@ghost
Copy link

ghost commented Apr 18, 2020

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!

@ljharb
Copy link
Member

ljharb commented Apr 18, 2020

Being the top entry on google for a very very specific five-term search query isn't particularly notable.

@dionysiusmarquis
Copy link

I waited so long to finally utilize import natively in node. The first time I gave it a try lately I was constantly stuck with:

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 esm and/or babel for another few years.

@MylesBorins
Copy link
Contributor

@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.

@dionysiusmarquis
Copy link

dionysiusmarquis commented Jun 16, 2020

I‘m currently trying to implement Snowpack for Sapper. The goal is to run the server and browser part completely without bundling while developing. The Snowpack browser part is super fast, but it's kind of pointless to a certain degree, because the server part runs much slower even than the bundled counterpart. It‘s because, if I understand you correctly, I still have to use esm or babel to run nearly every package that already uses ”type”: “module” valid module source code, but does not use relative import with extensions. I actually can‘t remember seeing any import with file extension since import exists in this context. Even though maybe the wast majority of esm ready packages out there are totally fine, but won‘t be accepted as valid as long as they don‘t use extensions in their import is a huge bummer. I can throw the server part, wich is completely esm into every bundler or helper like esm and It will always run just fine. Running it in Node with ”type”: “module” just won‘t. It means even if I rewrite every import In Sapper the developers writing the server part of their application will not be able to use mostly any other package out there with ”type”: “module” valid module source code. So you just cant utilize a lot packages, because no developer knew that there would ever be an context in wich an import with extension is essential to have written valid esm code.
Now I had to write a lot of caching and rerender mechanism code and live with quite a huge esm or babel overhead to get decent unbundled speed for the server part. This is a bit frustrating.
The import With extension might be completely logical, but its also completely unintuitive, because there was simply no context the last years where this part of the import mechanism was so essential. This will end up developers to stick with bundler and helper for esm just like before. And ideas like Snowpack are unnecessary complicated to implement for Node.
I will not enter this discussion. But I think an “real world” example regarding this issue, might be interesting.

@chase-moskal
Copy link

@dionysiusmarquis — hold up, wait a minute

nearly every package that already uses ”type”: “module”, but does not use relative import with extensions.

are you saying there are packages out there which are using "type": "module", and yet the relative imports therein are not suffixed with .js? can you show an example, or have i misread you?

@jkrems
Copy link
Contributor

jkrems commented Jun 16, 2020

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.

@dionysiusmarquis
Copy link

@chase-moskal Yes, you are right. That doesn't make sense.

@MylesBorins
Copy link
Contributor

@dionysiusmarquis thank you for the context. I notice you replaced type: module with valid module source code in you post and would like to suggest that the source code you are referring to is not in fact valid module source code as they are relying on specifier logic that is not supported by the majority of runtimes. I recognize this is frustrating, as the majority of ESM source code on the npm registry is written this way... but I think that the challenge here is that these modules have been written targeting transpilers, not runtimes... which results in the exact pain you are feeling.

You could attempt to run node with the --experimental-specifier-resolution=node flag to be able to keep the file extension / directory resolution algorithm, but at this time there is no movement to make that a default, or even a stable feature tbqh.

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.

@nick-bull
Copy link

nick-bull commented Feb 23, 2021

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, package.json configurations, etc. are needed to use something as simple as import ... from .... I understand there's a bunch of nuances, but why imports suddenly require extensions after using ESM is beyond frustrating. It is imo bad UX to have in one version a different requirement for file extensions than the other when most transpilers allow extensionless imports

Instead of impossibly long flags, it'd be really cool to have an option, similar to extension, such as directoryImports: true in package.json, if it is undesirable for directory imports to be included as default

@WebReflection
Copy link
Contributor

WebReflection commented Feb 24, 2021

why imports suddenly require extensions after using ESM is beyond frustrating

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 .js as extension for whatever it is, either ESM, or CJS, which is the case, for me, since dual packages exist: I import everything as .js and everything works as expected.

@nick-bull
Copy link

why imports suddenly require extensions after using ESM is beyond frustrating

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 .js as extension for whatever it is, either ESM, or CJS, which is the case, for me, since dual packages exist: I import everything as .js and everything works as expected.

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

@nick-bull
Copy link

Here's another convolution I'm experiencing from having attempted to move away from using the esm package to relying on node's es6 support. The solution so far for expected behavior has been to revert back to esm again

@ljharb
Copy link
Member

ljharb commented Feb 24, 2021

@nick-bull that is what always happens when a package's entry point is transpiled using babel - it exposes the .default property, which is an internal implementation detail of babel's. The solution is for your transpiled package to either hand-write CJS in the entry point, to strip off the .default, or to use https://www.npmjs.com/package/babel-plugin-add-module-exports.

@nick-bull
Copy link

@nick-bull that is what always happens when a package's entry point is transpiled using babel - it exposes the .default property, which is an internal implementation detail of babel's. The solution is for your transpiled package to either hand-write CJS in the entry point, to strip off the .default, or to use https://www.npmjs.com/package/babel-plugin-add-module-exports.

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

@mbrowne
Copy link

mbrowne commented Oct 18, 2021

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 esm. And you can of course specify the version of node you're using as the target, so it doesn't even have to transpile any of your code except for the import/export statements (or very new features not yet supported by node, if you want to use them).

Just mentioning this as an option. Obviously native modules come with many advantages, and would be preferable especially for new projects.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests