Written Proposal for "package" Keyword for ES20XX Imports

Greg McLeod cleod9 at gmail.com
Thu Feb 23 03:36:09 UTC 2017


>
> because of the way the imports in your snippet are ordered. As is clear,
> you are trying to extend class `A` before the class declaration has
> executed. Since initialization of block-scoped variables is not hoisted,
> `class B extends A` will throw because `A` is uninitialized.


Ahh, I see. Importing B before A in app.js does take care of the issue in
Babel. And by placing additional console logs in the constructor I can see
that A goes from being initially undefined to being valid by the time an
instance of B is created. Definitely the utility of live exports.

Babel's behavior essentially treats `class A {}` like `var A = class A {}`,
> hence the `undefined` value. That is not entirely correct, but the ordering
> of the imports is correct. There are cases where Babel is limited in
> implementing ES6's semantics 100%, but they are all around the behavior of
> live re-exports like `export * from 'foo';`.


Thanks for the additional insight. When I first started thinking about a
package operator a couple of years ago, it was definitely behaviors like
these that I wanted to remove from the list of daily JS development
nuances. I've found that this type of dependency issue just simply doesn't
occur in languages like C#/Java/AS3 because of how restricted those
languages are at the top level scope. The proposal presents the package
keyword in JS as an opt-in way to impose a similar restriction, but with
the goals of improving static analysis potential of code and making
circular dependency bugs a thing of the past.

I should also note that the code-snippet in the Gist demonstrating the
implementation of "package" is actual working ES5. The code allows the
circular imports example I wrote without the need for even a live exports
object.


On Wed, Feb 22, 2017 at 7:53 PM, Logan Smyth <loganfsmyth at gmail.com> wrote:

> > So it sounds like this TDZ error would be at runtime as opposed to
> compile-time
>
> Correct, since `extends`, computed properties, and decorators all require
> runtime behavior, class declarations are initialized a runtime when
> execution reaches them, unlike function declarations, where initialization
> can be hoisted.
>
> > would imply the ES6 spec does not truly support circular dependencies
>
> ES6 modules have no explicit handling of circular dependencies. What they
> do to approach this problem is have imported values reference the current
> value of the exported variable, rather than the value at the time the
> import was evaluated. The common problem with circular dependencies in
> CommonJS is that when you call `require`, the exported value might not have
> been assigned yet. Because ES6 modules expose live access to the current
> value, you don't need to worry about that edge case.
>
> This does not however free you from ensuring that your code initializes
> all values in the proper order. In your example, the code is executed like
> ```
> // app.js
> console.log("B's reference to A", A);
>
> class B extends A {
>   constructor() {
>     super();
>   }
> }
>
> console.log("A's reference to B", B);
>
> class A {
>   constructor() {
>   }
> }
>
> var a = new A();
> var b = new B();
> ```
> because of the way the imports in your snippet are ordered. As is clear,
> you are trying to extend class `A` before the class declaration has
> executed. Since initialization of block-scoped variables is not hoisted,
> `class B extends A` will throw because `A` is uninitialized.
>
> > Do you have a link to where this is referenced?
>
> The TDZ behavior is the standard behavior of block-scoped values. There is
> no special logic around this for imports.
>
> > Babel is not entirely incorrect
>
> Babel's behavior essentially treats `class A {}` like `var A = class A
> {}`, hence the `undefined` value. That is not entirely correct, but the
> ordering of the imports is correct. There are cases where Babel is limited
> in implementing ES6's semantics 100%, but they are all around the behavior
> of live re-exports like `export * from 'foo';`.
>
> On Wed, Feb 22, 2017 at 1:03 PM, Greg McLeod <cleod9 at gmail.com> wrote:
>
>> I'll point out that Babel doesn't follow spec perfectly because it can't
>>> transpile the bindings correctly in the case of synchronously executed code
>>> with circular dependencies. That's a CommonJS limitation that Babel can't
>>> work around any more than it already does.
>>
>>
>> @Isiah Is that so? Babel is leading the charge in terms of transpilers
>> right now, so that's unfortunate. Anyway I did some quick Googling and
>> found a couple of things related to the subject:
>>
>> https://github.com/webpack/webpack/issues/1788
>> https://esdiscuss.org/topic/how-to-solve-this-basic-es6-modu
>> le-circular-dependency-problem
>>
>> While it's evident people have been encountering this for quite some
>> time, it seems like the solutions up until now have been mostly
>> workarounds. My hope is that a "package" keyword could tackle this kind of
>> thing at more of a holistic level for bundled applications.
>>
>> What does the *spec* say should happen in that situation? (You've clearly
>>> been into it in some detail.) As opposed to what Babel does, since of
>>> course Babel while excellent is working with some limitations...?
>>
>>
>> @T.J. Good question, see below my response to Logan
>>
>> It would be a TDZ error, rather than `undefined`, in a real
>>> implementation of block scoping + ES6 modules. The ordering still means
>>> that `A` won't exist when `class B extends A` runs.
>>
>>
>> @Logan So it sounds like this TDZ error would be at runtime as opposed to
>> compile-time, correct? Do you have a link to where this is referenced? Both
>> cases would imply the ES6 spec does not truly support circular
>> dependencies, and that Babel is not entirely incorrect
>>
>>
>> On Wed, Feb 22, 2017 at 1:18 PM, Logan Smyth <loganfsmyth at gmail.com>
>> wrote:
>>
>>> > What does the *spec* say should happen in that situation?
>>>
>>> It would be a TDZ error, rather than `undefined`, in a real
>>> implementation of block scoping + ES6 modules. The ordering still means
>>> that `A` won't exist when `class B extends A` runs.
>>>
>>> On Wed, Feb 22, 2017 at 10:00 AM, T.J. Crowder <
>>> tj.crowder at farsightsoftware.com> wrote:
>>>
>>>> On Wed, Feb 22, 2017 at 5:50 PM, Isiah Meadows <isiahmeadows at gmail.com>
>>>> wrote:
>>>>
>>>>> I'll point out that Babel doesn't follow spec perfectly because it
>>>>> can't transpile the bindings correctly in the case of synchronously
>>>>> executed code with circular dependencies. That's a CommonJS limitation that
>>>>> Babel can't work around any more than it already does.
>>>>>
>>>> I was wondering about that. Greg, re your example where you said:
>>>>
>>>> In the latest Babel the above code fails to run on the first line of
>>>>> app.js (at the time it attempts to execute `_inherits(B, _A);`.). This is
>>>>> because "A" is undefined in module b.js due to the circular dependency. In
>>>>> this case Babel basically executed code in the incorrect order for this to
>>>>> be able to work.
>>>>
>>>>
>>>> What does the *spec* say should happen in that situation? (You've
>>>> clearly been into it in some detail.) As opposed to what Babel does, since
>>>> of course Babel while excellent is working with some limitations...?
>>>>
>>>> -- T.J. Crowder
>>>>
>>>
>>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20170222/f148e695/attachment.html>


More information about the es-discuss mailing list