Fwd: Are ES6 modules in browsers going to get loaded level-by-level?

# Wizek (9 years ago)

I've been redirected from here: tc39/ecma262#27 . Not sure if this is a good place to ask this question. If not, I'm sorry for the noise. Could you then point me elsewhere perhaps?

I've just read this post here: www.2ality.com/2014/09/es6-modules-final.html Which claims that the module system will support both sync and async loading. Which I like. But it made me wonder if/how well async loading would work for deeper dependency trees. E.g. if I had a project with 20 level deep dependency tree (at its deepest point) and my server would take on average 200ms to respond, then it would take about 4000ms minimum to execute any/all of my scripts, right? Or is there something I am missing?

If I interpret the situation correctly, what is the conceptual response to this scenario? Try to limit the tree depth? Concat everything just like it happens often with ES5? Something else?

# John Barton (9 years ago)

An async load will call for a module that knows--synchronously--its imports. The server can respond with the entire tree in one response.

The problem comes in avoiding duplicate transmission of modules shared by multiple async loads. The client will need to signal the server about the previously loaded modules.

If you search this list back to posts by Ian Hickson you can read about his HTTP/2 based discussions.

Broadly this subject is considered by "professional" web developers as just a moot point. They consider dynamic loading with decisions made in the client to be undesirable. They do support what might be called "optional" loading, where only part of the JS is loaded initially and more JS is loaded later. But the dependency decisions are all baked into the server and thus the tree of layers known only in the client does not arise.

HTH, jjb

# caridy (9 years ago)

The issue you're describing exists today, and it is the main reason why we do bundling (a la webpack, browserify, etc.).

ES6 modules are not introducing any new restriction here, it is just the way things work. HTTP2 is suppose to help in this , but only a little bit. In my experience, loading the modules is not even the biggest issue here, but executing 500 modules is, because it will require at least an order of magnitud more promises to be resolved, plus all the other normalization logic. This is where "folding" might help us. Assuming you have a huge tree of modules, we could analyze that tree and fold it into few modules that are key for the application to function, then fetching and executing those modules should not be a big deal, this is very similar to bundling as we know it today but without sacrificing module semantic, and loader advanced functionalities.

In any case, this should not prevent us from writing modules using ES6 import and export declarations today.

# medikoo (9 years ago)

It'll be great to have some more insight on this.

To my knowledge when using ES6 modules as currently specified there's no way to introduce more than one module with one file. So technically, the only way to use them natively in browsers, would be to serve them separately. This raises the question. Is there any mean in sight, that will allow us to serve them as fast as we can serve hundreds of bundled and minimized CJS modules now?

Because if not, then landscape looks troubling. As it means that to have same performance, we will need to transpile ES6 modules for browser into something else, even though browsers may support them natively.

-- View this message in context: mozilla.6506.n7.nabble.com/Fwd-Are-ES6-modules-in-browsers-going-to-get-loaded-level-by-level-tp337040p338209.html Sent from the Mozilla - ECMAScript 4 discussion mailing list archive at Nabble.com.

# Domenic Denicola (9 years ago)

Is there any mean in sight, that will allow us to serve them as fast as we can serve hundreds of bundled and minimized CJS modules now?

Yes. Any browser which implements the ES6 module loader (none of them right now) will also be a browser that implements HTTP/2 (all of them right now). HTTP/2 server push would allow you to respond to a single request for "entry.js" (e.g. from <script type="module" src="entry.js"></script>) with responses for all modules in the entire dependency graph, prioritized according to their level in the graph, all over a single TCP connection.

This is just the most naïve strategy I could think of with HTTP/2. There are more interesting ones too.

It's also important to note that bundling is an antipattern in the HTTP/2 world, as it prevents incremental cache updates by invalidating the entire bundle graph when you change a single file, and does not allow relative prioritization of individual files.

# John Barton (9 years ago)

On Thu, Apr 16, 2015 at 12:18 PM, Domenic Denicola <d at domenic.me> wrote:

Is there any mean in sight, that will allow us to serve them as fast as we can serve hundreds of bundled and minimized CJS modules now?

Yes. Any browser which implements the ES6 module loader (none of them right now) will also be a browser that implements HTTP/2 (all of them right now). HTTP/2 server push would allow you to respond to a single request for "entry.js" (e.g. from <script type="module" src="entry.js"></script>) with responses for all modules in the entire dependency graph, prioritized according to their level in the graph, all over a single TCP connection.

This is just the most naïve strategy I could think of with HTTP/2. There are more interesting ones too.

It's also important to note that bundling is an antipattern in the HTTP/2 world, as it prevents incremental cache updates by invalidating the entire bundle graph when you change a single file, and does not allow relative prioritization of individual files.

But the push scenario in your first paragraph would not use the cache either.

As far as I can tell, only the client knows which modules it has loaded; only the server knows the dependency graph for modules-to-be-loaded. So: one or the other has to send its information at the outset of a import request, or the server needs to send the entire bundle, or the loading has to be layer by layer. HTTP/2 does seem to have the magic to avoid considering these issues in module loading.

jjb

# Domenic Denicola (9 years ago)

From: John Barton [mailto:johnjbarton at google.com]

