Skip to content
This repository has been archived by the owner on Jul 31, 2018. It is now read-only.

.mjs extension trade-offs revision #57

Closed
YurySolovyov opened this issue May 11, 2017 · 246 comments
Closed

.mjs extension trade-offs revision #57

YurySolovyov opened this issue May 11, 2017 · 246 comments

Comments

@YurySolovyov
Copy link

YurySolovyov commented May 11, 2017

I obviously can't speak for the whole community, but it seems like a lot of people are not happy with .mjs.

One of the main arguments to keep .js is that if we can detect 99% of cases where we CAN tell if is it CJS or ESM (or where we just know what to do), we may just call rest 1% edge cases and deal with it.

We can even come up with some linter rules and/or workarounds to simply teach people to do the right thing.

@bmeck
Copy link
Member

bmeck commented May 11, 2017

I want to start off with the 99% fallacy here. We are talking about module graphs. If 1% of your graph is wrong it can affect 100% of your graph. Let us keep that in mind.

Lets also try and enumerate where things differ between CJS, Script, and ESM whenever we talk about the problematic situations.

Lets also try and enumerate use cases where those situation might occur whenever we accept or dismiss them as being valid or invalid.

@YurySolovyov
Copy link
Author

YurySolovyov commented May 11, 2017

Let's start with simple stuff.
I think one of the most common patterns in node is to require() a bunch of core modules and export either one object with some API, or just export a plain function:

// Full CJS
const fs = require('fs');
const path = require('path');

const api = function(...params) {
  // ... 
};

module.exports = {
  api: api
};

Which in ESM I guess translates to something like:

// Full ESM
import fs from 'fs';
import path from 'path';

const api = function(...params) {
  // ... 
};

export {
  api: api
};

Are there any problems so far in that particular example?

@bmeck
Copy link
Member

bmeck commented May 11, 2017

Parsing (focus on when ESM is not a parse error but others are):

Source Script CJS ESM
var arguments global local parse error
var eval global local parse error
import("x") ok ok ok
import "x"; parse error parse error ok
export {}; parse error parse error ok
with({}){} ok ok parse error
<!--\n ok ok parse error
-->\n ok ok parse error
0777 ok ok parse error
delete x ok ok parse error
try {} catch (eval) {} ok ok parse error
try {} catch (arguments) {} ok ok parse error
(function (_, _) {}) ok ok parse error
eval = eval ok ok parse error
arguments = [] ok ok parse error
implements ok ok parse error
interface ok ok parse error
let ok ok parse error
package ok ok parse error
private ok ok parse error
protected ok ok parse error
public ok ok parse error
static ok ok parse error
yield ok ok parse error
return parse error ok parse error
await ok ok parse error

Eval differences (overlap parses and runs, but diff results)

Source Script CJS ESM
this global module undefined
var x global local local
(function (){return this}()) global global undefined
(function (x) {x = 1; return arguments[0];})() 1 1 undefined
(function () {return typeof this;}).call(1) "object" "object" "number"
var x = 0; eval('var x = 1'); x 1 1 0
__filename global local global
__dirname global local global
require global local global
exports global local global
module global local global
arguments global local global

Timing differences (not a problem, listed for posterity)

Source Timing Hoisted Blocking
require('foo'); sync no yes
import "foo"; untimed (async generally) yes yes
import('foo'); async no no

Potential relevant use cases w/o ESM specific declarations:

In all cases where only import declarations are used import() may be used instead. This list really is about things that do not export values.

  • Mutation of Globals
    • Polyfills
  • Usage of Globals
    • Configuration of globals (source-map-support, some config scripts)
    • JSONP-like calling of globals
  • Prefetching (wants to be unordered parallel so uses import())
  • Init scripts (CLI etc.)
  • REPL like text (either for local testing or things like vm.runInContext)
    • Anything really that uses the completion value of the text

Potential relevant use cases that may combine source texts

Relevant to when import or export declarations may be added/removed.

  • Optimizers / Build Scripts
    • Inlining of dependencies
    • Concatenation of files
  • Editing a file
    • Copy/Paste from internet

