Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Enable explicit .m.js intent for ESM #16170

Closed
wants to merge 1 commit into from

Conversation

WebReflection
Copy link
Contributor

@WebReflection WebReflection commented Oct 12, 2017

As discussed in the .mjs extension trade-offs post, it would be great if NodeJS could provide a way to explicitly opt-in as ESM, without needing to use a different extension.

Goal

The purpose of this PR is to enable a universal convention for ESM that would work out of the box in both browsers and other JavaScript environment including SpiderMonkey and JSC.

If the file is imported with a fully qualified path name, and such path name uses the .m.js convention, the format will be ESM and it will throw if such file does not respect such format.

Please consider this PR

The current NodeJS diversion from the rest of the JavaScript environments is alarming for various reasons and consistency, as well as code reliability, is currently potentially compromised, as described in details in this blog post.

If there is anything else I could do in order to land this PR while ESM is still behind an experimental flag, please let me know, thank you.

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)

@Fishrock123
Copy link
Contributor

The purpose of this PR is to enable a universal convention for ESM that would work out of the box in both browsers and other JavaScript environment including SpiderMonkey and JSC.

That is already possible with .mjs. Browsers do not detect via extension, they detect via content-type.

@WebReflection
Copy link
Contributor Author

WebReflection commented Oct 12, 2017

.mjs breaks in many ways:

  • if transpiled, Babel breaks requires generating JS files
  • if transpiled, developers use the ./module pattern which is ambiguous: it breaks if you import from ESM a transpiled module, it's unreliable in terms of loaded code
  • if not transpiled, Python 2 and Python 3 simple HTTP servers won't serve .mjs with the correct mime type. Apache wouldn't neither, so wouldn't PHP. Every server-side spinner out there makes usage of the standard on the browser non portable. Every other env that does not know the .mjs extension would fail

The portability of each module is currently compromised and this is bad already, not even in the long term, as you could read through my post that explains all the caveats.

.mjs is not granting ESM, it's not a strong signal, it fails on the real world as solution.

@ljharb
Copy link
Member

ljharb commented Oct 12, 2017

It's a very minor change to tweak existing servers to supply the proper mime type for .mjs files.

@WebReflection
Copy link
Contributor Author

not everyone controls external environments/servers and the proposed change is one line full of wins for the entirety of the community.

@ripter
Copy link

ripter commented Oct 12, 2017

Like WebReflection, I often have no control over the server/environment. It's going to be a lot longer for me to able able to use .mjs over something like .m.js because all of the tools and ecosystem already exist for *.js.
The world will require updating to support .mjs which means I won't be able to use it for years.

@zenflow
Copy link
Contributor

zenflow commented Oct 12, 2017

The world will require updating to support .mjs which means I won't be able to use it for years.

s/will/would/

@refack
Copy link
Contributor

refack commented Oct 12, 2017

Just for context, there is an EPS with a suggestion for explicit ESM intent (via a package.json flag) - nodejs/node-eps#60

Also AFAIK the timeline for completing the ESM loader (i.e. until the CLI flag is removed) is for node 10, which is 6 more months away. So there is no rush, and hopefully improvement suggestions will have time for discussion.
tl;dr please keep this thread on point (as it has been till now), and please go easy with the emoji reactions.

@refack refack added esm Issues and PRs related to the ECMAScript Modules implementation. feature request Issues that request new features to be added to Node.js. labels Oct 12, 2017
@ljharb
Copy link
Member

ljharb commented Oct 12, 2017

If you're using node, .mjs will just work. If you're not using node, you can use .js and it will work fine - part of your build process can be to rename .mjs files to .js so that your legacy server can understand it. I'm not sure why that's a huge burden.

@WebReflection
Copy link
Contributor Author

If you're using node, .mjs will just work. If you're not using node, you can use .js and it will work fine

There is no reason to create an unnecessary portability barrier for the sake of it.