But the push scenario in your first paragraph would not use the cache either.

Yeah, that's what I was alluding to with the "most naïve" comment.

one or the other has to send its information at the outset of a import request, or

One way of doing this I came up with off the top of my head is to add some kind of "dependency graph version" or hash to the query string. I.e. <script type="module" src="entry.js?1234"></script>. The server can then assume that the client has in its cache version 1234 of the dependency graph, and can push the incremental updates since then (i.e. added or modified files). If parts of the cache were evicted, so that the versioning signal is not entirely accurate, then the penalty is not so bad, as you just fall back to the normal loading process for the evicted subset.

But I feel pretty silly speculating here as I'm not an expert on HTTP/2 techniques, and there are probably other methods that are better in various ways.

# John Barton (9 years ago)

On Thu, Apr 16, 2015 at 1:22 PM, Domenic Denicola <d at domenic.me> wrote:

From: John Barton [mailto:johnjbarton at google.com]

But the push scenario in your first paragraph would not use the cache either.

Yeah, that's what I was alluding to with the "most naïve" comment.

one or the other has to send its information at the outset of a import request, or

One way of doing this I came up with off the top of my head is to add some kind of "dependency graph version" or hash to the query string. I.e. <script type="module" src="entry.js?1234"></script>. The server can then assume that the client has in its cache version 1234 of the dependency graph, and can push the incremental updates since then (i.e. added or modified files). If parts of the cache were evicted, so that the versioning signal is not entirely accurate, then the penalty is not so bad, as you just fall back to the normal loading process for the evicted subset.

But I feel pretty silly speculating here as I'm not an expert on HTTP/2 techniques, and there are probably other methods that are better in various ways.

Perhaps, but I feel the issue is more fundamental. HTTP/2 shares statelessness with HTTP/1. It follows that the state of the client must be sent to the server or vice versa. HTTP/2 can make that process much faster but it's not going to know what state to send without instructions from clients or from servers. We can all make up those instructions one at a time and in our own unique ways or the module experts can come up with a good solution for the common cases. I'm hoping for the latter ;-)

jjb

# Glen (9 years ago)

You might find this interesting: ma.ttias.be/architecting-websites-http2-era/#comment-10935

(PUSH_PROMISE frame)

Glen.

# medikoo (9 years ago)

Thanks for clarifications,

Still after reading your comments I have a feeling that providing ES6 modules to browsers (efficient way) will be much more cumbersome and tricky than it is to provide CJS ones now. This may lead to scenario when most of us (for easy serve of bundle), will prefer to transpile them into something else, but I hope that won't be the case.

Another question raises about server support. People are relying on shared (or in cloud) hosting solutions. Do all popular servers (Apache, nginx, or http server in node.js) support HTTP/2 already? Is it easy to configure them to serve es6 modules efficient way?

-- View this message in context: mozilla.6506.n7.nabble.com/Fwd-Are-ES6-modules-in-browsers-going-to-get-loaded-level-by-level-tp337040p338232.html Sent from the Mozilla - ECMAScript 4 discussion mailing list archive at Nabble.com.

# Calvin Metcalf (9 years ago)

Neither node.js/iojs nor nginx. Though nginx supports spdy and there is a http2 module for node but it isn't compatible with express.

# John Barton (9 years ago)

On Thu, Apr 16, 2015 at 11:16 PM, medikoo <medikoo+mozilla.org at medikoo.com>

wrote:

Thanks for clarifications,

Still after reading your comments I have a feeling that providing ES6 modules to browsers (efficient way) will be much more cumbersome and tricky than it is to provide CJS ones now.

There is no technological reason to have such a feeling. The design of the ES6 module system makes creating bundles much easier and with much less chance of error. All of the imports can be computed from a given root module without relying on the developer build a list or organizing the bundle inputs into folders. This is not true for CJS.

jjb

# Matthew Phillips (9 years ago)

Can you clarify what you mean about bundling? Unless I've missed something, the ES6 module system does not have a story for bundling at all. Of course formats can be invented in userland but I'm not sure that they are any easier to implement than say AMD's. I might have missed something though, looking forward to your reply.

# Frankie Bagnardi (9 years ago)

Matthew, there are already tools for es6 modules + bundling (e.g. babel + webpack), or converting es6 modules to AMD (e.g. babel babeljs.io/docs/usage/modules).

# Eric B (9 years ago)

So just to clarify, when browsers support es6 modules we will still need some extra library to bundle the modules? This would mean es6 modules are only a syntactical addition and not something functional?

# Frankie Bagnardi (9 years ago)

In 10 years, we probably won't be using tools for the modules added in ES2015, but we might be using them for the changes made in ES2020.

# John Barton (9 years ago)

Correct, ES6 has no plans for a bundling solution and the whatwg group working on the loader has not proposed one.

Nevertheless bundling solution is easier to build and specify. In ES6, given a root module you can compute the (static) dependency graph as the basis for creating a bundle. The bundle will be complete and -- if the code has no unnecessary imports -- minimal. Moreover, the unnecessary imports can be determined by parser analysis alone.