Potential relevant use cases w/o package.json

  • One off Scripts
    • Bash-like utils
    • Education situations
  • Configuration scripts
    • Shared .rc style files
  • Movable scripts (npm bin scripts have similar but not same)
  • Serverless-like single file situations
  • Eval from CLI (STDIN, "-e", "-p", REPL)
    • including node myapp.js

Potential tooling that lacks package.json capabilities

  • Scripts without JSON libraries
    • nvm in particular
  • OS-like file associations
  • Code editors

Potential relevant use cases w/o a file extension

  • Eval from CLI (STDIN, "-e", "-p", REPL)
    • excluding node myapp.mjs

Potential tooling that lacks file extension capabilities

None known yet.

Mandates

Resolution

Resolution in ESM is URL based and should be 100% compatible in non-error cases with the web specification. This is known to have minor differences with CJS.

Forward Path

A path for CJS to being using ESM modules must exist.

  1. It must be possible for CJS files to import() ESM.
    • Since this is available in the Script goal, this is automatically fulfilled.

Backwards Path

A path for ESM to be created that uses legacy or CJS files must exist.

  1. It must be possible to import CJS files.
    • Some things such as Node core are unable to be upgraded to ESM due to design or usage differences. Other Modules such as meow use things that are not present/do not translate into ESM like module.parent (due to parallel loading, idempotency requirement, etc.).
    • Some things like "deep" linking is in use and necessary to support like "lodash/chunk"
  2. There must be an upgrade path for ESM to be safe against CJS becoming ESM.
    • Generally achieved by ensuring CJS files are a facade with only a single default export
  3. Mixed mode situations (both ESM and CJS in same app/package) must be supported. Note: again, there may be cases where code can never be updated to ESM.

ESM only future

It should be possible for the ecosystem to move to be ESM only for newly written code.

  1. Whatever path is taken, it should be considered debt if files are still easily or accidentally able to be CJS.
  2. Whatever path is taken, it should be ready for the so called "3rd goal" problem.
    • Relevant to WASM that is looking to use same stuff as JS
    • Could be relevant with other things that have been proposed like a "use pure" situation if syntax or semantic changes are required.

@bmeck
Copy link
Member

bmeck commented May 11, 2017

@YurySolovyov can you clarify what you mean by "Are there any problems so far in that particular example?" I mean, they do act similar but aren't the same.

@YurySolovyov
Copy link
Author

I mainly meant that they don't introduce ambiguity, right?
Even if we'll have to parse them >1 times, we can with 100% certainty tell which is which, right?

@bmeck
Copy link
Member

bmeck commented May 11, 2017

I am not asking in this issue if ESM can fulfill a use case, I'm asking about use cases where ambiguity is possible / exists. Take for example a simple prefetching script:

import('./something-for-later-1')
import('./something-for-later-2')
import('./something-for-later-3')
import('./something-for-later-4')

Such text is either a Script, CJS, or a Module, but could rely on something pretty easily that causes it to cease functioning:

import(`${__dirname}/something-for-later-1`)

@bmeck
Copy link
Member

bmeck commented May 11, 2017

@YurySolovyov for

const fs = require('fs');
const path = require('path');

const api = function(...params) {
  // ... 
};

module.exports = {
  api: api
};

We can make a good guess that it is CJS if we parse for require() but not all CJS uses require(). We need to define what mechanism are you using to perform these guesses.

@bmeck
Copy link
Member

bmeck commented May 11, 2017

It also would need to ensure no local variable named require exists.

@bmeck
Copy link
Member

bmeck commented May 11, 2017

Also in theory that CJS can parse and eval just fine in Module.

@YurySolovyov
Copy link
Author

Will the absence of import/export be a better sign of CJS than trying to parse requires ?

@bmeck
Copy link
Member

bmeck commented May 11, 2017

If import and export declarations do not exist, it could be either ESM, [Script], or CJS. It is better to say import and export declarations (not import()) signifies something that currently is only able to parse in ESM.