A standard JS module should work everywhere, there are not just browsers and Node, there's an infinity of other environment to consider too.

It's surprising, to say the least, someone from TC39 is pushing NodeJS to break where every other standard JS env would not.

part of your build process can be to rename .mjs files to .js

Are we saying, or reached the point, JavaScript is finally fully incapable, as a programming language, to work cross platform without needing third parts build processes?

Or we are consciously creating an unnecessary portability barrier?
Is the benefit of breaking portability worth it, when there is a practically identical alternative that will just work?

your legacy server can understand it

This hasn't even landed in NodeJS and you call servers unaware of .mjs legacy?
Legacy GitHub doesn't even highlight yet these files, can we please be a bit more realistic?

I'm not sure why that's a huge burden.

Why it's a huge burden to enable, as alternative solution, my proposal?
So far I haven't heard a single argument that justifies the break portability of a valid JavaScript file beside the mention to a Ruby bundler.

Where are stats about .m.js files?

Where are stats about the impact of unrecognized .mjs out there?

If everyone is concern about .mjs, and nobody is writing that extension in real-world code, what's the reason to not explore alternatives while behind an experimental flag?

@WebReflection
Copy link
Contributor Author

Where are stats about the impact of unrecognized .mjs out there?

I went ahead and done little research.

screenshot from 2017-10-12 21-27-03

Apparently, over 80% of the Web uses PHP.

So I've tried to see if latest PHP would be able to serve a .mjs module.

My version is 7.1.10

screenshot from 2017-10-12 21-28-39

So I've spinned the server and this is the result:

screenshot from 2017-10-12 21-29-51

As you can see, the most updated PHP wouldn't be able to serve JavaScript modules with an extension only NodeJS recognizes.

Since npm is the main package manager, ESM files will be published as .mjs breaking portability of these modules in over the 80% of the Web.

Then you say ...

It's a very minor change to tweak existing servers to supply the proper mime type for .mjs files.

So I've found few stats of the most common PHP versions online, for that over 80% of the Web.

screenshot from 2017-10-12 21-32-08

The reality is that if environments are so outdated, how can we even believe it's "a minor change" to tweak existing servers?

TL;DR we are breaking the whole package distribution ecosystem. We're unable to publish native ESM modules so we are slowing adoption of the standard because files with .mjs extensions cannot land, be loaded, or be served easily if not after adjustments not everyone know how, or can, do.

Accordingly, since the slice of side effects and issues with this new extension is way bigger than advantages, 'cause there are zero advantages outside NodeJS env niche, why are we destroying the future of native ES2015 modules like this here?

Where is the list of pros that overtake the list of cons, considering JavaScript has bigger adoption outside NodeJS, and again I'm not talking just about browsers?

@WebReflection
Copy link
Contributor Author

WebReflection commented Oct 13, 2017

Update if I understand correctly, using this loader and running node as such:

node --loader es.mjs

this PR/proposal would be enabled by default using an official mechanism provided in core.

If this is correct, than maybe using that loader is a way to understand how relevant, needed, or used, is the .m.js extension out there.

@vkurchatkin
Copy link
Contributor

It's surprising, to say the least, someone from TC39 is pushing NodeJS to break where every other standard JS env would not.

Every environment needs to know whether the file is a module or not before it can run it.

@michael-ciniawsky
Copy link

michael-ciniawsky commented Oct 13, 2017

Browser (Metadata (Attributes))

<script src="path/to/file.js"></script><!-- === node file.js -->
<script type="module" src="path/to/module.js"></script><!-- === node '--esm|-m' module.js -->

NodeJS (Metadata (Flag))

node --esm|-m module.js

package.json

{
  scripts: {
     start: "node --esm|-m module.js"
     // start: "NODE_ESM=true node module.js"
  }
}

npm (Metadata (module))

node_modules/package/package.json

