ModuleImport

Jussi Kalliokoski jussi.kalliokoski at gmail.com
Wed Jul 2 14:09:05 PDT 2014


On Wed, Jul 2, 2014 at 7:09 PM, John Barton <johnjbarton at google.com> wrote:

>
>> * (Reliably) statically analyzable syntax. This is my favourite feature
>> and really awesome. Allows engine to fetch next modules while the current
>> one is still being parsed,
>>
>
> This isn't true -- all module designs in play require parsing (include of
> course CJS and AMD).
>

Huh? I wasn't saying that they don't. I mean that with the static syntax
the parser can initiate the request immediately when it hits an import
statement, which is a good thing and not possible with what is out there.
You can of course assume that the require call with a static string does
what you'd expect but then you might end up loading something that was
never actually required but someone had their own require function there
instead that has something else.


> and tooling to better understand what the code does. However, this would
>> hold true even if all we standardized was syntactic sugar on top of
>> requirejs.
>>
>
> I don't believe that anyone expects such an outcome.
>

Heh of course not, that would be horrible; I was referring to the fact that
this is a low-hanging fruit to pick.


>  * Cyclic dependencies: First of all, this is not a feature you want to
>> necessarily encourage. It looks good in academia, but in my career I've yet
>> to see real world code that would benefit more from cyclic dependencies
>> more than refactoring. Not to mention that having cyclic dependencies have
>> been possible in global scope modules since LiveScript, and is possible in
>> CommonJS as well if you do a little DI:
>> https://gist.github.com/jussi-kalliokoski/50cc79951a59945c17a2 (I had
>> such a hard time coming up with an example that could use cyclic
>> dependencies that I had to dig the example from modules examples wiki). And
>> honestly I don't think it's a feature that deserves to be easier to do than
>> my example, and especially not worth making other design compromises for.
>>
>
> The arguments for and against supporting cyclic dependencies seem to be
> academic. I'm yet to see any evidence of their importance in practice nor
> proof they they are fundamental ... or not.
>

True, and that being the case I don't see the reason of putting them on a
pedestal. If they happen to be a nice side effect, that's fine, but I'm
mostly referring to arguments against different proposals using "doesn't
support cyclic dependencies".


> * Compile-time errors: This not a feature, it's a bug. Try finding a
>> website that doesn't somewhere in its code check for whether you have a
>> feature / module available, i.e. optional dependencies. Some examples:
>> Angular has jQuery as an optional dependency; spritesheet generator modules
>> for node that have multiple engines as optional dependencies because some
>> of them may be native modules that don't compile on all platforms. Also
>> things get worse if platform features are implemented as modules. Let's say
>> things like localStorage, IndexedDB and friends were provided as modules
>> and as a result, things like localforage would either not exist or would be
>> infinitely more complex. Just look at github search for keywords `try` and
>> `require`
>> https://github.com/search?l=javascript&q=try+require&ref=cmdform&type=Code
>> to see how widely used the pattern of wrapping a module load in a try-catch
>> is.
>>
>
> Optional dependency is completely supported by Loader.import().
>  Furthermore its promise based API avoids try/catch goop.
>

Try/catch is far less goop than promises, and furthermore your non-optional
dependencies don't come in as promises, and neither can you define your
module asynchronously and wait to see whether the optional dependency is
available before exposing your interface. If you could define your module
like this it would be less of a problem but still ugly and inferior (in
this specific case) to for example CJS:

import someRequiredDependency from "somewhere";

Loader.import("someOptionalDependency")
  .catch(function noop () {})
  .then(function (someRequiredDependency) {
    exports function doSomething (foo) {
      if ( someOptionalDependency ) {
        return someOptionalDependency(foo + 5);
      } else {
        return someRequiredDependency(foo + 2);
      }
    };
  });


>
>
>>
>> Now let's look at some things that tip the odds into existing solutions
>> favor:
>>
>> * Massive amount of existing modules.
>> * Existing large-scale user-bases.
>> * Node has stated that the core will always be CommonJS, meaning that on
>> node, in order to use ES6 modules, you'll have to be using two different
>> module systems which doesn't sound like a thing that people would do unless
>> there's proven benefits.
>>
>
> These points are not relevant since nothing in the current design prevents
> these success stories from continuing.
>

The two first points are relevant if they decrease the chances of ES6
modules becoming the most used module system (which is obviously a goal
because otherwise we'll just be making things worse by contributing to
fragmentation). The last one is relevant because it actively hinders that
goal.


>
>
>>  * Completely dynamic.
>>
>
> Neither Common JS nor AMD modules are not complete dynamic.  Both systems
> rely on preprocessing JS to package modules for browsers.
>