@bmeck
Copy link
Member

bmeck commented May 11, 2017

Detecting CJS really won't work due to amount of overlap in that direction, detecting [something that is only] ESM is possible though.

@YurySolovyov
Copy link
Author

So we can't just always start with ESM and then fallback to CJS if that failed?

Looking at all these parse errors in the table above, I don't think many of them are useful or just make a lot of sense in general.

I have a question though about let, yield, return and await: is this about top-level ones or what?
I can't imagine a module system where you can't use these in function's body.

@bmeck
Copy link
Member

bmeck commented May 11, 2017

No, default needs to be CJS since that is what existing backwards compat needs.

I have a question though about let, yield, return and await: is this about top-level ones or what?
I can't imagine a module system where you can't use these in function's body.

return works in CJS at top level.

let, yield, await, etc. are reserved words in ESM but not in the other goals.

Looking at all these parse errors in the table above, I don't think many of them are useful or just make a lot of sense in general.

Indeed! thats why https://github.com/bmeck/UnambiguousJavaScriptGrammar went to TC39!

Eval errors are much more insidious. Too many chats going on right now for me to complete.

@bmeck
Copy link
Member

bmeck commented May 11, 2017

To note as well: the guess cannot change over time. Once you ship the guessing mechanism it will stay backwards compat (so if something guesses CJS, it will always guess CJS even 10 years from now)

@YurySolovyov
Copy link
Author

Ok, I remember it was proposed at some point that we might want to have import/export declarations as indicators for switching into ESM mode, and it didn't worked out, is that because when switching to ESM you are also switching some parsing rules?

@ljharb
Copy link
Member

ljharb commented May 11, 2017

A file need not have import or export to be parsed as a Module, and parsing a file as a module or a script can definitely break it if you guess wrong.

@bmeck
Copy link
Member

bmeck commented May 11, 2017

@ljharb yup, which is why it needed standardization to remove wrong guesses

@YurySolovyov Jan 2017 TC39 Notes

@YurySolovyov
Copy link
Author

YurySolovyov commented May 11, 2017

A file need not have import or export to be parsed as a Module, and parsing a file as a module or a script can definitely break it if you guess wrong.

What do you mean by "break" ? If the file is valid in some mode, you'll guess until you succeed, or you just report that file is invalid in all of them.

@loganfsmyth
Copy link

The modes have different behaviors during execution time, it is not just about guessing how to parse the file. If you guess that something is an ESM, then this at the top level of the file for instance is undefined, whereas if you guess that it is CSJ then this is the exports object, and if it's a standard script, this is window.

Similarly, guessing something is a module and successfully parsing it means that code will now run in strict mode, but it's just as possible that a given file is a script that will fail when executed in strict mode.

@YurySolovyov
Copy link
Author

If we start with CJS, and that's indeed a module written with CJS in mind, we're ok.
If we start with CJS and it fails because of import/export, then the module just have to have valid ESM syntax, otherwise it would fail in ESM anyway.

I don't think we should try to "make the most sense" of invalid modules.

@bmeck
Copy link
Member

bmeck commented May 11, 2017

@YurySolovyov I think the point being made is that suddenly valid ESM is being treated as CJS. The implication being, ESM and CJS ambiguity shouldn't change how code evaluates. Hence attempt to pass a standard to remove the ambiguity.

@YurySolovyov
Copy link
Author

Ah, now I get it, I think.
Since so far node only had CJS, I'd expect any ambiguous code to end up in CJS mode, since that's first thing that "succeeds".
So if one wants this code to be ESM, you need to switch it manually, which I totally agree won't look very pretty. (export {} ?)
I'd like to know the use-case for such code though.

@ljharb
Copy link
Member

ljharb commented May 11, 2017

A polyfill, that is imported for side effects, that relies on implicit strict mode. import 'foo' and require('foo'); would have very different effects unless there's a way to ensure foo's "main" is parsed deterministically as a Script or a Module. A file extension (.mjs in this case) is the easiest and most appropriate way to describe how a file should be parsed - that's what extensions are for.

