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

Extremely poor ES6 module import syntax for default exports #2370

Closed
rotemdan opened this issue Mar 16, 2015 · 4 comments
Closed

Extremely poor ES6 module import syntax for default exports #2370

rotemdan opened this issue Mar 16, 2015 · 4 comments
Labels
Discussion Issues which may not have code impact

Comments

@rotemdan
Copy link

(my original comment on the ES6 modules thread: #2242 (comment))

Here's a challenge: ask the average programmer what this soon-to-be-valid TypeScript code does:

import X from "M";

My guess is that 90% of the respondents will tell you it would probably import some member (or "export", in the official terminology) called X from a module called M. Now what about this?

import X, * as Z from "M";

Well, that would probably import member X and the whole module (represented by *) aliased as Z from M. Then show them this:

import {X} from "M";

And this:

import {X, Y as Z} from "M";

My guess? they'll answer "the same?" - meaning it imports Xas itself and Y with alias Z

Now here's the real thing: according to the spec, the first example, although appearing quite straightforward, would do something _completely different_ than most people (including myself) would expect. It wouldn't import an export called X but instead _alias the default export_ of M to the identifier X.

So essentially it would appear to compile and run but later give out unexpected and weird run-time errors when X is accessed and used with a different semantic meaning. The third example, on the other hand, would import the X member as expected.

The second and forth examples, although very similar in appearance, still suffer from this problem, In the second example X will alias the _default_ export, but in the forth it would import the member X as expected.

So the first example could have been replaced, for instance, by a simpler and more natural syntax such as this:

import "M" as X;

And the second (not sure if any of this is valid syntax today but certainly could be):

import default as X, * as Z from "M";
// or
import {default as X, * as Z} from "M";

This may not be a TypeScript issue per se, but I cannot understand how anyone would allow such a poorly designed syntax to be introduced to an existing language that's currently quite neat and readable? I believe that as soon as this syntax gets to wider adoption, it would be inevitable to change it. ES would be literally _forced_ to fix this by the masses of developers perplexed by this insanity.

I personally think it's better sooner than later, and now this gaining early adoption within TypeScript and other transpilers, there's going to be actual production code using this, so a price may have to be payed when this is "fixed" later by ES.

Please keep this issue open as I honestly consider this to be a bug. It wouldn't be wise to direct all responsibility to ES as there are some talented people at Microsoft who have been spending years now in designing, developing and implementing a scalable, usable and readable language. It would be demotivating to force them to maintain and support a faulty and confusing syntax such as this (and later spend much time and thought on how to fix it and work around legacy code still making use of the "older" syntax).

@spion
Copy link

spion commented Mar 16, 2015

This happened because the original module system produced for ES6 favored multiple exports. This did not match what is in current use (single-item exports in CommonJS). They tried to fix that and the fix resulted with confusing syntax which tries to make default import first class while still preserving multiple exports.

You should definitely open a thread on esdiscuss by going here but I wouldn't keep my hopes up. The key way to understand the ES6 module system is to always think "multiple exports". Thats why the default export is imported using from.

The fact that the default import syntax hides this is both helpful (not repeating default as X all the time) and confusing / unnatural.

@Alxandr
Copy link

Alxandr commented Mar 16, 2015

I completely disagree with most of the points you make here, but let me go through them bit by bit.

Remember, the module syntax in ES6 (which is what this is, just added to TypeScript) was designed to please both people coming from CommonJS and people comming from AMD, and had to be statically verifiable as well as a whole bunch of other requirements. The syntax has been iterated quite a few times already, and the way you imported whole modules (* as foo) used to be quite different, and it lead to much confusion, thus it was changed.

Now, first off, let's look at the default import

import X from './foo';

What this does, is basically replace the simple require statement that returned a function or similar, and it isn't much more illogical than

let X = require('./foo');

True, the functionality is actually different, but the usage should be the same once we start writing modules with this syntax, because instead of setting module.export we'll be exporting the default export instead.

I actually am much more confused by your proposed syntax

import 'M' as X;

This is something akin to what the old "import *" syntax used to look like, and that's basically what I interpret it as. If X was set to the default export, and not the module itself, I'd be really confused.

Now, the beauty about the last syntax is that the authors realized that 90% of the time when we write modules we end up doing either this:

let foo = require('./foo'),
    bar = foo.bar
    baz = foo.baz;

or

let foo = require('./foo');

// later
doSomeStuff(foo.bar);

We normally don't need a handle for the module's export itself. Normally we're only interested in some (or all) of the exports. Thus a syntax was added to address that. And the beauty of it is that it's almost just an appliance of destructing.

Basically, you can turn this:

import {bar} from './foo'; 

into this:

import * as foo from './foo';

let {bar} = foo;

@rotemdan
Copy link
Author

@Alxandr, I appreciate the points you've made about compatibility and trying to capture the "spirit" of legacy and different module systems (though many of them had very little use in practice such older ES6 drafts or less popular ones such as RequireJS, or wasn't heavily used on the web like CommonJS), but my approach to evaluating syntax is mainly based on general readability and parallelisms with natural language semantics, similar to the way designers of languages like Java, Python or C# would approach new syntax.

I'm not sure there's much way to bridge our perceptions. I do agree though that

import "M" as X;

Is somewhat ambiguous between a * import and default import, but although not perfect, it is still somewhat acceptable compromise. Perhaps a better alternative could be found but as someone unassociated with both Ecmascript and Microsoft, I don't see the point of spending time on working out and trying to defend proposals that probably wouldn't be accepted anyway.

@RyanCavanaugh RyanCavanaugh added the Discussion Issues which may not have code impact label Mar 16, 2015
@RyanCavanaugh
Copy link
Member

There's a few things here.

Is this syntax confusing? I am inclined to say "yes" and I think most of the TypeScript team would agree with you on this. It was not well-received when we reviewed the proposal the committee was working on.

Will the ES committee change this later? Absolutely not, at least not in a way that changes the semantics of a supported syntax. The ES6 draft is basically at the last stage before final and once it has some behavior in runtimes, that is not going to change.

What should TypeScript do about it? It's not like we're going to diverge from ES6 behavior, and adding another syntax variant would almost certainly be more confusing rather than less. Even if the ES6 module syntax is nuts, it's a kind of nuts you can learn and eventually reason about. It's not in our goals to create lots of syntactic variations on existing constructs.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Discussion Issues which may not have code impact
Projects
None yet
Development

No branches or pull requests

5 participants