{
  name: 'dependency'
  version: "1.0.0",
  main: 'dist',
  module: 'src', // I'm only recognized when node --esm|-m file.js
  scripts: {
     build: "babel src -d dist" // Like today 
  }
}

Default loader (node index.js) is CJS for a long time and ESM usage simply needs to be evaluated based on availability of your required dependencies.

Migration Path

$PROJECT/package.json

{
  scripts: {
-     start: node index.js
+     start: "node --esm|-m module.js"
  }
}

$DEPENDENCY/package.json

{
  name: 'dependency'
  version: 1.0.0,
  main: 'dist',
+  module: 'src',
  scripts: {
     build: "babel src -d dist"
}

@WebReflection
Copy link
Contributor Author

@vkurchatkin

Every environment needs to know whether the file is a module or not before it can run it.

No. The import syntax implies you are loading ESM. Browsers do that, SpiderMonkey do that, and JSC do that too.

@michael-ciniawsky

the --esm|-m meta flags are nowhere in this repo/proposals list, are they? 'cause that's what I was hoping for too. Force the env to run ESM only for modules updated and shipped as ESM and let bundlers solve the burden when and if needed for browsers.

Accordingly, for the time being, this loader should help migrating without involving any module in package.json.

import _url from 'url';

const builtins = new Set(
  Object.keys(process.binding('natives')).filter(str =>
    /^(?!(?:internal|node|v8)\/)/.test(str))
);

const esmIfExplicit = url => {
  const eof = url.slice(-5);
  return {
    url,
    format: eof === '.m.js' || eof.slice(1) === '.mjs' ?
            'esm' : 'dynamic'
  };
};

export const resolve = (specifier, base) =>
  builtins.has(specifier) ?
    {url: specifier, format: 'builtin'} :
    esmIfExplicit(new _url.URL(specifier, base).href;

@WebReflection
Copy link
Contributor Author

Quoting an email that didn't land here, without mentioning the author, in case it didn't land for that reason

PHP is not typically used to serve JavaScript. If you do have an unusual setup where your JavaScript files are being served by PHP, use header() to set the MIME type correctly and .mjs will work just fine. But that is likely to be a thing for a tiny percentage of actual PHP users.

Every developer spinning a localhost with PHP will have issues but it's true that's not the production scenario. However, the point here is that if PHP versions are not updated, it's clear that it's not so easy/straight-forward to expect the whole world suddenly will be able to serve .mjs files.

It's hard to know what you were actually testing without seeing code and server configuration (although please don't interpret that as a request), but it was probably something like vanilla-ish Apache serving .mjs files. For that, you will need to add the mime type to your Apache config file (which might include a .htaccess file depending how your server is configured).

Yes, even latest Apache does not serve .mjs and thinking the world can easily change Apache config and/or server .htaccess is unrealistic.

@michael-ciniawsky
Copy link

the --esm|-m meta flags are nowhere in this repo/proposals list, are they? 'cause that's what I was hoping for too. Force the env to run ESM only for modules updated and shipped as ESM and let bundlers solve the burden when and if needed for browsers.

@WebReflection Nope, just as a reminder that from usage standpoint this could be simple :)
It's really just a switch and distinction between two contexts which could be achieved by either

  1. A flag/env node file.js && node --esm|-m file.js/ NODE_LOADER=esm node file.js (:+1:)
  2. A extname prefix index.js (CJS) && index.module.js || index.m.js (ESM) (Your PR) (:ok:)
  3. 1 && 2 (Both) (:-1:)

Your proposal is much better then introducing an new file extension && MIME for all the reasons your already extrapolated above 😛 . I would still prefer the 'metadata' approach since this aligns more with how browsers implemented it, but that's less of a concern to the status quo imho

To say it directly .mjs is simply pushing the current node ecosystem incompatibilities elsewhere (HTTP Server Config, Firewall, Other Server-Side Languages, Tooling) for less good reason while the approach itself isn't really cleaner nor beneficial and will likely split the overall JS Ecosystem (albeit I must admit I can only rely on guessing/ subjective feedback on this as everybody else).