@YurySolovyov
Copy link
Author

A polyfill, that is imported for side effects

Can you just import it with require then?
Given the purpose of polyfills, I don't expect them to be imported in most of the modern envs that have proper modules.
You can also just export and invoke a function that conditionally performs polyfilling

@ljharb
Copy link
Member

ljharb commented May 11, 2017

@YurySolovyov yes but then that means the consumer has to know what kind of module it is - and you shouldn't have to know that.

@bmeck
Copy link
Member

bmeck commented May 11, 2017

@YurySolovyov that was one approach that was discussed in great depth, it means CJS permanently exists in all ESM of Node though, so no path towards a --esm-only flag etc. It also causes upgrade problems; consider the following:

import "dep";

If the mode of "dep" must be known:

  • If "dep" is CJS: we cannot have any of our modules written purely in ESM since they must use require
  • When "dep" is upgraded to ESM: all consumers must change to use import

If the mode is not specified by how it is loaded:

  • If "dep" is CJS: it acts like an ESM with a single default export
  • When "dep" is upgraded, consumers using import are unaffected assuming it keeps the same single default export compatible with CJS versions.

It is safe to move to ESM even if your dependencies are not known if we provide a safe facade like the single default export approach.

@bmeck
Copy link
Member

bmeck commented May 11, 2017

moved into above

We should also list other things outside of polyfills like:

  • prefetch one above
  • one offs that just affect globals like sourcemap-support
  • some configuration bootstraps (webpack configs sometimes)
  • single file scripts (simple tests not using repl realistically)
  • non standard modules (the amd loaders on npm)

the prefetch use / anything that only uses import() is the most trouble to me

@bmeck
Copy link
Member

bmeck commented May 11, 2017

It gets into more trouble once source texts are combined when taking the parsing approach, that means in some 5000 line file, like 1337 might be an export that changes how the whole file works. Thats a bit of a needle in the haystack to find.

You might even remove that line and accidentally change how the whole file works as well.

Or you might accidentally add an export that swaps a CJS file. [via file concat or somesuch]

[ /me thinks of stack overflow mode poisoning w/ import/export ]

@bmeck
Copy link
Member

bmeck commented May 12, 2017

Problemspace comment I think is in a decent place now

@vkurchatkin
Copy link

how is breaking everything else already working with .js backward compatible in your opinion?

What exactly is going to break?

A file that use export const NUM = 123; is never ambiguous to me, browsers, SpiderMonkey, and JSC (plus others) ... only Node has issues with it.

That's because Node supports CJS modules.

@WebReflection
Copy link

WebReflection commented Oct 15, 2017

What exactly is going to break?

Two things:

  • servers not updated will be unable to serve JS modules to browsers
  • even if you import explicitly an .mjs file that does not guard against ambiguity. Developers omit module extensions so they don't know what they are loading and authors are incapable of publishing modules that resists transpilers once eventually re-bundled.

Proof

I am the author of module ok, published as on.mjs

export default function ok(cond) {
  console.assert(cond);
};

The Node author of module test writes this file as test.mjs:

import ok from 'ok';
function notok(cond) {
  console.assert(!cond);
}
export {ok, notok};

Now test.mjs is transpiled and published as test.js to not break backward compatibility ... right?

Question

What do you expect from the following files?

index.mjs

import {ok, notok} from 'test';

ok(true);

index.js

console.log(Math.random());

Question 2

Assuming ok module gets transpiled too, what do you expect from this indx.mjs file?

import {ok, notok} from 'test';

ok(true);

I really would like to see how much ambiguity .mjs solved as a goal here ... looking forward to your straight-back answers without even trying the code


That's because Node supports CJS modules.

AFAIK Node is moving away from CJS, isn't that the long term plan?

Keep .js to CJS and see that'll never happen.

@vkurchatkin
Copy link

servers not updated will be unable to serve JS modules to browsers

That is not a breaking change. Browsers never were and never will be able to run arbitrary Node code.

