Rationale for dropping ModuleImport syntax?
I am beginning to come around to the removal. It will just encourage module authors to use default exports exclusively (e.g. fs will default-export an object including readFile et al., instead of exporting multiple functions), which will put us squarely back in parity with the CommonJS/AMD systems, and ease migration.
If module authors try to use multi-exports, they will get upset users who wish to be able to do import _ from "underscore"; _.zip(...)
, but cannot. So instead they will use default exports only, which does allow this familiar style.
In the end all of the non-default export forms, and the braced multi-import form, will just be relegated to the "bad parts" bin, and default exports and unbraced single-import will remain in the "good parts" bin, giving us the same CommonJS/AMD world we have today, but with some vestigial syntax unused by popular libraries.
Isn’t the problem, though, that default-exporting an object prevents static checking? It feels like an abuse of this feature to me.
With a ModuleImport statement, you have the choice between selectively importing items and importing everything as an object. With default-exporting an object, you don’t have that choice (you can work around this limitation, but still).
I do see your point, but I think module imports are useful when the contents of a module change relatively frequently and/or if there are many items in a module. In other words, I think that is something that will live on (justifiedly so) and should be supported properly.
Why can't we have this?
// when only default exports are used
// import that one function
import mkdirp from "mkdirp";
// when only named exports are used
// import it like a module
import fs from "fs";
// when both named and default exports are used
// this imports the default export
import when from "when";
// and the named ones can only be imported via
import {all, map} from "when";
This way modules with one export work well and bags of utilities also work well. The third case is a little trickier but it's not much worse compared to how it would have been with a ModuleImport syntax, since if you want to use both default export and named exports in the same file you would end up with something like this:
module whenModule from "when";
import when from "when";
whenModule.map([when(foo), when(bar)]);
So ideally it should be possible to do
import when from "when";
import {all} from "when";
// when is a function and also has named exports attached
when.map([when(this)]);
all(promises);
From: Axel Rauschmayer [mailto:axel at rauschma.de]
Isn't the problem, though, that default-exporting an object prevents static checking? It feels like an abuse of this feature to me.
We don't have static checking today, so this is no loss to me. (And ES6 modules give enough benefits over ES5 ones without static checking to still have a chance in the marketplace, e.g. they statically require imports being at top-level and string-only, and automatically introduce "use strict"
for you.)
Isn't the problem, though, that default-exporting an object prevents static checking? It feels like an abuse of this feature to me.
We don't have static checking today, so this is no loss to me.
If I understand ES6 modules correctly, importing a non-exported identifier gives you a load-time error (that’s what I meant with “static checking”). If you default-import an object with exports, you only get run-time errors.
This is more subjective, but what I like about modules is that they lead us away from objects-as-modules. If default exports, used in this manner, become popular, that won’t really happen. We’ll have pseudo-modules, used inside a module system.
(And ES6 modules give enough benefits over ES5 ones without static checking to still have a chance in the marketplace, e.g. they statically require imports being at top-level and string-only, and automatically introduce
"use strict"
for you.)
I agree. I also love tools such as the es6-module-transpiler, which allow us to move beyond the AMD/CJS schism right now.
If the 'module' form is left out, it can be added later. If the 'module' form is left in, it can never be removed. jjb
On the other hand, we’ll have many pseudo-modules, which is also a barrier against making progress later on.
On Mon, Jun 9, 2014 at 6:54 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
On the other hand, we’ll have many pseudo-modules, which is also a barrier against making progress later on.
Sorry, I don't understand what a pseudo-module is. Are you saying that the core import/export system is not correct and that we should have a system based exclusively on 'module'?
jjb
I’m assuming that people will default-export objects (for Underscore.js-like libraries). I’d call those pseudo-modules, because one would be partially working around the module system (no load-time errors!).
Maybe we’ll import modules like this ^1, but that feels syntactically inconsistent to me and you don’t get load-time errors, either:
import "Underscore";
const _ = System.get("Underscore");
I would like to see a reigning in of uses of curly braces. We already have at least these:
- code blocks
- literal objects
- destructuring
I don't like that the module syntax adds yet another. I'd like to think of the curly braces in the module syntax as being like destructuring. That way the mental model could be that whenever you see curly braces on what is conceptually the left-hand side of an expression, you can at least think of it as being like destructuring.
So the following would mean I want all the exports as a single object: import fs from "fs";
and the following would mean I only want some of the exports from a module: import {mkdir, write} from "fs";
Maybe the following could be used to get the "default" export from a module: import default fsd from "fs";
I agree with Alex.
We can tolerate this syntactic form being dropped for now, but that doesn't eliminate the semantic need. If the "module" contextual keyword is a problem, then we should be able to come up with another color for the bikeshed, e.g.:
import "foo" as foo;
As an aside, it is yet to be seen whether the "default" export thing is the best way, or the bad part itself. We don't have the real world experience yet to answer that.
As an aside, it is yet to be seen whether the "default" export thing is the best way, or the bad part itself. We don't have the real world experience yet to answer that.
I’d even argue that they led to the predicament that we are currently in.
If the default export didn’t look like “the module”, things would, in my opinion, be easier to understand:
import _ from "Underscore";
import { flatten, union } from "Underscore";
import default someFunction from "single_function_module";
My perspective here is that there are not too many modules (in nodejs) that rely on more than a handful of exports from a particular module, we are actively working on validating this using esprima in a large set of npm modules. If this is true, we should be just fine with specific imports, and for the edge cases, an imperative form should be sufficient.
For now, I will ask you all to try to find a modules that are using too many exported methods from one of its imported modules, you will be suprise how hard it is too find those.
Traceur definitely has a lot of exports in a single module.
google/traceur-compiler/blob/master/src/syntax/Parser.js#L15
We do not however, use the module
form since we want to get rid of the
extra Get (which deopts switch statements in some engines).
"esparse" used to have a module which exported a huge list of things, and for the same reason I believe.
Basically there's an "AST" module which exports a huge list of AST node-type classes. That module is then exported to the public interface.
zenparsing/esparse/blob/53566066a9d1780bb27f73d208160595e2e0bb47/src/AST.js
I moved away from this approach only for performance reasons (basically to avoid inheritance) because I'm optimizing hot, transpiled ES6 -to-ES5 code. Were it not for the (somewhat ugly) performance optimization, I would have left the multi-export design.
On Mon, Jun 9, 2014 at 7:51 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
I’m assuming that people will default-export objects (for Underscore.js-like libraries). I’d call those pseudo-modules, because one would be partially working around the module system (no load-time errors!).
Then aren't you arguing against default-export?
Maybe we’ll import modules like this ^1, but that feels syntactically inconsistent to me and you don’t get load-time errors, either:
import "Underscore"; const _ = System.get("Underscore");
Seems to me that users will write
import {_} from 'Underscore';
because the underscore team will write their ES6 modules to support this syntax.
These examples mix issues. If you want to rename a passel of imports, you necessarily have a passel of renamings to write. That is not the fault of the module syntax. Similarly, if the module author chooses to export a long list of functions, write a long list of imports or find another library. Finally, analysis based on node modules assumes a function-oriented or object-based language, but ES6 is a class-based language: based on our experience the majority of exports going forward will be classes.
jjb
On Mon, Jun 9, 2014 at 9:08 AM, John Barton <johnjbarton at google.com> wrote: [...]
but ES6 is a class-based language: based on our experience the majority of exports going forward will be classes.
Hi John, that sounds interesting. What more can you tell us about that experience?
Pirouette also has many exports per module for its bindings:
E.g. toshok/pirouette/blob/master/bindings/uikit.js.
I use both import-{}-from (with many imported bindings) and module-from forms, tending toward the former in framework code and the latter in application code.
The fact that the both forms can be accommodated from a single export form is the key for me.
but ES6 is a class-based language: based on our experience the majority
of exports going forward will be classes.
Hi John, that sounds interesting. What more can you tell us about that experience?
My experience also agrees with John's. It just makes sense from a design-perspective to organize larger code bases around noun-concepts, to which ES6 classes are very elegantly suited.
If you feel like browsing code, these are all good examples to look at:
zenparsing/ziptar, zenparsing/moon-unit, zenparsing/esparse
There are modules which export functions, and modules which export classes, but the class-modules tend to dominate the large-scale structure.
Chris, the number of exports is not relevant, and in fact, there is no way
to export all members in one go, which aligns well with the proposal to
remove the way to import an object with all members. check the consumers of
the uikit
module, and count how many of those exported methods are used
in a single module. In fact, this repo is consistent with what we have been
saying. If you look at
toshok/pirouette/blob/master/bindings/objc.js, it uses
module objc_internal from '@objc_internal';
, then it uses 5 methods
exported by that module, which should not be a pain to declare them
explicit as imports.
Chris, the number of exports is not relevant, and in fact, there is no way to export all members in one go, which aligns well with the proposal to remove the way to import an object with all members. check the consumers of the
uikit
module, and count how many of those exported methods are used in a single module. In fact, this repo is consistent with what we have been saying. If you look at toshok/pirouette/blob/master/bindings/objc.js, it usesmodule objc_internal from '@objc_internal';
, then it uses 5 methods exported by that module, which should not be a pain to declare them explicit as imports.
True, but that analysis doesn't apply to the example that I and Erik have shared.
In sum, there are valid use cases for importing a large number of things from a module.
IIUC, the motivation for dropping the form is that it's confusing to have this other syntactic option which uses a different (contextual) keyword.
Well, we already have this:
import "./foo";
which you can use when you want to load+execute but not import anything. Is that going away? If not, then it makes sense to just add a renaming clause:
import "./foo" as foo;
Does that help with the confusion?
import "./foo" as foo;
certainly looks nice.
On Mon, Jun 9, 2014 at 9:17 AM, Mark S. Miller <erights at google.com> wrote:
On Mon, Jun 9, 2014 at 9:08 AM, John Barton <johnjbarton at google.com> wrote: [...]
but ES6 is a class-based language: based on our experience the majority
of exports going forward will be classes.
Hi John, that sounds interesting. What more can you tell us about that experience?
Traceur is >30kloc of es6: google/traceur-compiler. It
has 774 exports, roughly split three ways with slightly more class and var than function. Many of the var exports are constants. So I was incorrect on 'majority', but it feels like a majority because the class structure creates a deep graph while the other two tend to be shallow. Plus the classes and objects have properties as their focus which broadens the impact of the import. (We have 9 uses of export default; about 5 uses of module..from, but this could be a style issue).
That particular use of module-from is a little special. "@objc_internal" is a native module. Once ejs supports enumerating multiple exports from native modules I'll be switching that to an import-{}-from.
An example of use would be my toy test applications that consume the modules:
toshok/echo-js/blob/master/test/osx-test/helloosx.js
This uses the import-{}-from syntax and as you can see it's getting rather long for appkit (and it only has a window, a button, and a table view). In my other toy apps I've switched exclusively to module-from to keep things from getting out of hand while I work. I thought I'd also added an iOS test to the git repo, but apparently I haven't - will rectify today.
I would also suggest that supporting both forms allows for better early prototyping (module-from) when you don't know exactly what you'll be pulling in, with the optional upgrade to potentially faster (definitely faster in ejs currently) import-{}-from form later when your code matures, all while maintaining easy static checking.
And the as portion can be optional if all you want is import side effects.
Why can't we have both with the same syntax?
// imports a function which is the default export
import mkdirp from "mkdirp";
and
// imports all named exports, like module used to
import fs from "fs";
On Mon, Jun 9, 2014 at 7:40 PM, Karolis Narkevičius <karolis.n at gmail.com>
wrote:
Why can't we have both with the same syntax?
// imports a function which is the default export import mkdirp from "mkdirp";
and
// imports all named exports, like module used to import fs from "fs";
According to the spec 1 (IIUC) you can import both all/some of the named export values and the default export value, due to the second option of the ImportClause:
ImportClause :
ImportedBinding
ImportedBinding , NamedImports
NamedImports
Does this mean that a module can export both a default value and several named values? If so, libraries like Underscore would need to use both namedExport and defaultExport to suite all users. Or would the default value be an object containing all the named export values? If so, it seems like your suggestion is already in the spec.
ImportClause : ImportedBinding ImportedBinding , NamedImports NamedImports
Side topic, but this particular production:
ImportClause: ImportedBinding , NamedImports
needs to die an immediate death.
Or would the default value be an object containing all the named export values?
No - the default has no default, as it were.
On Tue, Jun 10, 2014 at 1:13 AM, Kevin Smith <zenparsing at gmail.com> wrote:
ImportClause : ImportedBinding ImportedBinding , NamedImports NamedImports
Side topic, but this particular production:
ImportClause: ImportedBinding , NamedImports
needs to die an immediate death.
Yeah, ModuleImport isn't the only thing that needs to be removed from the spec.
Or would the default value be an object containing all the named export values?
No - the default has no default, as it were.
So if the module does not define a default export, then it must be imported
using the import {foo} from "foo"
syntax, and not the import bar from "bar";
syntax. And if it only has a default export, then it must be
imported using the second statement (import bar from "bar"
) and not the
first (import {foo} from "foo"
). Without looking at the source of the
module you wish to import you cannot know which of these two import
statements to use.
But, IIUC, the spec lets you export both a default value and named values. Most libraries will have to use both of these exports, so users can choose which import statement to use. So for underscore.js:
var _ = { /*....*/ };
export default = _;
export {_.map as map, _.reduce as reduce, _.filter as filter, /*...*/};
But there is nothing to ensure that the two exports are the same object (which for underscore will likely lead to bugs):
export default = 5;
export {1 as a};
//in another module
import {a} from "sillyModule";//a is 1
import a from "sillyModule"; //a is 5
This will likely lead to a lot of confusion, not only for module makers but also for module consumers.
Marius Gundersen
From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Marius Gundersen
This will likely lead to a lot of confusion, not only for module makers but also for module consumers.
Agreed. Which is why I predict module makers will, at the encouragement of module consumers, stick to default-export only, since it is more in line with existing practice.
On Tue, Jun 10, 2014 at 9:06 AM, Domenic Denicola < domenic at domenicdenicola.com> wrote:
From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Marius Gundersen
This will likely lead to a lot of confusion, not only for module makers but also for module consumers.
Agreed. Which is why I predict module makers will, at the encouragement of module consumers, stick to default-export only, since it is more in line with existing practice.
That works around all of the static analysis made possible by the current spec. It is impossible to do static analysis on a defaultExported object. It would be unfortunate if all the time and effort spent on making module exports statically analysable is for nothing simply because the real world finds another way to use modules.
I'd say we only support named exports, something like this: gist.github.com/mariusGundersen/88a4c5690e08da0d07f6
Marius Gundersen
Agreed. Which is why I predict module makers will, at the encouragement of module consumers, stick to default-export only, since it is more in line with existing practice.
FWIW, I predict the opposite, but I could be wrong. A really good thing about the current design is that it allows both possible futures to flourish.
Let's stick to the status quo. If there are cosmetic tweaks that need to
be made to ModuleImport
, then let's do that instead of reopening old
debates without bringing new information. Let's not forget that the
current design is the result of a hard-earned compromise (a year old, BTW).
From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Marius Gundersen
I'd say we only support named exports, something like this: gist.github.com/mariusGundersen/88a4c5690e08da0d07f6
If you do that, the real-world consequences will be even worse. Nobody (to a first approximation) will use ES6 modules at all, as they will be entirely incompatible with how modules are used today by both AMD and CommonJS communities.
The fact is, static verification of exports---which is notably different than static analysis of imported module IDs---is not something any noticeable segment of the existing module-using community has been crying out for. Nobody has even developed a linter to check for that sort of thing! So it's largely something that TC39 came up with and championed as important, meeting with indifference from the (majority of the) community. It's a nice side benefit of certain usage patterns of ES6 modules, but the recent removal of module x from "y"
tips the scales from "slightly inconvenient to adapt to" toward "too inconvenient to use in practice."
Remember, our target audience is not people who read es-discuss, sometimes program in static languages, and are intimately familiar with the tradeoffs between different module systems and usage styles. It is existing AMD and CommonJS communities of real-world JavaScript practitioners, who are already confused and exasperated as to "why doesn't that silly committee just adopt CommonJS" (or AMD).
I've worked hard over the past year or so to try to convince some of those practitioners of the benefits of statically-analyzable imported module IDs, which is something they can understand (given how it makes tools like browserify more feasible, and prevents the edge-case failures of AMD's CommonJS sugar). I've met with some mixed success, despite the committee's frankly confounding focus on statically-verifiable exports, or magic with
-esque aliasing-bindings, throwing up roadblocks along the way. But it's an extremely delicate balance, and at this point I am still not convinced (even without the recent round of usability-reducing changes) that ES6 modules have a chance in the marketplace of ideas.
If we don't have a really clean and simple upgrade path for all existing users of module systems, I can't see ES6 modules gaining widespread adoption. If nobody objects to that core premise, we must make it easy for users and producers of modules like underscore, as well as producers and consumers of modules like mkdirp, to upgrade.
Current mkdirp style module:
module.exports = function mkdirp(path) {
// code here
}
Current consumer of an mkdirp style module:
var mkdirp = require('mkdirp');
// code here
This is easy to upgrade with default exports as it becomes:
export default function mkdirp(path) {
// code here
}
and
import mkdirp from 'mkdirp';
Note that it's critically important that I can call my mkdirp function whatever I like. e.g.
// The hyperquest module is just one implementation of the "request" function.
// It is important that I don't have to care what hyperquest calls its function internally.
import request from 'hyperquest';
For underscore we currently have:
exports.map = function (array, fn) {
// code here
};
exports.filter= function (array, fn) {
// code here
};
// many more functions here
and
var _ = require('underscore');
// lots of code here
It would be nice for consumers who just want the map function to be able to indicate that, as it will allow minifiers to more easily remove unused code, but we need to focus on making sure that the upgrade path is easy for existing people, or they simply won't upgrade. As such I would like to see the producer look like:
export function map(array, fn) {
// code here
};
export function filter(array, fn) {
// code here
};
// many more functions here
and then the consumer should ideally look like:
import _ from 'underscore';
I would then propose that we allow modules to have either a default export, or named exports, but not both. This way users of existing module systems just have to learn the import _ from 'underscore';
syntax to get started, and can graduate to importing just the functions they need later. We could have import 'underscore' as _;
, but that doesn't match the import syntax for a module with a default export, which I think creates unnecessary confusion.
These and other options have been discussed on es-discuss over the past 2 or 3 years, but didn't win out. (I'm having trouble finding links at the moment, unfortunately.) No new information is being presented here.
Again, the current design was a hard-earned compromise and should not be tinkered with.
Please, cosmetic changes only! : )
Please, cosmetic changes only! : )
Fair enough. In that spirit, how about we keep the functionality that was recently dropped, but fix the strange wording of it (a cosmetic change) so that it becomes:
import 'underscore' as _;
as has been suggested by other people. It's not ideal, but we then end up with three ways of importing a module:
Single default export:
import mkdirp from 'mkdirp';
Many named exports:
import 'underscore' as _;
Individual named exports:
import {map} from 'underscore';
That is a small cosmetic change (relative to what was the proposal until a few days ago) but, I believe, provides all the required functionality. This has already been proposed by others in this thread, and i don't think I've seen any meaningful criticism of the idea?
These and other options have been discussed on es-discuss over the past 2
or 3 years
But back then there was no real world usage yet? Shouldn't new feedback be taken into account?
(bikeshed: don't leave out this option for the syntax import module from "underscore"
)
(bikeshed: don't leave out this option for the syntax
import module from "underscore"
)
Unfortunately, this one won't work because "module" in this context is a plain ol' identifier. AFAIK, there's no interest in making "module" a proper keyword. (Right now it's just a contextual keyword.)
These and other options have been discussed on es-discuss over the past 2 or 3 years
But back then there was no real world usage yet? Shouldn't new feedback be taken into account?
If there is new data it should definitely be considered. I just don't see any new data right now that changes the arguments that have come before.
Also, I and many others have been experimentally coding with transpiled ES6 modules for quite a while (~2 years?), so we've had some measure of real-world usage for some time.
On Tue, Jun 10, 2014 at 5:19 AM, Domenic Denicola < domenic at domenicdenicola.com> wrote:
From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Marius Gundersen
I'd say we only support named exports, something like this: gist.github.com/mariusGundersen/88a4c5690e08da0d07f6
If you do that, the real-world consequences will be even worse. Nobody (to a first approximation) will use ES6 modules at all, as they will be entirely incompatible with how modules are used today by both AMD and CommonJS communities.
This is my primary concern as well. I know from conversations with Node developers (some of whom are connected to the development of the platform itself) that many, if not most, of them are dubious about the benefits of the new module system. I tend to be pessimistic, so I also tend to write down my intuition somewhat, but based on the conversations I've had within the Node community, I would be surprised if a statistically meaningful number of the modules on npm end up being rewritten to use ES6 module syntax (once it's even available in V8). If you make changes that affect the usability of either single export or single import of multiple export, you're in effect making the headwinds that much stiffer. I think Domenic and Kevin's concerns about messing with a fragile consensus are entirely warranted.
My broader concern is that it's very late in the specification process to be proposing these kinds of changes, and it feels like the proposals on the table violate the spirit of the design process that's been proposed for ES7 and beyond. I know that ES6 is still governed by the old rules, but if the goal of the future process is to have the volume of changes to proposed features converge on zero as the feature moves through the process, that doesn't seem like it has to be something that blocks on the formal adoption of a new process.
F
I've been thinking about this thread a lot the last couple days. I started out feeling defensive of the current proposal but I think I do agree that the idea of live bound imports is neat it's also not something I'm asking for or planning to use in the near term. I really just want single exports and destructuring of single exports...
Is there a way to eventually spec the 'value as getter' concept separately?
I really just want single exports and destructuring of single exports...??
I would second that. I have seen no desire for any static analysis beyond "this module depends on that module" and I've seen no desire for live bound imports.
I accept that we're messing with a fragile consensus, but the removal of ModuleImport syntax has already done that, so we're way beyond purely cosmetic changes. I think if this is done wrong it will just do more harm than good, and further fragment the ecosystem. We need something that makes the upgrade path for CommonJS users really simple and clear.
I would second that. I have seen no desire for any static analysis beyond "this module depends on that module" and I've seen no desire for live bound imports.
I believe that viewpoint is adequately represented in the status quo. No need to legislate other viewpoints away.
I accept that we're messing with a fragile consensus, but the removal of ModuleImport syntax has already done that, so we're way beyond purely cosmetic changes.
Well, you're assuming exactly the state of affairs that this thread is questioning... The fact that the threat of changing things to this degree has dredged up such polarized opinions should indicate that we ought to be leaving things alone.
On Wed, Jun 11, 2014 at 3:41 PM, Kevin Smith <zenparsing at gmail.com> wrote:
Well, you're assuming exactly the state of affairs that this thread is questioning... The fact that the threat of changing things to this degree has dredged up such polarized opinions should indicate that we ought to be leaving things alone.
...or that the compromise is making nobody happy, in which case perhaps we've be better off with a simpler design which at least some people really liked?
I don't know. My personal opinion is that the modules stuff still feels like the odd duck which is being shoved into ES6 even though it's not quite ready yet. But I'm hoping it all works out in the end...
I have been working extensively with modules in a project that will be going live this year. I am using traceur and I find myself often doing the following:
module fs from "fs";
var { readFile } = fs;
OR
import { readFile as _readFile } from "fs";
var
readFile = _readFile;
It's partially due to the way module's get transpiled, if I were to just
do import { readFile } from "fs"
then every reference to readFile in the
source ends up looking like deps[0]["readFile"]()
Transpile aside, I don't want that performance concern. Most of the time I want a real solid reference and the only way to get it as the spec stands is to import something and then cache it locally. Isn't that kind of crazy?
- Matthew Robb
From: es-discuss <es-discuss-bounces at mozilla.org> on behalf of Matthew Robb <matthewwrobb at gmail.com>
Transpile aside, I don't want that performance concern. Most of the time I want a real solid reference and the only way to get it as the spec stands is to import something and then cache it locally. Isn't that kind of crazy?
I don't know what performance concern you're referring to (probably a transpiler-only thing). But yes, I agree that it's crazy that you can't get solid references that you control (instead of aliasing bindings that the model author controls) without such shenanigans. I brought up that point a long time ago, and was told that we wanted to follow Scheme and ML instead of existing JS module systems. Which goes back to my "frankly confounding" comment from earlier...
On Wed, Jun 11, 2014 at 4:39 PM, Domenic Denicola <domenic at domenicdenicola.com> wrote:
From: es-discuss <es-discuss-bounces at mozilla.org> on behalf of Matthew Robb <matthewwrobb at gmail.com>
Transpile aside, I don't want that performance concern. Most of the time I want a real solid reference and the only way to get it as the spec stands is to import something and then cache it locally. Isn't that kind of crazy?
I don't know what performance concern you're referring to (probably a transpiler-only thing). But yes, I agree that it's crazy that you can't get solid references that you control (instead of aliasing bindings that the model author controls) without such shenanigans. I brought up that point a long time ago, and was told that we wanted to follow Scheme and ML instead of existing JS module systems. Which goes back to my "frankly confounding" comment from earlier...
The answer you actually got when you asked this from Andreas expresses it very nicely: esdiscuss.org/topic/import-and-aliasing-bindings#content-1
Scheme and ML were only brought up when you asked about other language's module systems.
The Traceur project would be interested in your issues and in a discussion on how to improve. Improvements are easy to try. jjb
On Wed, Jun 11, 2014 at 1:53 PM, John Barton <johnjbarton at google.com> wrote:
The Traceur project would be interested in your issues and in a discussion on how to improve. Improvements are easy to try. jjb
No matter what improvements could be made you are always going to have a slower, less performant program if every access to the imported identifiers has to do a value lookup operation.
On 11 June 2014 14:01, Matthew Robb <matthewwrobb at gmail.com> wrote:
On Wed, Jun 11, 2014 at 1:53 PM, John Barton <johnjbarton at google.com> wrote:
The Traceur project would be interested in your issues and in a discussion on how to improve. Improvements are easy to try. jjb
No matter what improvements could be made you are always going to have a slower, less performant program if every access to the imported identifiers has to do a value lookup operation.
This transpiler lookup only applies when ensuring the live binding support for exports. The output being referred to is not yet fully performance-optimized, and that is because we have prioritised completeness currently, ensuring exact circular reference and binding support.
Thanks for bringing it up, as the work at the transpiler level does need to shift towards performance now that completeness has been covered, but this is very much a transpiler-level concern, unrelated to the spec.
Everyone just needs to chill out - ES6 modules are well-designed (thanks to Sam and Dave and Andreas and maybe myself a little ; ) and they are going to work extremely well in the field.
The message needs to be that modules are done. Period. (Minus some minor cosmetic issues, perhaps.) Opening up this can of worms at the last TC39 meeting was a big mistake.
Kevin, although I agree that ES6 modules are well-designed, I don't think the checkpoint that we did last week was a mistake, in fact, we invited implementers of the polyfills and transpilers to share their concerns and questions, to help us to correct course, that's all it was.
Saying that the moduleImport
syntax was not confusing and we should not
change it is just saying that TC39 is not open for feedback, there is a
clear problem with that, and you can see it in the way people are trying to
use it, (e.g.: normalize.github.io/#transformations, check the way
those folks are using the imported module as a function), and believe me,
there are many more people using it wrong, because it is just confusing,
that's all. we see this very often when people are asking questions during
trainings/workshops and opening bugs for the transpilers and polyfills. The
question is, what are we going to do about it?
Thanks Caridy,
Please forgive my occasional hyperbole, I think es-discuss is best served with a dash of spice now and then. : )
I agree that the current design is somewhat confusing. That's because it represents a perfectly balanced compromise between the multi-export, remote-binding design favored by PL-types, and the single-export design favored by developers accustomed to Node.js and AMD. That duality is inherent in the design, and it is critical for its acceptance among all parties.
Removing "ModuleImport", in effect, tips that balance away from multi-exports and jeopardizes the compromise, and jeopardizes the effort.
The inherent duality and resulting "confusion" should be left to evolution to work out. Supporting both outcomes (or a mixture thereof) is a feature, not a bug. That's my take, anyway.
On the other hand, the lack of "__dirname", etc. was a major hole and thank you for addressing it!
Kevin
For anyone interested in the transpiler story around the existing spec I opened an issue here: google/traceur-compiler#1072
- Matthew Robb
Is there anything preventing the addition of a "ModuleImport" like affordance at a later stage (e.g. ES7)?
I haven't done much development with ES6 modules but I've not found much use for that form when I have.
Related to some other comments made in this thread, I think static verification errors are a great idea. I'd also like to echo the comments that ES6 modules seem well designed, looking forward to native implementations.
On Thu, Jun 12, 2014 at 3:46 AM, Brian Di Palma <offler at gmail.com> wrote:
Is there anything preventing the addition of a "ModuleImport" like affordance at a later stage (e.g. ES7)?
ModuleImport is the only way (in syntax) to achieve CommonJS require
behavior on the consumer-side when a module author exclusively uses
multiple exports.
Its lack will force existing module maintainers to export as small a set as possible - likely either a single identifier - so that they can service the existing identifier-as-namespace convention CommonJS forces.
Given that there's no real syntactic difference between single identifier
export and default export, I would imagine default export would win since
then you get: import _ from 'underscore'
instead of import { _ } from
'underscore'`.
Related to some other comments made in this thread, I think static
verification errors are a great idea. I'd also like to echo the comments that ES6 modules seem well designed, looking forward to native implementations.
I definitely agree. I think the semantics and specification are awesome. And having syntax at all is huge for tooling and aot/static compilers.
The ModuleImport syntax can be bikeshedded until the cows come home. It's not important (to me). What's important is that there is syntax to get at its functionality, not imperative code.
IMO the only real issue is the tight coupling between syntax used to import
and syntax used to export. Why as a module consumer should the module
author's choice dictate which syntax I'm forced to use? And why as a
module author should the syntax my users want to use dictate how I have to
export my module? If syntaxes were decoupled, ModuleImport could go away
and we wouldn't lose the functionality, it would simply be import _ from 'underscore'
.
I don't think it's outlandish, the possibility that a large enough portion of the community will decide on a single import syntax as "best", and network effects will result in it going from "best" to "only".
On Thu, Jun 12, 2014 at 6:37 PM, Chris Toshok <toshok at gmail.com> wrote:
On Thu, Jun 12, 2014 at 3:46 AM, Brian Di Palma <offler at gmail.com> wrote:
Is there anything preventing the addition of a "ModuleImport" like affordance at a later stage (e.g. ES7)?
ModuleImport is the only way (in syntax) to achieve CommonJS
require
behavior on the consumer-side when a module author exclusively uses multiple exports.Its lack will force existing module maintainers to export as small a set as possible - likely either a single identifier - so that they can service the existing identifier-as-namespace convention CommonJS forces.
Given that there's no real syntactic difference between single identifier export and default export, I would imagine default export would win since then you get:
import _ from 'underscore'
instead of import { _ } from 'underscore'`.
I can see that being a valid path for certain modules to take. I'm not sure large utility packages will be as prevelant in future though. Once we have a standard module system it seems just as likely that these packages might break apart somewhat. There seems no reason to load all of underscore into a module for just one or two functions.
The underscore web page itself divides the functions underscorejs.org
We should also be wary of building cases on code from Parsers, I believe the unstructured switch statement was designed for parsers. It didn't turn out to be an optimal design for routine programming though.
I was more wondering if there was anything preventing a module import statement from being added later, if it was found to be a requirement. I can't see any reason why it couldn't, that would also allow time for bikeshedding the syntax.
Related to some other comments made in this thread, I think static verification errors are a great idea. I'd also like to echo the comments that ES6 modules seem well designed, looking forward to native implementations.
I definitely agree. I think the semantics and specification are awesome. And having syntax at all is huge for tooling and aot/static compilers.
The ModuleImport syntax can be bikeshedded until the cows come home. It's not important (to me). What's important is that there is syntax to get at its functionality, not imperative code.
The imperative code given as an alternative is ugly, it probably won't gain many users.
IMO the only real issue is the tight coupling between syntax used to import and syntax used to export. Why as a module consumer should the module author's choice dictate which syntax I'm forced to use? And why as a module author should the syntax my users want to use dictate how I have to export my module? If syntaxes were decoupled, ModuleImport could go away and we wouldn't lose the functionality, it would simply be
import _ from 'underscore'
.
I like the idea but I can't imagine many people would welcome yet more changes to ES modules.
I was more wondering if there was anything preventing a module import statement from being added later, if it was found to be a requirement. I can't see any reason why it couldn't, that would also allow time for bikeshedding the syntax.
It could be added later, but to turn the question around: why should it be dropped? It has been part of the design for a very long time, it's currently used by many people working in the ES6 space, and it meets a semantic need.
If you want to drop a feature this late in the game, then you need to show that it's one of the following:
- Buggy
- A footgun
- Not useful
- Future-hostile
I don't see that it meets any of those requirements, do you?
That's a very good set of criteria, Kevin; I think it helps frame the discussion.
I think the argument is that, based on experience with the transpilers, it is a footgun, with related to people not knowing when to use which. This has been exacerbated by transpilers not correctly distinguishing import x from "y"
and module x from "y"
, and the complete lack of stable usable documentation for the spec. In my opinion, people have not had enough experience with a documented, stable, spec, or with non-buggy transpilers, so trying to argue that it is a footgun in the current environment should not hold much weight.
There are also arguments that it is not useful, but I think those arguments are specious for the reasons I've already been over earlier.
On Thu, Jun 12, 2014 at 8:50 PM, Kevin Smith <zenparsing at gmail.com> wrote:
I was more wondering if there was anything preventing a module import statement from being added later, if it was found to be a requirement. I can't see any reason why it couldn't, that would also allow time for bikeshedding the syntax.
It could be added later, but to turn the question around: why should it be dropped? It has been part of the design for a very long time, it's currently used by many people working in the ES6 space, and it meets a semantic need.
If you want to drop a feature this late in the game, then you need to show that it's one of the following:
- Buggy
- A footgun
- Not useful
- Future-hostile
I don't see that it meets any of those requirements, do you?
I have no strong opinions either way. I don't feel it's any of those things.
The argument that was given was that people were confused by it and
were using it like an import
statement.
I said to Eric via Twitter that if people were building incorrect
compilers and modules then they will eventually learn the error of
their assumptions.
To me the argument didn't seem that strong, the native implementations will be correct and people will correct their broken code.
I'm not supporting the removal. I simply don't think it's a catastrophe.
isn't the foot gun the difference between single and multiple exports, i.e. to import underscore you'd use
module _ from 'underscore'
because it is multiple methods on an object but for jquery you'd have to use
import $ from 'jquery'
because the root object is a function instead of an object On Thu, Jun 12, 2014 at 8:50 PM, Kevin Smith <zenparsing at gmail.com> wrote:
I was more wondering if there was anything preventing a module import statement from being added later, if it was found to be a requirement. I can't see any reason why it couldn't, that would also allow time for bikeshedding the syntax.
It could be added later, but to turn the question around: why should it
be
dropped? It has been part of the design for a very long time, it's currently used by many people working in the ES6 space, and it meets a semantic need.
If you want to drop a feature this late in the game, then you need to show that it's one of the following:
- Buggy
- A footgun
- Not useful
- Future-hostile
I don't see that it meets any of those requirements, do you?
I have no strong opinions either way. I don't feel it's any of those things.
The argument that was given was that people were confused by it and
were using it like an import
statement.
I said to Eric via Twitter that if people were building incorrect
compilers and modules then they will eventually learn the error of
their assumptions.
To me the argument didn't seem that strong, the native implementations will be correct and people will correct their broken code.
I'm not supporting the removal. I simply don't think it's a catastrophe.
On Thu, Jun 12, 2014 at 10:07 PM, Calvin Metcalf <calvin.metcalf at gmail.com> wrote:
isn't the foot gun the difference between single and multiple exports, i.e.
I thought it was imports that were being misused. People were writing
module m from 'mymodule';
m();
So they treated module
just like import
. I'm not sure I see the
logic in doing that.
Did they not wonder why there were two ways to accomplish the exact same thing?
As I said, I didn't find the reasoning compelling.
One unusual but interesting metric: try to find blog posts explaining module m from 'mymodule'; vs posts explaining import. At least my attempts failed.
Basically authors who thought ES6 modules are worth explaining did not think 'module' was worth explaining.
jjb
So I think this argues for two actions:
-
Leave the syntax as-is. The "module from" syntax makes the distinction between getting the module instance object, and importing bindings from a module very clear.
-
Educate. Perhaps those of us on the list that really get modules should be writing about them as well.
The fact that here is a distinction between the bindings from the module and the instance object of the module is the issue
On Thu, Jun 12, 2014 at 5:30 PM, Kevin Smith <zenparsing at gmail.com> wrote:
So I think this argues for two actions:
- Leave the syntax as-is. The "module from" syntax makes the distinction between getting the module instance object, and importing bindings from a module very clear.
If the goal is community adoption, I think this is probably the best option. It’s too late to reopen the discussion of import x from "x" changing to have syntax that better supports multiple export, and I really dislike the implications of dropping module from at this point in the process.
- Educate. Perhaps those of us on the list that really get modules
should be writing about them as well.
I’d prefer evangelizing more than educating. I’d like to see more direct attempts to engage with the proponents of CommonJS and AMD to see where their comfort level is now. Some of them have prematurely written off ES6 modules (IMO), but the larger community (particularly around Node / browserify) just doesn’t understand the feature, and therefore can’t give meaningful feedback for or against it – or what should change – yet.
F
The fact that here is a distinction between the bindings from the module and the instance object of the module is the issue
But that distinction has always been central to the design. A module is a collection of named bindings. The default thing is an optimization feature, not the core.
I agree unless the properties of said object are getter-objects that return the bound identifier from the exporting module. Of course this would require specifying such a thing and can happen later.
What if we get rid of this "module instance object" and instead treat it as a binding namespace? The engine would then bind the import based on the accessed export.
import a from "b"; a.c;
Is the same as
import {c} from "b";
Context: gist.github.com/caridy/eefb9b104874465d4e1c#1-moduleimport-syntax-importdeclaration
module foo from "foo"; // drop this import bar from "bar";
I’m seeing the following contra against dropping ModuleImport syntax:
Isn’t this a frequent use case? Which would lead to ugly and very inconsistent code, especially if multiple imports are involved. I also don’t see how CommonJS-style modules could be neatly migrated to ES6 modules if this feature was dropped.
I do agree that the ModuleImport reads a bit strange, but that could be fixed, e.g. via a suggestion I’ve seen somewhere:
import module foo from "foo";