If all of this isn't sufficient resolvable then there must be spec changes proposed until a env agnostic workable standard is found (from a pure Module System point of view) and node is better off with CJS meanwhile

@WebReflection
Copy link
Contributor Author

Since there were metrics about extension issues, here I've found also metrics about how backward incompatible would be to adopt .m.js:
nodejs/node-eps#3 (comment)

screenshot from 2017-10-13 12-15-52

I would personally file a PR to fix that single module with .m.js files.

@tbranyen
Copy link

@michael-ciniawsky @WebReflection please see: ayojs/ayo#91 as it appears we are duplicating efforts. This implements --esm and import { require } from 'module';

I'm currently battling with v8 to land import.meta in this branch to move require there. I think this offers the best path forward (within my limited knowledge). It's one I'm going to pursue since I prefer .js not m.js or .mjs. I'd rather not change my existing projects that already build ESM-compat .js files.

@WebReflection
Copy link
Contributor Author

WebReflection commented Oct 13, 2017

@tbranyen yes, absolutely agreed and the only reason I've filed this PR is that .mjs seems to be irrevocable decisions nobody, beside two people, is happy with.

My only attempt is to not diverge from standards and other environments, creating an absolutely redundant extension nobody needs to signal that its JavaScript is really just JavaScript.

Having Node shipping standard import behaviors and require to bring in CommonJS is the best way to move forward.

People early adopting CommonJS as ESM already use tooling so those won't be affected, everyone else that would like to jump into fully standard ESM and keep the ability to require CommonJS legacy modules could simply do so.

It'd be the best solutions we all want but for some reason somebody prefers to promote community failures and curse the language with two extensions.

Thank You !!!

P.S. the only reason I won't abandon this and I'm fulfilling all steps to have a proper PR is that I'd like to keep all possible doors open until a final, not experimental anymore, decision has been taken.

@michael-ciniawsky
Copy link

@tbranyen I saw that one, but ayojs/ayo would need a rebase before it can be reviewed 😞 . Why not open it here aswell for better visibility and discussion ?

@refack
Copy link
Contributor

refack commented Oct 13, 2017

[Partially OT] Just finished watching @MylesBorins give the modules talk for NINA 2017 -
https://www.youtube.com/watch?v=W5CXzo4TZVU
IMHO a very good recap of the challenges and tradeoffs for ESM in node.