Since bundling includes issues of transport, compression, and minification, I suspect that a standard may not emerge any time soon. Rather I expect a few tools to emerge and these will become de facto standards for bundling.

jjb

# Domenic Denicola (9 years ago)

Indeed, there is no built-in facility for bundling since as explained in this thread that will actually slow down your performance, and there’s no desire to include an antipattern in the language.

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Eric B Sent: Thursday, April 23, 2015 10:25 To: Frankie Bagnardi; Matthew Phillips Cc: es-discuss Subject: Re: Re: Are ES6 modules in browsers going to get loaded level-by-level?

So just to clarify, when browsers support es6 modules we will still need some extra library to bundle the modules? This would mean es6 modules are only a syntactical addition and not something functional?

On Thu, Apr 23, 2015 at 10:18 AM Frankie Bagnardi <f.bagnardi at gmail.com<mailto:f.bagnardi at gmail.com>> wrote:

Matthew, there are already tools for es6 modules + bundling (e.g. babel + webpack), or converting es6 modules to AMD (e.g. babelbabeljs.io/docs/usage/modules).

On Wed, Apr 22, 2015 at 7:10 PM, Matthew Phillips <matthew at bitovi.com<mailto:matthew at bitovi.com>> wrote:

Can you clarify what you mean about bundling? Unless I've missed something, the ES6 module system does not have a story for bundling at all. Of course formats can be invented in userland but I'm not sure that they are any easier to implement than say AMD's. I might have missed something though, looking forward to your reply.

# Erik Arvidsson (9 years ago)

To add one more option. You can create a service worker that loads a single zip file from the server and then splits it up for the client.

# James Burke (9 years ago)

On Thu, Apr 23, 2015 at 7:47 AM, Domenic Denicola <d at domenic.me> wrote:

Indeed, there is no built-in facility for bundling since as explained in this thread that will actually slow down your performance, and there’s no desire to include an antipattern in the language.

Some counterpoint:

For privileged/certified FirefoxOS apps, they are delivered as zip files right now. No HTTP involved. Asking for multiple files from these local packages was still slower than fetching one file with scripts bundled, due to slower IO on devices, so the certified apps in FirefoxOS right now still do bundling for speed concerns. No network in play, just file IO.

With service workers, it is hard to see that also being faster since the worker needs to be consulted for every request, so in that FirefoxOS app case, I would still want bundling.

With HTTP2, something still needs to do the same work as bundling, where it traces the dependencies and builds a graph so that all the modules in that graph can be sent back in the HTTP2 connection.

So the main complexity of bundling, a "build" step that traces dependencies and makes a graph, is still there. Might as well bundle them so that even when serving from browser cache it will be faster, see device IO concerns above.

Plus, bundling modules together can be more than just a speed concern: a library may want to use modules in separate files and then bundle them into one file for easier encapsulation/distribution.

I am sure the hope is that package managers may help for the distribution case, but this highlights another use related to bundling: encapsulation. Just like nested functions are allowed in the language, nested module definitions make sense long term. Both functions and modules are about reusing units of code. Ideally both could be nested.

I believe that is a bigger design hurdle to overcome and maybe that also made it harder for the module champions to consider any sort of bundling, but bundling really is a thing, and it is unfortunate it is not natively supported for ES modules.

The fun part about leaving this to transpilers is trying to emulate the mutable slots for import identifiers. I think it may work by replacing the identifiers with loader.get('id').exportName, or whatever the module meta/loader APIs might be, so having those APIs are even more important for a usable module system. There is probably more nuance to the transformation than that though. Like making sure to add in "use strict" to the function wrapper.

It is kind of sad that to use ES modules means to actually not really use them at runtime, to transpile back to ES5-level of code, and needing to ship a bootstrap loader script that allows slotting that the ES5-level code into the ES loader. For the extra script and transpiling concerns, it does not seem like an improvement over an existing ES5-based module systems.

James

# Brendan Eich (9 years ago)

James Burke wrote:

It is kind of sad that to use ES modules means to actually not really use them at runtime, to transpile back to ES5-level of code, and needing to ship a bootstrap loader script that allows slotting that the ES5-level code into the ES loader. For the extra script and transpiling concerns, it does not seem like an improvement over an existing ES5-based module systems.

Your lament poses a question that answers itself: in time, ES6 will be the base level, not ES3 or ES5. Then, the loader can be nativized. Complaining about this now seems churlish. :-|

# John Barton (9 years ago)

Sorry, but what I read was not an explanation but rather a hope that HTTP/2 would magically solve this problem. I'm all for HTTP/2 solving this. But so far I've not heard or read anything to back up the idea.

Will HTTP/2 make multiple round trips, one for each level of the dependency tree, competitive with pre-bundling? If not, then we will have to send dependency info to the client or cache info to the server or bundle. Or there is some magic as yet unexplained?

jjb

# Matthew Phillips (9 years ago)

I think the issue of round-trips is a red-herring. I spent some effort trying to optimize an es6 loader with caching in indexeddb and even that did not help much. I think what caridy said earlier is likely the biggest issue, processing a large number of Promises.

# Matthew Robb (9 years ago)

It would be great if the web app manifest included the dependency graph for the app. Something like the depCache in system js.