even if you import explicitly an .mjs file that does not guard against ambiguity.

For module loader, it does.

Now test.mjs is transpiled and published as test.js to not break backward compatibility ... right?

Transpiling has absolutely nothing to do with this discussion. Babel's output is inherently not spec-compliant.

@demurgos
Copy link

demurgos commented Oct 15, 2017

one thing: backwards compatibility.

how is breaking everything else already working with .js backward compatible in your opinion?

What exactly is going to break?

Two things:

  • servers not updated will be unable to serve JS modules to browsers
  • even if you import explicitly an .mjs file that does not guard against ambiguity. Developers omit module extensions so they don't know what they are loading and authors are incapable of publishing modules that resists transpilers once eventually re-bundled.

Backward compatibility is about not breaking any existing code. If you use the new features, it is not existing code but new code. It sounds a bit as if you were complaining that Symbol is not backward compatible because now typeof can return a new value. If you don't use Symbol, your code won't even notice it got introduced: this is backward compatibility, you have to opt-in for the change. It's similar here: unless you decide to use ES modules (you opt-in), the fact that Node introduces support for .mjs won't break any existing code.

What existing code would be broken by the introduction of .mjs?

Edit: And I approve the message above. As I told in an earlier message, one of the main reason for native support of ES modules is that transpilation + cjs will never be able to be spec-compliant.

@WebReflection
Copy link

WebReflection commented Oct 15, 2017

you have to opt-in for the change

nobody can because they all have to rename their files, their imports, and their code that works already in every browsers and target Node too, because NPM is used for both browsers and node and I swear you can write Node targeting code without needing any of the Node core.

What existing code would be broken by the introduction of .mjs?

The problem is not that my code breaks in Node if I write .mjs, the problem is that Node is incapable of loading as ESM every single .js file I have that works already in both browsers and SpiderMonkey and JSC but cannot land in Node as valid ECMAScript module.

I have to publish two damned identical ES2015 files and I've no idea what to put in the package.json because the code that should just work everywhere cannot be loaded as it's meant to be loaded whcih is bloody ESM via import in NodeJS.

Do you guys ever write cross platform code?

I target Node, browsers, and internet of Things engines.

I don't live in a Node-only bubble.

How is it even possible nobody is understanding that the real issue is that Node is incapable of doing what every other environment does by default?

@WebReflection
Copy link

Anyway. I'm done here.

As previously mentioned, my ideal world solution is here and it solves every real-world issue.

If you don't like .m.js which is my second favorite solution (seriously everything but .mjs) at least consider that explicit --esm flag which enables always ES2015+ for import and keep require around for legacy CJS modules.

Best Regards.

@ljharb
Copy link
Member

ljharb commented Oct 15, 2017

It's going to end up being pretty easy to make a foo.mjs (ESM) and foo.js (CJS ES5) file alongside one another; either manually (if you insist) or via a build process - bundlers will be able to transparently pick up whichever format they want, node foo will work if both have a shebang, regardless of node version, non-node tools will be able to trivially determine when to set type="module" and the proper MIME, etc.

The story for all these use cases would not be nearly so simple with a different approach.

@WebReflection
Copy link

WebReflection commented Oct 15, 2017

It's going to end up being pretty easy to make a foo.mjs (ESM) and foo.js (CJS ES5) file alongside one another

I don't want that. I want a foo.js file that is ESM and works everywhere including Node.

I don't use transpilers, I don't like them, I don't want to be force by Node to use them, neither I like to be forced by Node to write .mjs files nothing and nobody at this point in time understand or use.

bundlers will be able to transparently pick up whichever format they want

bundlers have never been an issues. Modules shouldn't require bundlers by defaults.

The story for all these use cases would not be nearly so simple with a different approach.

The PR with --esm is deadly simple and it solves ambiguity and it paves a migration path to ES2015+ modules only instead of enabling people to easily stick with CJS forever.

If you don't like .m.js, please consider that alternative.

@ljharb
Copy link
Member

ljharb commented Oct 15, 2017