As discussed in the [.mjs extension trade-offs](nodejs/node-eps#57 (comment)) post, it would be great if NodeJS could provide a way to explicitly opt-in as ESM, without needing to use a different extension.

The purpose of this PR is to enable a universal convention for ESM that would work out of the box in both browsers and other JavaScript environment including SpiderMonkey and JSC.

If the file is imported with a fully qualified path name, and such path name uses the `.m.js` convention, the format will be ESM and it will throw if such file does not respect such format.

The current NodeJS diversion from the rest of the JavaScript environments is alarming for various reasons and consistency, as well as code reliability, is currently potentially compromised, as described in details in [this blog post](https://codeburst.io/the-javascript-modules-limbo-585eedbb182e).

If there is anything else I could do in order to land this PR while ESM is still behind an experimental flag, please let me know, thank you.
@tbranyen
Copy link

@michael-ciniawsky good point! once I get the latest v8 landed and swap import { require } from 'module'; for import.meta.nodejs.require I'll open here, since that has more likely of a chance of landing than the former.

@Trott
Copy link
Member

Trott commented Oct 13, 2017

Quoting an email that didn't land here, without mentioning the author, in case it didn't land for that reason

For the record, it was me. I deleted the comment because I realized this topic is sprawling and has multiple rabbit holes, and that the rabbit holes I was gravitating towards weren't the ones that were going to get us to a resolution or greater understanding. So I removed my comment. Your reply is helpful, though. I understand now why you brought that topic up. So thanks for that.

@ljharb
Copy link
Member

ljharb commented Nov 17, 2017

@devsnek TC39 and browsers have all rejected that option; node is not likely to go with that option in that light. .mjs is a file extension, and browsers and the language all don't care about file extension, so it doesn't need to be standard (the same applies to anything with package.json) - node's choices there are authoritative.

Yes, the only person who should decide if something is ESM or not is the author of that code, not the consumer - only the author has the ability to safely decide that. A file extension, as well as package.json metadata, achieve that.

@ljharb
Copy link
Member

ljharb commented Nov 17, 2017

@LinusU yes, import { renderToString } from 'react-dom' would not work (unless they added a .mjs file alongside their "main" export, in which case it would work smoothly). My understanding is that there's timing/ordering issues between node and the spec that prevent it from being able to infer named imports without having to actually evaluate the code; @bmeck can explain more in depth.

@michael-ciniawsky
Copy link

michael-ciniawsky commented Nov 17, 2017

for the sake of discussion, and since its pretty simple to implement, whats wrong with something like 'use modules';. It would be pretty simple to implement with the existing loader and i don't see it being too much of a perf issue. Yes it wouldn't be standard with other browsers and runtimes, but neither is .mjs (yet). It would also keep telling node that something is esm to the person writing the code not the person running the code, which i think is a pretty important detail. (also +1 to to pkg.module)

@devsnek The concern with use module; equal to .mjs is that it would again be redundant/unneeded in browsers and is therefore node specific since the metadata approach is already more or less set in stone on the browsers side, unless there isn't any other discussion I'm unaware of... ¯_(ツ)_/¯

@WebReflection
Copy link
Contributor Author

WebReflection commented Nov 17, 2017

Aha, the way you presented it made it sound like a stated requirement. I stand corrected...

This is key. I can make up my own requirements too and decide that keeping .js is mandatory.

It's been months we're discussing this issue with some people talking like a secret room of very important people made up constrains so that nothing works because of these constrains nobody can even read to make counter-proposals ... so to whoever is concerned about the quality of this discussion, how about we start moderating every subjective opinion delivered as VIP decision?

Thank you.

@ljharb
Copy link
Member

ljharb commented Nov 17, 2017

This isn't just a subjective thing; this requirement is necessary for there to be a smooth migration path to ESM for everyone. Either ESM becomes the universal module format for JS, or it's DOA, and if "migrating my module to ESM" has to be a breaking change, then package authors won't do it en masse. The requirement to not have to know the module format you're importing is precisely for this reason: because without it, ESM is dead.

@targos
Copy link
Member

targos commented Nov 17, 2017

@ljharb I totally agree with this requirement. However, unless I'm missing something, this can only work if we have named imports with CJS interop. Otherwise, one has to know that a module is CJS or ESM to use its exported functions. And worse: a module migrating to ESM would be a breaking change for people who already import it via CJS interop.

Note: we are trying to get named exports from CJS in this PR: #16675

@LinusU
Copy link
Contributor

LinusU commented Nov 17, 2017

This isn't just a subjective thing;

I kind of think that it is though. All the different way forwards has different pros and cons, and which one you care most about is subjective...

this requirement is necessary for there to be a smooth migration path to ESM for everyone.

I think a very good migration path is to introduce it in a way where you can use both the new (import _ from _) and the old const _ = require(_) way at the same time. Then the ecosystem can slowly shift as the support is being rolled out to all currently used versions of Node.

[...] if "migrating my module to ESM" has to be a breaking change, then package authors won't do it en masse.

I mean, won't that always be the case? Because if the module starts using the import syntax internally it won't work with older Node.js, which is a breaking change.

Furthermore, it doesn't have to be a breaking change to the api, if paired with a field in the package.json a module could publish a file with "module": "esmodule.js" that uses the old require and exports a new interface. e.g.

const Spacer = require('./index')

export default Spacer
export const calculateSpace = Spacer.calculateSpace
export const collapseChildren = Spacer.collapseChildren

The requirement to not have to know the module format you're importing is precisely for this reason: because without it, ESM is dead.

I don't really see how you are making this connection. The only thing this would mean is that when I'm importing another library, I need to know how to import it. Since at this point I'll probably be looking at the documentation for the modules anyway, that is probably the first thing mentioned.

It's also a really simple rule to remember, to import an es module use import, to import a commonjs module use require.

@targos
Copy link
Member

targos commented Nov 17, 2017

@LinusU

I think a very good migration path is to introduce it in a way where you can use both the new (import _ from ) and the old const _ = require() way at the same time. Then the ecosystem can slowly shift as the support is being rolled out to all currently used versions of Node.

By the time ESM is shipped in Node, you will probably be able to use the old require(...) through import.meta.require(...). I have a working implementation of it, it's just waiting for support on the V8 side (currently only in canary).

I mean, won't that always be the case? Because if the module starts using the import syntax internally it won't work with older Node.js, which is a breaking change.

I depends on how the module is published. Authors can write the module with the import syntax and publish it along with the code transpiled to CommonJS.

@WebReflection
Copy link
Contributor Author

without it, ESM is dead

I use ESM daily, it's pretty much alive to me, and this is the kind of speculative tone I'm a bit tired about.

I will no answer anymore to speculative, subjective, arguments that bring to this topic absolutely nothing.

I think a very good migration path is to introduce it in a way where you can use both the new (import _from ) and the old const _ = require() way at the same time. Then the ecosystem can slowly shift as the support is being rolled out to all currently used versions of Node.

I fully agree on this approach, which is the same shared by Chris in this tweet:
https://twitter.com/isntitvacant/status/921238953197318144

@WebReflection
Copy link
Contributor Author

@targos I believe import.meta.require(...) would work as well for migration forward

@tbranyen
Copy link

@targos awesome! I had tried landing the latest v8 at the time import.meta shipped, but was unable to get past the merge conflicts in the node repo. Can you point me to your implementation? Also IIRC import(..) works with .js files, am I mistaken about that assumption?

@WebReflection
Copy link
Contributor Author

IIRC import(..) works with .js files, am I mistaken about that assumption?

everything in JS works with .js files and dynamic import does the same, at least on browser land, where there is no CommonJS

@tbranyen
Copy link

@WebReflection yeah, I'm wondering if Node intends to interfere with that or if that will inevitably become a "backdoor" into loading .js files.

(async () => {
  const { default: map } = await import('/node_modules/lodash-es/map.js');
})();

package.json field isn't a bad idea for sharing modules, but at that point, you could just infer from the module-or-equivalent entry specifier (but this admittedly isn't as flexible when digging into the FS contents, weird inference).

I much prefer the --esm flag for the Node process. If TSC could discuss anything, to me, it would be whether or not it is viable to emulate what browsers are doing with "entrypoint" script vs module semantics. I believe it is, and have not seen any sufficient evidence to the contrary.

To echo what @jmeas had brought up before, it would be great to see evidence that is clearly documented that refutes the notion of a CLI flag. At the time I was working on that implementation, I was constantly told that Node would never support it, and any attempts to land it would be blocked.

More specifically, we need more concrete reasons, instead of (what I consider FUD):

Either ESM becomes the universal module format for JS, or it's DOA

I hope the TSC can be more open-minded about how users will consume modules, it's hard to discuss alternatives when one side is absolutely deadlocked.

@michael-ciniawsky
Copy link

michael-ciniawsky commented Nov 17, 2017

There is @std/esm for node =< v10.0.0, #16675 for named CJS imports/exports, #15713 for dynamic import(), import.meta seems to be drafted out as well but awaiting V8 version shipping with Chrome 64 and landing in node (?) and ES Modules already landed for node >= v10.0.0. So all that is left is to define how to distinguish the parsing goal e.g node --esm && a standard to provide ESM in npm packages e.g pkg.module ? If it's assumed that especially #16675 works out, what are the concerns/reasons still left to justify introducing a new file extension affecting systems outside of node itself ? Or in other words in which cases would node --esm && pkg.module not be sufficient enough compared to .mjs ?

@WebReflection
Copy link
Contributor Author

WebReflection commented Nov 17, 2017

also, from that very same thread:

the semantics of ESM required knowing the names prior to any evaluation, which precluded runtime determination.

this basically erase any concern about not needing to know what kind of module you are importing, something I've anyway never experienced in 18+ years of JS (require, import, globals, JSONP, I always knew what I was consuming).

@Trott
Copy link
Member

Trott commented Nov 17, 2017

@tbranyen I might not be telling you anything you don't already know below, but I'm going to say it anyway for observers/readers.

I much prefer the --esm flag for the Node process. If TSC could discuss anything, to me, it would be whether or not it is viable to emulate what browsers are doing with "entrypoint" script vs module semantics.

The TSC doesn't usually get involved in things to that level of detail. Generally, the decisions are made by the people doing the implementation work along with whatever subset of the 100+ Collaborators who review the specific contributions. The TSC gets pulled into ESM frequently because it's so contentious, so I wouldn't remotely be surprised if the TSC had to weigh in on a PR implementing a --esm flag. Just saying: This stuff doesn't usually happen top-down (from the TSC to the project) in Node.js. It's usually the other way around. (This is perhaps the primary reason why @bmeck's opinion tends to carry so much weight with TSC members.)