# James Burke (9 years ago)

On Thu, Apr 23, 2015 at 4:48 PM, Brendan Eich <brendan at mozilla.org> wrote:

Your lament poses a question that answers itself: in time, ES6 will be the base level, not ES3 or ES5. Then, the loader can be nativized. Complaining about this now seems churlish. :-|

So let's stay on this specific point: bundling will still be done even with ES modules and a loader that would natively understand ES modules in unbundled form. Hopefully the rest of my previous message gave enough data as to why.

If not natively supported in ES, it would be great to get a pointer to the officially blessed transform of an ES module body to something that can be bundled. Something that preserves the behaviors of the mutable slots, and allows using the module meta.

James

# Allen Wirfs-Brock (9 years ago)

On Apr 23, 2015, at 10:35 PM, James Burke wrote:

On Thu, Apr 23, 2015 at 4:48 PM, Brendan Eich <brendan at mozilla.org> wrote: Your lament poses a question that answers itself: in time, ES6 will be the base level, not ES3 or ES5. Then, the loader can be nativized. Complaining about this now seems churlish. :-|

So let's stay on this specific point: bundling will still be done even with ES modules and a loader that would natively understand ES modules in unbundled form. Hopefully the rest of my previous message gave enough data as to why.

If not natively supported in ES, it would be great to get a pointer to the officially blessed transform of an ES module body to something that can be bundled. Something that preserves the behaviors of the mutable slots, and allows using the module meta.

I think you're barking up the wrong tree. ECMAScript has never said anything about the external representation of scripts (called "Programs" prior to ES 2015) and the ES 2015 spec. doesn't impose any requirements upon the external representation of Modules. One Script or Module per external contain or multiple Scripts and Modules per external container - it makes no difference to the ES 2015 semantics.. Such encoding issues are entirely up to the host platform or ES implementation to define. But the platform/implementation has no choice in regard to the semantics of a Module (including mutability of slots or anything else in the ES 2015 specification). No matter a Module is externally stored it must conform to the ES 2015 module semantics to be a valid ES 2015 implementation.

So, if you want physical bundling, you need to convince the platform designers (eg, web, node, etc) to support that. Personally, I think a zip file makes a fine "bundle" and is something I would support if I was building a command-line level ES engine.

# James Burke (9 years ago)

On Fri, Apr 24, 2015 at 8:42 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>

wrote:

I think you're barking up the wrong tree. ECMAScript has never said anything about the external representation of scripts (called "Programs" prior to ES 2015) and the ES 2015 spec. doesn't impose any requirements upon the external representation of Modules. One Script or Module per external contain or multiple Scripts and Modules per external container - it makes no difference to the ES 2015 semantics.. Such encoding issues are entirely up to the host platform or ES implementation to define. But the platform/implementation has no choice in regard to the semantics of a Module (including mutability of slots or anything else in the ES 2015 specification). No matter a Module is externally stored it must conform to the ES 2015 module semantics to be a valid ES 2015 implementation.

Understood: the ES2015 spec makes it a point to not get into this. I was hoping that the module champions involved with the ES2015 spec would be on this list to respond to how to use modules in practice.

So perhaps I was incorrect to ask for "officially blessed", but more "a bundling form that module champions know will meet the ES2015 semantics of a Module".

The difficulty is precisely that ES2015 sets strong semantics on a Module that seem difficult to translate to a script form that could allow bundling.

I expect module meta to play a fairly important role for that translation, so having that defined, in some ES spec or elsewhere, and how that might work in bundling, would also be really helpful to complete the module picture.

So, if you want physical bundling, you need to convince the platform designers (eg, web, node, etc) to support that. Personally, I think a zip file makes a fine "bundle" and is something I would support if I was building a command-line level ES engine.

See my first post to this thread why when we had this in practice in FirefoxOS, a zip file with the contents, it was decided to use script bundling to increase performance. With the extensible web and more userland JS needed to bootstrap things like view selection and custom elements, getting the JS up and running as soon as possible is even more important.

The arguments so far against script bundling have been "there are better things that can be made for performance", but I do not see that in practice, particularly for the offline web on mobile devices. Besides that, I see modules as units of reusable code, like functions, which do allow bundling, nesting.

I can understand that is not the goal of ES2015, so hopefully the use case feedback will be useful to help flesh out a full module system that can use the ES module semantics.

James

# Brendan Eich (9 years ago)

James Burke wrote:

So let's stay on this specific point: bundling will still be done even with ES modules and a loader that would natively understand ES modules in unbundled form. Hopefully the rest of my previous message gave enough data as to why.

Not "bundling" in full; your previous post talked about HTTP2 but mixed dependency handling and bundling. You seemed not to advert to the problem of one big bundle being updated just to update one small member-of-bundle. One can be skeptical of HTTP2 but the promise is there to beat bundling.

So in a future where ES6 or above is baseline for web developers, and HTTP2 is old hat, there won't be the full bundling and module body desugaring you seem to be insisting we must have in perpetuity. (Yes, there will be dependency graphs expressed concisely -- that's not bundling.) Right?

# Jonathan Bond-Caron (9 years ago)