If a switch is provided that makes all .js files be treated as ESM, it will at best break on most of the npm ecosystem, and at worst it will silently do the wrong thing. It's the same reason that the flag to enable strict mode everywhere breaks horribly; because sloppy code is written to be sloppy, and needs to remain so. Similarly, CJS needs to remain CJS, even if it was published on npm 5 years ago and will never be updated (for example)

@demurgos
Copy link

demurgos commented Oct 15, 2017

I am trying to understand your examples in your previous comment (with the two questions):

For both cases I expect it to run successfully: load the ok function and do the assertion.

Does it corresponds to the code in the following repo: https://github.com/demurgos/mjs-example ?

I can either run it with ES modules enabled, or "transpile" ok and run it without enabling ES modules:

# Use ES entry point (with ES support)
node --experimental-modules index.mjs
# Simulate transpilation of `ok.mjs`
mv _ok.js ok.js
# Use cjs entry point (without ES support)
node index.js

What is the problem? Could you provide an example repo if I understood your scenario wrongly?

Edit: Just clone the repo and run the commands: it does work.

@WebReflection
Copy link

WebReflection commented Oct 15, 2017

it will at best break on most of the npm ecosystem

no, it's a flag. developers know what they are doing (at least some of them). nothing breaks, already tried that.

It's the same reason that the flag to enable strict mode everywhere breaks horribly; because sloppy code is written to be sloppy, and needs to remain so.

no, you require CJS modules via CJS. Only core and real ESM modules are allowed as ESM imports.
No ambiguity, nothing breaks. It's a moving forward, future proof, not interfering with the ecosystem, pattern.

Similarly, CJS needs to remain CJS, even if it was published on npm 5 years ago and will never be updated (for example)

You load that via require instead of import and problem solved.
You have a clear indication of which module is old/legacy and needs updates and which one is new.


For both cases I expect it to run successfully:

no, everything breaks always. I'm done with this thread. I am unsubscribing.

@ljharb
Copy link
Member

ljharb commented Oct 15, 2017

Again, I must be able to import CJS - otherwise users of my module will be forced to deal with a breaking change were I to migrate to ESM, which means I won't migrate to ESM - and neither will many module authors. That will kill the future we all want where ESM is the only module format.

@tilgovi
Copy link

tilgovi commented Oct 16, 2017

I do not wish to endorse a specific path forward, but perhaps I can clarify a point where I see parties talking past one another.

These two claims seem to keep coming up, without a resolution.

  • Whatever node does, it should allow a package author to upgrade to ESM with breaking consumers.
  • With ".mjs", an author can't simply transpile because consumers still have to know to get the default property of the package exports.

Making it possible for a consumer not to know the format of a module is difficult. It is unreasonable to expect this without some work from authors. ESM distinguishes between the default export and the module namespace while CJS does not.

The following pattern requires a little bit of work, but address the issue for some cases.

index.js

// Notice that the default export is lifted here, for CJS consumers.
// This is taking care of interopRequireDefault for non-babel, CJS consumers.
module.exports = require('./lib/index.js').default;

index.mjs

export * from './lib/index.mjs';

This approach does require that the author ensure that the default export of ./lib is its namespace, otherwise the results are different.

The situation is a little trickier when consumers want to do deep imports/requires. My intuition is that there is a pattern that would work for this, but would need to thinking further to see exactly how.

@ljharb
Copy link
Member

ljharb commented Oct 16, 2017

Well said. For deep imports, it'd be the same thing; for each entry point, include a .js file that requires and extracts .default - or for maximum backwards compat, author in ES5 CJS and manually create a .mjs file for each entry point.

@bmeck
Copy link
Member

bmeck commented Oct 16, 2017

Again, I must be able to import CJS - otherwise users of my module will be forced to deal with a breaking change were I to migrate to ESM, which means I won't migrate to ESM - and neither will many module authors. That will kill the future we all want where ESM is the only module format.

Right now CJS cannot take the shape of all ESM when imported thats why efforts are going on with tooling about how to fully support this. There is some push back because it would encourage people to just ship CJS+pragma in some people's eyes.