At least in the browser, RequireJS is dynamic during runtime, as is CJS as
implemented by browserify and the APIs in both are completely dynamic.
There's of course the (for requirejs optional) build steps that do
preprocessing and node uses preprocessing by default to make it seem as if
`module`, `exports` and `require` were globals to avoid having to create
different global subcontexts for each module. This is however not required
by CJS and can be turned off if you want to for example make node look bad
in performance benchmarks. :P


>
>
>> Now, I know there are people that think that this isn't not good, but it
>> is. It gives you a lot of power when debugging things or playing around
>> with new things (something I haven't seen discussed re:modules on this
>> list). One of the greatest things in JS is that instead of reading the
>> usually poor documentation, let alone the code, of something you want to
>> use you can just load it up in node or the developer tools and play around
>> with it. With node, you require() something in the repl and you see
>> immediately what it exports.
>>
>
> Loader.get() provides the module.
>

Hmm, my bad, I actually thought that Loader.get() works only when the
module has already been fetched. Well that improves things a lot but that
still leaves the disparity between what you'd write in actual code and the
repl and thus fails to be better (in this case) than for example CommonJS.


>
>
>> You can do whatever with the exports: enumerate, extend your own exports
>> with them, whatever you want, it's just an object.
>>
>
> As far as I understand it, the return value from Loader.get() is also an
> object.
>

Hopefully, I don't see what else it could be. :S


> The mutable bindings are not mutable after import: they are  just
> properties.
>

That's a relief, but what's the point then anyway? It's not like you can do:

import meh from "somewhere";

console.log(meh);

exports function wat () {
  // at this point the module changed its meh binding
  import meh from "somewhere";
  console.log(meh);
};

right? Please say yes...


>
>
>> In debugging situations and learning of new libraries, it's really
>> straightforward to just require the module and give it different inputs to
>> see what the outputs to see if it's something you want to use or what's the
>> edge case of the method that causes the bug you're trying to fix (and then
>> just make a failing test out of it). I can't tell you how many times a day
>> I write angular.element(document.body).injector().get("Something") or
>> require(function (Something) { window.Something = Something }) in the dev
>> tools.
>>
>
> IMO, poor support for modules in devtools is a consequence of delay in the
> ES process for modules.
>

The status quo implementation status is not the problem, but the disparity
between what you'd write in the devtools and what you'd write in the actual
code is. We're making the load time programmable and behave differently
than the runtime, and the devtools live in runtime, while the module
imports you'll write in your actual code will be mostly load time.


>
>
>> With the current design of ES6 modules, I don't think it's even feasible
>> to allow the users to type into the devtools console first `import
>> something from "somewhere"` and then in the next entry refer to
>> `something`.
>>
>
> Loader.import() provides this ability.
>

See above.

As an aside, maybe we should encourage devtools REPLs to wait if the user's
expression evaluates to a promise? That would make debugging async stuff
easier. Of course then there'd need to be a mechanism to forcefully yield
back to the REPL so that a hanging promise doesn't hang the REPL.


>
>
>> Not to mention that the dynamic loading in the existing solutions caters
>> for situations like build tools, for example loading grunt tasks. The case
>> there is that a lot of grunt plugins have dependencies lists from here to
>> the end of the world, so when you run a task, you only want to load the
>> modules that are required by that task to avoid concatenating files taking
>> 20 seconds because it loads a ton of crap for other tasks. So people
>> started loading the modules when the tasks get called, not all at the same
>> time when initializing and this proved to have significant performance
>> benefits. So much that now there's jit-grunt that by guessing and
>> configuration is able to load the modules for a given task when the given
>> task is invoked. This is simply not possible with ES6 modules without a
>> massive boilerplate spaghetti with async stuff.
>>
>
> Badly designed systems will exist independent of language features.
>

True.


> The combination of statically analyzed declarative imports and dynamic
> imperative import allows much better tool support than is possible with
> Common JS and AMD.
>

Right you are. Again, I had a blackout about Loader.get() while writing
this, and Loader.get() would work in that particular case (since Grunt's
design is not exactly async-friendly, but sometimes you have to try to make
the best out of badly designed things).


>
>
>>
>> Given all this, how are we supposed to convince people to use this stuff?
>> These concerns are not something that can be fixed later either, they're
>> fundamental to the current design.
>>
>
> I think that you are misunderstanding the current design.
>

Do you mean my misunderstanding of Loader.get(), or are you referring to
something else?


> I do agree that devtools support is very important. Sadly this support is
> not given enough weight.
>

Yes. :/


> We warned about the issue with exceptions in Promises and now we have
> Promises that easily make programs essentially undebuggable.
>

Yes. :/


> Modules are less fundamentally broken, but they have more surface area for
> tools to cover.
>

I don't think I can agree on modules being less broken. Promises are
lacking proper devtools support, but at least it's clear how it should be
implemented. (The last I looked at the discussion, the consensus seemed to
be that non-handled errors would be logged once GCed).

Cheers,
Jussi
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20140703/7fc05be5/attachment-0001.html>


More information about the es-discuss mailing list