On Fri Apr 24 01:35 AM, James Burke wrote:

If not natively supported in ES, it would be great to get a pointer to the officially blessed transform of an ES module body to something that can be bundled. Something that preserves the behaviors of the mutable slots, and allows using the module meta.

Started using:

export in "name"; export with {bundle: "name"}; // with {metadata object}

disclaimer: Not officially blessed by spiritum consensum

# James Burke (9 years ago)

On Fri, Apr 24, 2015 at 11:39 AM, Brendan Eich <brendan at mozilla.org> wrote:

Not "bundling" in full; your previous post talked about HTTP2 but mixed dependency handling and bundling. You seemed not to advert to the problem of one big bundle being updated just to update one small member-of-bundle. One can be skeptical of HTTP2 but the promise is there to beat bundling.

So in a future where ES6 or above is baseline for web developers, and HTTP2 is old hat, there won't be the full bundling and module body desugaring you seem to be insisting we must have in perpetuity. (Yes, there will be dependency graphs expressed concisely -- that's not bundling.) Right?

There are some nice things with HTTP2 and being able to update a smaller set of files vs needing to change a bundle.

I am mostly concerned about startup performance primarily on mobile devices, and in the offline cases where HTTP2 is not part of the equation, at least not after first request. For the Firefox OS Gaia apps, they are currently zip files installed on the device. The same local disk profile exists with service worker-backed apps that work offline.

In the Firefox OS case, loading the bundle of modules performs better than not bundling, because multiple reads to local disk was slower than the one read to a bundled JS file. I expect this to be true in the future regardless of ES6 baseline or the existence of HTTP2.

A bundle of modules that have already been traced, usually ordered by least dependencies first, most dependencies last in one linearized fetch vs. in the unbundled case, the dependency tree needs to be discovered and then fetched as the modules are parsed.

It is hard to see the second one winning enough to discard wanting to bundle modules. Even if the bundle alternative is some sort of zip format that requires the whole thing to be available in memory. There is still the read, parse, back-and-forth traffic to the memory area, converting that to file responses. With service workers in play, it just adds to the delay.

James

# joe (9 years ago)

What I do is send the files over in as TAR archives, with mod_deflate turned on (they basically turn into .tar.gz files at that point). It's reasonably fast, even though I'm processing thirty megabytes of data this way (yay for typed arrays). I highly recommend it.

# joe (9 years ago)

Replies interspersed below

On Thu, Apr 23, 2015 at 9:48 AM, James Burke <jrburke at gmail.com> wrote:

On Thu, Apr 23, 2015 at 7:47 AM, Domenic Denicola <d at domenic.me> wrote:

Indeed, there is no built-in facility for bundling since as explained in this thread that will actually slow down your performance, and there’s no desire to include an antipattern in the language.

Some counterpoint:

For privileged/certified FirefoxOS apps, they are delivered as zip files right now. No HTTP involved. Asking for multiple files from these local packages was still slower than fetching one file with scripts bundled, due to slower IO on devices, so the certified apps in FirefoxOS right now still do bundling for speed concerns. No network in play, just file IO.

With service workers, it is hard to see that also being faster since the worker needs to be consulted for every request, so in that FirefoxOS app case, I would still want bundling.