I hope the TSC can be more open-minded about how users will consume modules,

The opinion your quoting/responding to is not from the TSC or even a TSC member. That said, I get it that from your perspective, the TSC is not being sufficiently open-minded. I'll just add that it has also been stated (including by someone doing actual implementation work on ESM for Node.js) that the TSC has been insufficiently decisive about this stuff, meaning we get to have the same conversations over and over and get pull requests that undermine decisions/direction that should have been set a long time ago.

I'm not saying your wrong to feel the way you do. But ESM is an explosive topic in some ways and the TSC is getting it from all sides. This is going to require patience (and a tolerance for repetition, I'm afraid). Thanks to everyone for participating. I know it can be frustrating.

@WebReflection
Copy link
Contributor Author

WebReflection commented Nov 17, 2017

@Trott since TSC is aware of the situation I also hope it can help moving forward with the right decision for Node and the JS community.

If I can summarize the current status, since I've been involved a lot over the last months, and I've proposed already various alternatives, patches, solutions, this is in favor of keeping .js

  • ESM semantics require developers to know exactly what they are importing. Using an import with an omitted extension, that might or might not end up loading CJS instead of ESM brings more ambiguity to the developers plate than the one .mjs is supposed to solve
  • Web developers that strictly adhere to ECMAScript standards already use fully qualified names to load ESM. There are already various libraries consumable via unpkg.com fully written in ES2015+. Since I am a very pragmatic developer, the moment the extension got approved, and even if Apache didn't, I've PR'd myself the change to such CDN to make .mjs usable via web to. As of today, I am probably the only developer on earth that published .mjs modules there though. Even big players keep using ESM via .js and I strongly believe the majority of developers will keep publishing .js for either Node modules or Browsers
  • yes, there are modules consumable by both Node and Browsers, this is a strength of the language and there's no reason to deprave JS of its most desirable strength (write once, run everywhere)
  • in NodeJS very few use module extensions. Thinking that an extensions can solve anything is very optimistic but it's just magic behind the scene.
  • if the explicit extension is a made up subjective mandatory constrain to define how the module should behave, using this very same PR would keep .js extension and fade away that ambiguity keeping the entirety of the current ecosystem untouched but promoting, as migration pattern, import * from './library.m';
  • in NodeJS very few use module extensions. That means that even if this PR would land, nobody will ever care and will let tooling solve everything for them
  • since tooling is apparently a made up subjective mandatory constrain for the modern JS development, that means nobody needs .mjs
  • the extension purpose is proposed as migration pattern but there's no proposal to deprecate .js and everyone knows that is not going to happen for at least the next 10 years. Accordingly, diverging from the rest of the current/working JS ecosystem and real-world code-bases / environments looks like everything but the best way forward for either Node or the JS language itself
  • there is data out there about how CJS and/or simulated ESM is used these days: it's ambiguous but it's easily solved by tooling. The community already took the migration pattern, NodeJS just realized that probably a bit too late. Browsers also already shipped ESM, but today we need tooling to consume those modules in NodeJS, an environment supposed to be ahead of browsers, not behind.
  • developers expect in Node to use Node stuff (require, global, process, etc). That is normal, that is about not breaking backward compatibility. All proposed alternatives keep that in mind and work better than a strict .mjs behavior which is not granted to be respected due transpilation/sharing/extension omission
  • in my personal experience, but you can read others with similar idea in this very same thread, a developer always know what module is loading and samever has a reason to exist. Libraries that can be updated will push a major and specify either engine Node 10 or module info in the package.json, those that cannot be updated will have developers well aware so that they can use require or the meta one and have a visual instant feedback of which module is old and which one is still maintained / updated. This is a friendly migration pattern, a new extension will have all of the issues and none of the wins.