With ".mjs", an author can't simply transpile because consumers still have to know to get the default property of the package exports.

This isn't related to .mjs but to the fact that import is allowed to load CJS. There are similar effect to JSON files and C++ addons as well. I think this relates to my previous comment, but your solution seems workable.

I would like to see better first class support for generating Module Namespaces so that people can implement various module types land as we get user feedback from the loader hooks, as this would allow more rich integration with other module systems. That will take time though.

devsnek added a commit to devsnek/node that referenced this issue Nov 2, 2017
I know a lot of discussion went into the original decision on how mjs
would handle cjs modules but after using the system for a while, and
talking with a lot of other people in the community, it just seems
like the expected behavior and the wanted behavior is to export named
based on the keys. This PR implements that in what is hopefully a
performant enough solution, although that shouldn't be too much of a
problem since this code only runs during initial module loading.

This implementation remains safe with regard to named exports that are
also reserved keywords such as `class` or `delete`.

Refs: nodejs/node-eps#57
@cztomsik
Copy link

cztomsik commented Dec 12, 2017

Is this --esm flag ever going to happen before somebody will just fork node again?

BTW: I am currently using reify as a workaround but .mjs is wrong decision in the long term.

@drzraf
Copy link

drzraf commented Jan 27, 2018

Given the following: index.js, es6moduleA.js
If I symlink es6moduleA.js as es6moduleA.mjs, then
index.js can successfully import "es6moduleA.mjs"
(nodejs --harmony --experimental-modules --preserve-symlinks ) and es6moduleA.js is still usable by browsers.

But as soon as es6moduleA.js itself import 'es6moduleB.js', you're stuck:

  • Either it's imported using the .js extension (code works with browsers, file is served application/javascript by Apache, Nginx, php built-in webserver, ... ...)
  • Either it's renamed and imported using .mjs and javascript-code start to be node.js-only because most tooling is not compliant with this new extension and because JS-engines must not consider ES6 modules served using a wrong mimetype

Currently, a brand-new purely ES6 project, following the language syntax (using *.js file extension and working in most browsers) would not only just fail under node.js but there is currently no command-line parameter to make it work.

@ljharb
Copy link
Member

ljharb commented Jan 27, 2018

Extensions are not part of the language; there’s nothing “correct” about using .js except for what it conveys by convention: that it’s a JavaScript Script.

Browsers ignore extensions entirely; the point of them is to inform the server how to parse the file (to tell it what kind of file it is, to inform the mime type etc).

@drzraf
Copy link

drzraf commented Jan 27, 2018

Following this reasoning servers are in charge of dealing with extensions -> mimetype (w.r.t to IETF)
but they do not support mjs (and from the PoV I guess there is a few reason to support it).
Still node.js, which is not a server does not even offer an option to be extension-agnostic.

Given that there’s nothing “incorrect” in not using .js, a simple --esm is a must have so that even script.jpeg could be interpreted as an ES6 module.

@ljharb
Copy link
Member

ljharb commented Jan 27, 2018

They don't support it yet; at one time, long ago, they didn't support .js either. As soon as node ships .mjs unflagged, all the servers that want to stay relevant will ship support for it.

node certainly could take any extension - or even an extensionless file - and choose to parse it however it wants (including obeying a command-line argument). However, that wouldn't be very useful, and it would almost certainly hide bugs and mistakes.

@bmeck
Copy link
Member

bmeck commented Jan 27, 2018 via email

@drzraf
Copy link

drzraf commented Jan 29, 2018

Nevermind, I just discovered the (much awaited) PR nodejs/node#18392

@caub
Copy link

caub commented May 13, 2018

just on a lighter aesthetic aspect, I'd have liked .es

@tbranyen
Copy link

@caub Folks are already using mjs, IMO if Node chooses to use a new extension, it's far too late to try proposing a change.

@cztomsik
Copy link

cztomsik commented May 15, 2018

@tbranyen I personally don't know anybody who does, they use reify/esm

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