If we're just talking about scripts, why not simply cache the entire zip file on the client end? That way there'd be only one request to the service worker (and perhaps queries to see if the zip's been updated).

With HTTP2, something still needs to do the same work as bundling, where it traces the dependencies and builds a graph so that all the modules in that graph can be sent back in the HTTP2 connection.

So the main complexity of bundling, a "build" step that traces dependencies and makes a graph, is still there. Might as well bundle them so that even when serving from browser cache it will be faster, see device IO concerns above.

There's one big advantage of not bundling: debugging tools. Chrome's debugger is much more stable than it used to be (I've not tried Firefox for a long time), to the point that being able to load one's scripts in their original form and map them to their location on the file system is quite nice (you can then live edit scripts in Chrome DevTools, which is unbelievably nice, you have no idea).

Plus, bundling modules together can be more than just a speed concern: a library may want to use modules in separate files and then bundle them into one file for easier encapsulation/distribution.

Right. Bundling libraries for release builds makes perfect sense.

I am sure the hope is that package managers may help for the distribution case, but this highlights another use related to bundling: encapsulation. Just like nested functions are allowed in the language, nested module definitions make sense long term. Both functions and modules are about reusing units of code. Ideally both could be nested.

I believe that is a bigger design hurdle to overcome and maybe that also made it harder for the module champions to consider any sort of bundling, but bundling really is a thing, and it is unfortunate it is not natively supported for ES modules.

The fun part about leaving this to transpilers is trying to emulate the mutable slots for import identifiers. I think it may work by replacing the identifiers with loader.get('id').exportName, or whatever the module meta/loader APIs might be, so having those APIs are even more important for a usable module system. There is probably more nuance to the transformation than that though. Like making sure to add in "use strict" to the function wrapper.

It is kind of sad that to use ES modules means to actually not really use them at runtime, to transpile back to ES5-level of code, and needing to ship a bootstrap loader script that allows slotting that the ES5-level code into the ES loader. For the extra script and transpiling concerns, it does not seem like an improvement over an existing ES5-based module systems.

James

Syntactically it's quite nice, though. IMHO, even if it's just syntactic sugar it's still worth it. I remember thinking the grammar was insane when I implemented it, but after using es6 modules for a while I now think they're (far) superior to even python's approach, at least in terms of usability.

# #!/JoePea (3 years ago)

It's 5 years later, but still no (obvious) sign of HTTP/2 servers specialized in ES Module push.

Do any exist? Anyone have a list? I'm especially interested in the self-hostable servers, but also curious about solutions where we may publish modules to.

The non-self-hosted solutions may be attractive to those people who normally publish static sites and need to publish ES Modules as static resources somewhere without the fuss if managing a server.

#!/JoePea

# J Decker (3 years ago)

On Sat, Oct 10, 2020 at 5:19 PM #!/JoePea <joe at trusktr.io> wrote:

It's 5 years later, but still no (obvious) sign of HTTP/2 servers specialized in ES Module push.

What does it mean to specialize in module push? How can modules be pushed without the browser requesting them? Is it a server that reads the scripts and pre-feeds the scripts?

Do any exist? Anyone have a list? I'm especially interested in the self-hostable servers, but also curious about solutions where we may publish modules to.

The non-self-hosted solutions may be attractive to those people who normally publish static sites and need to publish ES Modules as static resources somewhere without the fuss if managing a server.

Oh - it's to update into the server?

J

# #!/JoePea (3 years ago)

I'm asking about a server that, upon request of a .js file, knows how to enumerate the dependency tree based on that file, then HTTP pushes all the modules at once.

So basically, from the code

<script type=module src="./path/to/module.js">

the client knows it should request ./path/to/module.js, but it does not know what modules that ./path/to/module.js will want to import because the source code has not yet arrived.

So what I am imagining is the server receives the request for ./path/to/module.js, and it enumerates the dependencies of ./path/to/module.js and also pushes those.

If I understand HTTP/2 correctly, this requires more than a server that simply has HTTP push, it requires a server that understands how to read ES modules and enumerate their dependencies.

So what I wondering is if there are any servers that do that.

#!/JoePea

# Jan Krems (3 years ago)

If I understand HTTP/2 correctly, this requires more than a server that simply has HTTP push, it requires a server that understands how to read ES modules and enumerate their dependencies.

Not only that: The server also has to "know" which modules are already cached by the client (including potential match/modified-since logic). If the server always sends all modules in the dependency graph, then it's just a less efficient bundle of all modules.

Afaik the more promising path are prefetch hints on the client. E.g. the client (or initial HTML payload) knows the dependency tree, adds tags for preloading the required modules, and then the browser can properly handle fine-grained caching from there, only requesting what is actually needed.

# #!/JoePea (3 years ago)

Right, exactly. So naively sending all dependencies wastefully is just the first step.

Afaik the more promising path are prefetch hints on the client. E.g. the

client (or initial HTML payload) knows the dependency tree, adds tags for preloading the required modules, and then the browser can properly handle fine-grained caching from there, only requesting what is actually needed.

That may be nice, but I imagine people importing libraries from different domains (without knowing the graphs, for example, in simple codepen or jsfiddle demos with no build tooling).

So suppose the client code consists only of one single line of HTML, just

<script type=module src="./path/to/module.js">

In this case, the ES-module-aware server needs to just send everything wastefully, because there's no way to know what not to send.

Optimizations requirements aside, are there any HTTP/2 servers that are es-module-aware?

#!/JoePea

# Randy Buchholz (3 years ago)

I've been doing some work around module loading/importing recently, writing some "import awareness" into the server request pipeline. I'm also doing things on the client side, but I'm not set up to build a browser so I'm substituting a DI based injection approach for the import operation. It's given me a different perspective on module imports, especially when working with ES Classes. Here are my basic thoughts.

To get to smarter imports both the client and server need to be "import aware". But I think just as importantly (and IMHO a prerequisite) is that the transport layer needs to directly support the concept of import. From the server side there is nothing in the message/request (e.g., in the header) that lets the server know the request is for an import - it just comes in as a Fetch. Knowing the type of request is an import up-front would allow early routing to "import handlers" on the server. I've suggested adding an import category to the headers spec or extending sec-fetch-dest values to include import but there seems to be little interest.

Also missing is a standard way to indicate what the client wants in the request. An import is basically a function call that uses "remote parameters" - the request response. {a, b, c} scoped = importFrom("/url"); In worst-case form, the client is requesting a file and hoping it contains "parameters" usable by the import function. I say hoping, because the server doesn't know to return an importable file, it's up to the client to know what lies in the server url topology. Once the "importer" gets the parameter file there is another leap of faith (especially with named imports). Even if the file is importable, will it produce the correct types? We should be able to let the server know what we want. While this is too much info for a header, there should be a standard form of letting the server know what the client is looking for beyond "whatever is at this endpoint". Once a communication protocol is standardized, clients and servers can implement "import awareness".

At least in my work, I've come to believe that the idea of "requesting a file to import" at a url address is too limiting. I really don't care where the items come from. Conceptually I'm thinking "give me these items" or "execute the import response process with these parameters", and not "give me this file". I almost see it as an HTTP verb -IMPORT lol. Anyway just my 2c.

# #!/JoePea (3 years ago)

So in practice, bundling is still a thing because there isn't an import-aware server that has been released that proves to be better than bundling? Or perhaps it's too much overhead to set up a server, so people just bundle?

#!/JoePea

# #!/JoePea (3 years ago)

es-dev-server by open-wc seems to be import-aware. open-wc.org/developing/es-dev-server.html #!/JoePea

# Randy Buchholz (3 years ago)

I think some form of bundling will always be necessary. I use classes and took a name-spaced and typed approach to modules and classes, putting each class in its own module in a file hierarchy (namespace). This is an enterprise level LOB application with dozens of classes. Many classes are used cross-domain, limiting static/design-time bundling approaches. Also, an issue I encountered with static bundling is that classes aren't hoisted, so there are ordering concerns/issues with class bundles.

I have multiple workers as background services that use these. Each class usually has a few imports for the classes it uses. Using normal imports, I was soon generating 100's of requests for the files. Even with caching, there is a lot of overhead. As classes are used more, this can become a real issue.

I ended up with an approach where I added dependency metadata to each class to support bundling. The metadata helps with the "what to send" issue. When I need a class/type do an import for it. The server walks the dependencies, and bundles the request class with the full tree of dependencies. When the client receives the bundle it adds the new classes to a type library. It's designed for enterprise use where you have more control of things and can enforce standards.

Class looks like this:

// File Bar.cmjs

//::Requires: Foo.Package.Class1 Foo.Package.Class2
class Bar {
    const a = new Class1(); // Actually usually the qualified Foo.Package.Class1
    ...
}

// File /Foo/Package/Class1.cmjs

//::/Requires: Common.Util.Whatever
class Class1{

}

The basic idea is that when the server gets a request it reads the "Requires" and gets those files, recursively reading requires. I keep a list of all files and the depth so I know if I already captured a required, and how to order the results. Once I have all of the files I write them to a single bundle. I don't need to parse the files (Requires is just a comment), but can if I want more control. The bundled file looks like:

class Whatever {...}

class Class1 {...}

class Class2 {...}

class Bar {...}

It's more complex, because of name collisions and import scoping. What I do is process the bundle and promote the classes out of the scope.

globalThis
   .Foo
      .Package
         .Class1 = Class1; // (The "newable" class)
         .Class2 = Class2;
   .Common
      .Util
         .Whatever = Whatever;

Now I can just do new Foo.Package.Class1() anywhere in the context, not just in the import scope. On the client I use inject in many places instead of import - inject('A.B.Class'); const x = new A.B.Class();`. This checks for the type, and if it doesn't exist on the client it requests it from the server. The server creates a bundle for it and its dependencies. I add these to the type-tree. My server isn't really import-aware, I just use middleware to intercept the request. This is why I could use a way to identify "import requests". I know when I'm doing an "import" through injection, but with a regular import I have to do some inspection of the fetch to know to initiate the process.

# #!/JoePea (3 years ago)

That's neat, but it seems like the same work that a server would have to do with actual ES Module imports, right? And the "type tree" equivalent is the modules that the JS engine stores as a map from import identifier to module scope instance. It seems that in the end, the PUSH approach should work with the same efficiency, right?

Seems the only thing that makes it difficult is checking the map. In your special case, with inject, you can physically check the global namespaces to see if the module is available. But with ES Modules, we can't check if some module has already been lodade by its identifier, can we? So we have to make the request, because that's the only way to check.

#!/JoePea

# Randy Buchholz (3 years ago)

Right, it's basically just doing what an import aware server might do and the type-tree is a hierarchal version of the scope imports. The rest is just extra stuff. Probably the biggest difference though is that it lets me isolate prototypes. From what I gather it seems that import stores a live "ghosted" version of the prototype that it checks before making additional requests for the item. The scope basically gets a reference to this prototype. If you do things like add a property with reflect in one scope that property shows up everywhere. And since it modified the "ghost" it persists after the scope goes away. It's caused me some headaches especially when dealing with inheritance/extends and in workers.

Yeah, inspecting is in issue. I haven't found a way to inspect modules to see what they have in them. They're a strange beast. You can see their scope in the debugger and they look like an ES Object or IDL interface, but I don't know how to get a reference to them in code. But, they're new, so we'll see where they go. Maybe someday we'll have a modules collection we can interrogate.

# #!/JoePea (3 years ago)

It's caused me some headaches especially when dealing with inheritance/extends and in workers.

Like, extending from a Module object?

Maybe someday we'll have a modules collection we can interrogate.

That may be nice, to query which modules have already been imported, etc.

It would also be great if we could unload modules.


Skypack is making news rounds as an ESM server: skypack.dev

It says it supports HTTP/2 and HTTP/3. But it isn't open source.

Seems that there isn't any open source solution (otherwise I'm sure people would be using that over bundling if it works out better, at least alternatives to skypack would exist, f.e. well-know projects like React, Angular, Vue, Svelte, etc, could all have their own ES Module servers if it was viable).

Seems that there hasn't been any free/open project to prove viability yet, and the existing ones are closed source.

Seems that even jspm.dev is closed source.

Looks like at this point in time people are aiming to make money from ESM servers, and there's no viable open source ESM server solution.

#!/JoePea

# J Decker (3 years ago)

On Thu, Oct 22, 2020 at 2:23 PM #!/JoePea <joe at trusktr.io> wrote:

It's caused me some headaches especially when dealing with inheritance/extends and in workers.

Like, extending from a Module object?

Maybe someday we'll have a modules collection we can interrogate.

That may be nice, to query which modules have already been imported, etc.

It would also be great if we could unload modules.


Skypack is making news rounds as an ESM server: skypack.dev

It says it supports HTTP/2 and HTTP/3. But it isn't open source.

Seems that there isn't any open source solution (otherwise I'm sure people would be using that over bundling if it works out better, at least alternatives to skypack would exist, f.e. well-know projects like React, Angular, Vue, Svelte, etc, could all have their own ES Module servers if it was viable).

Seems that there hasn't been any free/open project to prove viability yet, and the existing ones are closed source.

Seems that even jspm.dev is closed source.

Looks like at this point in time people are aiming to make money from ESM servers, and there's no viable open source ESM server solution.

Seems like it wouldn't be a LOT of work to take Acorn (

www.npmjs.com/package/acorn ) and http server ( www.npmjs.com/package/http ) and parse the pages loaded if (*.[cm]+js) (something)

Though my observation is that when the browser gets the first page, async requests go out for more content even before it's actually interpreted/run the script; and the requests are streamed over one or more http(s) connections. this screenshot pasteboard.co/JwUnbAD.png of this demo d3x0r.github.io/Voxelarium.js shows the network load time; recently updated to imports and non-built scripts... though I do see a gap where the html script loading ends and the imports in the scripts actually go... but that could also be the pause setting up the opengl surface... it's about 50ms.

since 'import' is itself async I sort of expected a lot of overlap in the requests; there's only a single network wire, so there's not a LOT to be gained parallelizing things.

If there was even some sort of manifest could make a background service worker (which itself doesn't support import bugs.chromium.org/p/chromium/issues/detail?id=680046 ) which can behave like a offline storage/cache so the server can dump requests to the client before it knows to ask for them... and then it doesn't have to ask the server for anything at all later even; which sort of de-emphasizes all the work put into the server in the first place :)

J

#!/JoePea

# Augusto Moura (3 years ago)

We could wirte a simpler parser just for the imports subset, given the simpler isolated grammar I don't think is that hard with parser combinators. Maybe it's a cool idea for a npm library that just points out the dependencies of a single file and then recursively scan the rest. From that is just hook some cache system to a node server and voi'la

I migh try it as a weekend toy project

Em qui, 22 de out de 2020 23:26, J Decker <d3ck0r at gmail.com> escreveu:

# Guy Bedford (3 years ago)

It seems clear some kind of dependency graph metadata hinting is needed somewhere in the loading pipeline to optimize network delivery for unknown dynamic imports.

The question is just where and how that gets specified.

Between Web Bundles, Import Maps and Compartments, I am hopeful that there will be some mechanism available here in future.

# Randy Buchholz (3 years ago)

Like, extending from a Module object?

No, it’s more when I derive from an imported class. I had a “ReceivePort” module and a “SendPort” module that both use a base class “CommPort”. Since a module “seems to be” an isolated scope I thought I could extend CommPort individually inside each module. I knew the browser cached the download, but didn’t know about it caching a ”live” object.

This isn’t exactly what I was doing, but it close to the idea.

Module: CommPort
class CommPort {
    set duplexHost(host) { … }
}

// Module: ReceivePort
import { CommPort } from…
class ReceivePort extends CommPort { }

// Module: SendPort
import { CommPort } from…
class SendPort extends CommPort { }

// Module: DuplexPort
import { SendPort } from…
import { ReceivePort } from…

class DuplexPort {
    #sendPort = new SendPort();
   #receivePort = new ReceivePort();
    constructor(){
        sendPort.duplexHost = this;
        receivePort.duplexHost = this;
          } 
}

I have “Worker Farms” where each worker has multiple DuplexPort to communicate with other workers. I would send a message to one worker and pretty much all of them received the message. The problem wasn’t obvious to me, especially since I was generally new to ES at the time.

What happens is the browser downloads CommPort once, and also creates a single CommPort object. All of the scoped CommPort objects are just pointers to that. So the imported CommPort in SendPort is the same CommPort as the one in ReceivePort. When the DuplexPort registers itself on the ports (even though it seems isolated within a module scope) the registration “leaks” up to the live ghosted CommPort. Every message was going through the last port registered! This is one reason for the approach I’m taking. When I inject I get a “clean” base class to inherit from.

# Randy Buchholz (3 years ago)

We could wirte a simpler parser just for the imports subset

IMHO code on client and server isn't the root problem. I've written a few versions and the biggest issue I had was how to distinguish an import request/fetch from any other. There is nothing in the request metadata to let you know it's an import. I've suggested adding an import group to the headers spec or import to the list of sec-fetch-dest approved header values.