On the other side, to argument against this very same PR which is a last resort for .js, I've even heard that some Ruby server has issues understanding file names and work only with extensions ... not only that Ruby server thing has nothing to do with Node itself, but that's apparently a compelling reason to break everything else on the internet that cannot be easily updated but already works with .js since 2000.

Thanks for reading through.

@MylesBorins
Copy link
Contributor

MylesBorins commented Nov 17, 2017 via email

@WebReflection
Copy link
Contributor Author

WebReflection commented Nov 18, 2017

@MylesBorins not sure it could be helpful/related but I've proposed on January module.import(...):Promise to indeed cover the ES2017 dynamic import(...) case for CommonJS.
This is my old PR and it's needless to say there have been developers exploiting the PR with sentences like:

... this only works if you use module.import instead of require all the time, but no one will do that ...

which is unfortunately the feeling I have every time I try to interact with NodeJS repository.

There are always quite few unproductive, subjective, theories exposed like absolute truth written in stones.

I am looking forward to read every comment you have on this matter and thanks in advance for taking the time.

@WebReflection
Copy link
Contributor Author

last thought about the JS community: if it really matters, reading developers feedback on this very same thread should be considered.

I'm the first one that doesn't like this last resort proposal to keep the .js extension and yet this is the current community feedback I can see:

screenshot from 2017-11-18 00-43-56

Scrolling through the rest of the discussion it's easy to spot what is welcomed by the community and what is considered more hostile.

@devsnek
Copy link
Member

devsnek commented Nov 18, 2017

i don't think that 30 people speak for a community of millions, and it would be irresponsible to assume so.

@WebReflection
Copy link
Contributor Author

Neither do I, although that’s the only data we have here so counter evidence is needed to state otherwise 🤷‍♂️

@MylesBorins
Copy link
Contributor

I'm going to go ahead and close this as we are not currently exploring .m.js as a solution

You can find out more about the current state of modules in https://medium.com/the-node-js-collection/the-current-state-of-implementation-and-planning-for-esmodules-a4ecb2aac07a

please let me know if this should be reopened

@jamesplease
Copy link
Contributor

For future visitors: npm’s recommendation for ES Modules uses flags (similar to ayojs/ayo#91) rather than .mjs or .m.js, which is quite cool 😎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
esm Issues and PRs related to the ECMAScript Modules implementation. feature request Issues that request new features to be added to Node.js.
Projects
None yet
Development

Successfully merging this pull request may close these issues.