A few module ideas

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

A few module ideas

James Browning
These are just some ideas I've had for improving es modules based on
my experiences with them. The syntax and stuff with them isn't too
important, the main point is the problem I'm trying to solve with each
of them, feedback and criticism is welcome.

# Dynamic modules

One of the biggest issues I've had with ES modules is not being able
to load classic scripts as part of the dependency graph, one of the
solutions I've used but am not particularly happy with is having an
async loader function e.g.:

```js
function loadScript(url) {
    return new Promise(resolve => {
        const scriptElem = document.create('script')
        scriptElem.src = url
        scriptElem.onload = resolve
    })
}

// some other file

async function computeSpline() {
    await loadScript('./mathjs.js')
    // use math here
}
```

And while this approach works somewhat it's a bit of a pain for a
couple reasons:

- If something gains a `script` dependency it necessarily breaks all
consumers by becoming asynchronous even if the original operations
were synchronous
- It's not generic for other types of resources e.g. I can't load an
image without creating another loader function or so on

---

My proposed solution, dynamic (but static) export:

```js
// math.mjs
import loadScript from ".../loadScript.js"

loadScript('./math.js').then(_ => {
    const math = window.math
    delete window.math
    export({
        math as default
    })
})
```

This solution is also generic so it can be used for loading any type
of resource:

```
// highPerformanceMath.mjs
fetch('.../math.wasm').then(response => response.arrayBuffer())
    .then(buffer => WebAssembly.instantiate(buffer, {}))
    .then(({ instance }) => {
        export({
            instance.exports as default
        })
        // or potentially named exports
        export({
            instance.exports.fastFourierTransform as fastFourierTransform
            ...
    })
```

Now this solution would be nice because it's generic and allows for
loading any (even asynchronous) object as part of the module graph and
doesn't cause explosions where because one part becomes asynchronous
everything becomes asynchronous.

However there is a deficiency in that it can be quite verbose for
similar tasks e.g. loading WebAssembly modules which is why I thought
of idea 2:

# Module Arguments

Effectively module arguments would allow passing data to a module
(statically) during loading e.g.:

```js
// some-file.js
import dict from "./dictionary.js" where { lang = "en-US" }

// dictionary.js
fetch(`./dictionaries/${ import.arguments.lang }.txt`)
    .then(response => response.text())
    .then(text => export({
        JSON.parse(text) as default
    })
```

This solves the previous problem of very similar dynamic modules for
similar types by allowing details like that to be passed in as
arguments e.g.:


```js
import math from "./loadScript.mjs" where {
    script = './math.js',
    globalName = 'math'
}
```

# Lazy Export-From

One of the nice things about named exports is you can minimize the
amount of mostly similar `import` declarations e.g.:

```js
import map from "./lodash/map.js"
import filter from "./lodash/filter.js"
import flatMap from "./lodash/flatMap.js"
...

// can become
import { map, filter, flatMap, ... } from "./lodash/lodash.js"
```

However it has a major downside of massively increasing the amount of
fetch/parse/execute time for all those additional things exported by
the combined module.

My idea is to allow modules to declare that parts need to not be
fetched parsed or executed if they're not actually imported e.g.:

```js
// my-operators-library.js
static export { map } from "./map.js"
static export { filter } from "./filter.js"
static export { reduce } from "./reduce.js"
```

Effectively all my idea adds is the `static export` (syntax not
important) form that effectively says these names should only be
resolved if they're actually imported and can be safely ignored if
they're not used. This way you get both the benefits of collection
modules (easier to `import` and reduces duplication) and the benefits
of individual `import`s (lesser loading sizes).

# Summary

Basically the ideas suggested here are to solve these particular
problems I've had with ES modules:

- Unable to load classic scripts (and other types of resources
statically e.g. conditional modules) as part of the module graph
- Unable to specify more specific behavior for a module to prevent duplication
- Either have to have lots of almost duplicate import declarations or
have to load unnecessary files

The solutions I proposed aimed to keep the constraint that module
exports should remain statically parsable which is why `export({ ...
})` shares the syntactic form.

I refrained from specifying the semantics of the specific operations
as there's details that'd need to be sorted out for all of them if
there is any interest whatsoever in implementing them.
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A few module ideas

kai zhu
for the forseeable future, the only practical solution i see to these
frontend performance issues is to avoid using es modules, and revert
to the simple, old-fashioned method of assigning libraries to the
window namespace.

On 8/20/17, James Browning <[hidden email]> wrote:

> These are just some ideas I've had for improving es modules based on
> my experiences with them. The syntax and stuff with them isn't too
> important, the main point is the problem I'm trying to solve with each
> of them, feedback and criticism is welcome.
>
> # Dynamic modules
>
> One of the biggest issues I've had with ES modules is not being able
> to load classic scripts as part of the dependency graph, one of the
> solutions I've used but am not particularly happy with is having an
> async loader function e.g.:
>
> ```js
> function loadScript(url) {
>     return new Promise(resolve => {
>         const scriptElem = document.create('script')
>         scriptElem.src = url
>         scriptElem.onload = resolve
>     })
> }
>
> // some other file
>
> async function computeSpline() {
>     await loadScript('./mathjs.js')
>     // use math here
> }
> ```
>
> And while this approach works somewhat it's a bit of a pain for a
> couple reasons:
>
> - If something gains a `script` dependency it necessarily breaks all
> consumers by becoming asynchronous even if the original operations
> were synchronous
> - It's not generic for other types of resources e.g. I can't load an
> image without creating another loader function or so on
>
> ---
>
> My proposed solution, dynamic (but static) export:
>
> ```js
> // math.mjs
> import loadScript from ".../loadScript.js"
>
> loadScript('./math.js').then(_ => {
>     const math = window.math
>     delete window.math
>     export({
>         math as default
>     })
> })
> ```
>
> This solution is also generic so it can be used for loading any type
> of resource:
>
> ```
> // highPerformanceMath.mjs
> fetch('.../math.wasm').then(response => response.arrayBuffer())
>     .then(buffer => WebAssembly.instantiate(buffer, {}))
>     .then(({ instance }) => {
>         export({
>             instance.exports as default
>         })
>         // or potentially named exports
>         export({
>             instance.exports.fastFourierTransform as fastFourierTransform
>             ...
>     })
> ```
>
> Now this solution would be nice because it's generic and allows for
> loading any (even asynchronous) object as part of the module graph and
> doesn't cause explosions where because one part becomes asynchronous
> everything becomes asynchronous.
>
> However there is a deficiency in that it can be quite verbose for
> similar tasks e.g. loading WebAssembly modules which is why I thought
> of idea 2:
>
> # Module Arguments
>
> Effectively module arguments would allow passing data to a module
> (statically) during loading e.g.:
>
> ```js
> // some-file.js
> import dict from "./dictionary.js" where { lang = "en-US" }
>
> // dictionary.js
> fetch(`./dictionaries/${ import.arguments.lang }.txt`)
>     .then(response => response.text())
>     .then(text => export({
>         JSON.parse(text) as default
>     })
> ```
>
> This solves the previous problem of very similar dynamic modules for
> similar types by allowing details like that to be passed in as
> arguments e.g.:
>
>
> ```js
> import math from "./loadScript.mjs" where {
>     script = './math.js',
>     globalName = 'math'
> }
> ```
>
> # Lazy Export-From
>
> One of the nice things about named exports is you can minimize the
> amount of mostly similar `import` declarations e.g.:
>
> ```js
> import map from "./lodash/map.js"
> import filter from "./lodash/filter.js"
> import flatMap from "./lodash/flatMap.js"
> ...
>
> // can become
> import { map, filter, flatMap, ... } from "./lodash/lodash.js"
> ```
>
> However it has a major downside of massively increasing the amount of
> fetch/parse/execute time for all those additional things exported by
> the combined module.
>
> My idea is to allow modules to declare that parts need to not be
> fetched parsed or executed if they're not actually imported e.g.:
>
> ```js
> // my-operators-library.js
> static export { map } from "./map.js"
> static export { filter } from "./filter.js"
> static export { reduce } from "./reduce.js"
> ```
>
> Effectively all my idea adds is the `static export` (syntax not
> important) form that effectively says these names should only be
> resolved if they're actually imported and can be safely ignored if
> they're not used. This way you get both the benefits of collection
> modules (easier to `import` and reduces duplication) and the benefits
> of individual `import`s (lesser loading sizes).
>
> # Summary
>
> Basically the ideas suggested here are to solve these particular
> problems I've had with ES modules:
>
> - Unable to load classic scripts (and other types of resources
> statically e.g. conditional modules) as part of the module graph
> - Unable to specify more specific behavior for a module to prevent
> duplication
> - Either have to have lots of almost duplicate import declarations or
> have to load unnecessary files
>
> The solutions I proposed aimed to keep the constraint that module
> exports should remain statically parsable which is why `export({ ...
> })` shares the syntactic form.
>
> I refrained from specifying the semantics of the specific operations
> as there's details that'd need to be sorted out for all of them if
> there is any interest whatsoever in implementing them.
> _______________________________________________
> es-discuss mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss
>
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: A few module ideas

Caridy Patino
export values are dynamic today! you can do:

let lib = null;
export default lib;
loadScript("foo").then(o => lib = o);

but what cannot be dynamic, is the set of export names, which shapes the module.

/caridy

> On Aug 20, 2017, at 8:57 AM, kai zhu <[hidden email]> wrote:
>
> for the forseeable future, the only practical solution i see to these
> frontend performance issues is to avoid using es modules, and revert
> to the simple, old-fashioned method of assigning libraries to the
> window namespace.
>
>> On 8/20/17, James Browning <[hidden email]> wrote:
>> These are just some ideas I've had for improving es modules based on
>> my experiences with them. The syntax and stuff with them isn't too
>> important, the main point is the problem I'm trying to solve with each
>> of them, feedback and criticism is welcome.
>>
>> # Dynamic modules
>>
>> One of the biggest issues I've had with ES modules is not being able
>> to load classic scripts as part of the dependency graph, one of the
>> solutions I've used but am not particularly happy with is having an
>> async loader function e.g.:
>>
>> ```js
>> function loadScript(url) {
>>    return new Promise(resolve => {
>>        const scriptElem = document.create('script')
>>        scriptElem.src = url
>>        scriptElem.onload = resolve
>>    })
>> }
>>
>> // some other file
>>
>> async function computeSpline() {
>>    await loadScript('./mathjs.js')
>>    // use math here
>> }
>> ```
>>
>> And while this approach works somewhat it's a bit of a pain for a
>> couple reasons:
>>
>> - If something gains a `script` dependency it necessarily breaks all
>> consumers by becoming asynchronous even if the original operations
>> were synchronous
>> - It's not generic for other types of resources e.g. I can't load an
>> image without creating another loader function or so on
>>
>> ---
>>
>> My proposed solution, dynamic (but static) export:
>>
>> ```js
>> // math.mjs
>> import loadScript from ".../loadScript.js"
>>
>> loadScript('./math.js').then(_ => {
>>    const math = window.math
>>    delete window.math
>>    export({
>>        math as default
>>    })
>> })
>> ```
>>
>> This solution is also generic so it can be used for loading any type
>> of resource:
>>
>> ```
>> // highPerformanceMath.mjs
>> fetch('.../math.wasm').then(response => response.arrayBuffer())
>>    .then(buffer => WebAssembly.instantiate(buffer, {}))
>>    .then(({ instance }) => {
>>        export({
>>            instance.exports as default
>>        })
>>        // or potentially named exports
>>        export({
>>            instance.exports.fastFourierTransform as fastFourierTransform
>>            ...
>>    })
>> ```
>>
>> Now this solution would be nice because it's generic and allows for
>> loading any (even asynchronous) object as part of the module graph and
>> doesn't cause explosions where because one part becomes asynchronous
>> everything becomes asynchronous.
>>
>> However there is a deficiency in that it can be quite verbose for
>> similar tasks e.g. loading WebAssembly modules which is why I thought
>> of idea 2:
>>
>> # Module Arguments
>>
>> Effectively module arguments would allow passing data to a module
>> (statically) during loading e.g.:
>>
>> ```js
>> // some-file.js
>> import dict from "./dictionary.js" where { lang = "en-US" }
>>
>> // dictionary.js
>> fetch(`./dictionaries/${ import.arguments.lang }.txt`)
>>    .then(response => response.text())
>>    .then(text => export({
>>        JSON.parse(text) as default
>>    })
>> ```
>>
>> This solves the previous problem of very similar dynamic modules for
>> similar types by allowing details like that to be passed in as
>> arguments e.g.:
>>
>>
>> ```js
>> import math from "./loadScript.mjs" where {
>>    script = './math.js',
>>    globalName = 'math'
>> }
>> ```
>>
>> # Lazy Export-From
>>
>> One of the nice things about named exports is you can minimize the
>> amount of mostly similar `import` declarations e.g.:
>>
>> ```js
>> import map from "./lodash/map.js"
>> import filter from "./lodash/filter.js"
>> import flatMap from "./lodash/flatMap.js"
>> ...
>>
>> // can become
>> import { map, filter, flatMap, ... } from "./lodash/lodash.js"
>> ```
>>
>> However it has a major downside of massively increasing the amount of
>> fetch/parse/execute time for all those additional things exported by
>> the combined module.
>>
>> My idea is to allow modules to declare that parts need to not be
>> fetched parsed or executed if they're not actually imported e.g.:
>>
>> ```js
>> // my-operators-library.js
>> static export { map } from "./map.js"
>> static export { filter } from "./filter.js"
>> static export { reduce } from "./reduce.js"
>> ```
>>
>> Effectively all my idea adds is the `static export` (syntax not
>> important) form that effectively says these names should only be
>> resolved if they're actually imported and can be safely ignored if
>> they're not used. This way you get both the benefits of collection
>> modules (easier to `import` and reduces duplication) and the benefits
>> of individual `import`s (lesser loading sizes).
>>
>> # Summary
>>
>> Basically the ideas suggested here are to solve these particular
>> problems I've had with ES modules:
>>
>> - Unable to load classic scripts (and other types of resources
>> statically e.g. conditional modules) as part of the module graph
>> - Unable to specify more specific behavior for a module to prevent
>> duplication
>> - Either have to have lots of almost duplicate import declarations or
>> have to load unnecessary files
>>
>> The solutions I proposed aimed to keep the constraint that module
>> exports should remain statically parsable which is why `export({ ...
>> })` shares the syntactic form.
>>
>> I refrained from specifying the semantics of the specific operations
>> as there's details that'd need to be sorted out for all of them if
>> there is any interest whatsoever in implementing them.
>> _______________________________________________
>> es-discuss mailing list
>> [hidden email]
>> https://mail.mozilla.org/listinfo/es-discuss
>>
> _______________________________________________
> es-discuss mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Re: A few module ideas

Dante Federici
In reply to this post by James Browning
  • Unable to load classic scripts (and other types of resources statically e.g. conditional modules) as part of the module graph
How are conditional imports static? In both examples I see the module as being async, and therefore every dependent module is async. Your "dynamic but static" is explicitly using "then" -- or are you implying a module exporting async resources is a better solution than an async module?

  • Unable to specify more specific behavior for a module to prevent duplication
By passing arguments in, what do you expect to occur? Do you expect the module itself to be run with those arguments, exporting a set of things? How is that any better than just importing a constructor function from the module/library? This problem sounds like designing the library in a better way would make more sense than affording config to be passed into import, which would mean each import would re-run the module, so no caching.

  • Either have to have lots of almost duplicate import declarations or have to load unnecessary files
I can see a benefit for reducing files in the static export -- that suggestion has been a good example of existing problems with tree shaking d3, to which the response has been "design better exports". As for the multiple fetch part of the problem, HTTP/2 spec addresses the performance hit for that, and it's effectively what you're asking the "static" prefix to assert. Out of curiosity, how would you expect static to work in the first place? Who would do the assertion that it doesn't depend on any other symbol in the module it is a part of?

I feel like out of these, the solution is much closer to "Better library design". I'm still not 100% on how your dynamic example addresses "turns my code async".  Static export is an interesting one -- effectively asking for pure symbols. Maybe identify an entire file as "load only these symbols, ignore other source"?

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Re: A few module ideas

Andrea Giammarchi-2
I've solved this (for my needs) long time ago, the pattern is the following:

```js
export default new Promise(async $export => {
  // await anything that needs to be imported
  // await anything that asynchronous
  // finally export the module resolving the Promise
  // as object, function, class, ... anything
  $export(
    {module: 'object'} ||
    function () {}     ||
    class Anything {}
  );
});
```

You can do pretty much everything you need as both consumer or exporter.

```js
// ES2017 Asynchronous Export
// module.js
export default new Promise(async $export => {
  const module = await Promise.resolve(
    {my: 'module'}
  );
  $export(module);
});

// - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// ES2015 consumer
import module from './module.js';

module.then(exports => {
  // will log "module"
  console.log(exports.my);
});

// - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// ES2017 consumer
(async () => {
  const module = await (
    await import('./module.js')
  ).default;
})();


// - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// ES2017 consumer and exporter
export default new Promise(async $export => {
  const module = await (
    await import('./module.js')
  ).default;
  $export({module, method(){}});
});
```

The pattern easily inter-operate with CommonJS

```js
// CommonJS consumer and/or importer
module.exports = new Promise(async $export => {
  const module = await require('./module');
  $export({module, method(){}});
});
```

I still don't understand why it's difficult to imagine asynchronous exports when it's apparently normal to imagine asynchronous imports .... but that's another story.

Best Regards






On Sun, Aug 20, 2017 at 8:35 PM, dante federici <[hidden email]> wrote:
  • Unable to load classic scripts (and other types of resources statically e.g. conditional modules) as part of the module graph
How are conditional imports static? In both examples I see the module as being async, and therefore every dependent module is async. Your "dynamic but static" is explicitly using "then" -- or are you implying a module exporting async resources is a better solution than an async module?

  • Unable to specify more specific behavior for a module to prevent duplication
By passing arguments in, what do you expect to occur? Do you expect the module itself to be run with those arguments, exporting a set of things? How is that any better than just importing a constructor function from the module/library? This problem sounds like designing the library in a better way would make more sense than affording config to be passed into import, which would mean each import would re-run the module, so no caching.

  • Either have to have lots of almost duplicate import declarations or have to load unnecessary files
I can see a benefit for reducing files in the static export -- that suggestion has been a good example of existing problems with tree shaking d3, to which the response has been "design better exports". As for the multiple fetch part of the problem, HTTP/2 spec addresses the performance hit for that, and it's effectively what you're asking the "static" prefix to assert. Out of curiosity, how would you expect static to work in the first place? Who would do the assertion that it doesn't depend on any other symbol in the module it is a part of?

I feel like out of these, the solution is much closer to "Better library design". I'm still not 100% on how your dynamic example addresses "turns my code async".  Static export is an interesting one -- effectively asking for pure symbols. Maybe identify an entire file as "load only these symbols, ignore other source"?

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Re: A few module ideas

James Browning
> export values are dynamic today! you can do:

> let lib = null;
> export default lib;
> loadScript("foo").then(o => lib = o);

> but what cannot be dynamic, is the set of export names, which shapes the module.

Cariday, while interesting the main problem with this approach is it
doesn't guarantee that the desired module is actually usable at any
particular point e.g. this might not work:

```js
// math.mjs
let math
export { math as default }
loadScript('./math.js').then(_ => {
    math = window.math
    delete window.math
})

// cardinalSpline
import math from "./math.mjs"

export default function cardinalSpline() {
   // use math to compute the spline here
}

// import that function
import cardinalSpline from "./cardinalSpline.mjs"

// I can't reliably use cardinalSpline here yet as math might still be undefined
// even after asynchronous work I can't reliably use it as the script
// might still be fetching
```

---

Dante, I didn't really clarify what I meant by conditional exports, I
don't mean things that sometime export but sometimes don't, the names
exported should remain static but rather what is exported depends on
(possibly asynchronous) things e.g.:

```js
if (typeof self !== 'undefined') {
    // Browser like
    loadScript('./math.js')
        .then(_ => {
             const math = window.math
             export({ math })
             delete window.math
        })
} else {
    // Try node like
    // we'll ignore other environments for now for simplicity
    export({ require('mathjs') as math })
}
```

I'd expect it to be a syntax error if the exported names in one
`export(...)` were different to any other `export(...)` to preserve
the fixed names.

Concerning the other point modules in the browser are *already* async,
it's just observable that they're async and that's the whole point of
why I want dynamic modules is that they can do asynchronous work
before completion, note that the imported module *is* available
synchronously as the module won't be executed until the export
resolves e.g.:

```js
// math.mjs
loadScript('./math.js').then(_ => {
    const math = window.math
    export({ math })
    delete window.math
})

// other file
import math from "./math.mjs"

// We can reliably use math here as this file will not
// be executed until export({ ... }) is reached
// in my idea export(...) is similar to Promise.resolve
```

Foreign module types is nothing new the spec is [specifically designed
for them](https://tc39.github.io/ecma262/#sec-abstract-module-records),
this is how CommonJS will work with `import commonJS from
"commonJSmodule"`. My idea is simply to add a way to add those dynamic
module types as a part of the language instead of part of the loader.


> By passing arguments in, what do you expect to occur? Do you expect the module itself to be run with those arguments, exporting a set of things? How is that any better than just importing a constructor function from the module/library? This problem sounds like designing the library in a better way would make more sense than affording config to be passed into import, which would mean each import would re-run the module, so no caching.

Yes I'd expect it to evaluate multiple times (but fetch/parse only
once) which saves round trips, I mostly only thought of it because of
the way I suggested how dynamic export could work, without dynamic
export it's not particularly useful, it's mostly for reducing the
amount of those script/wasm -> es module modules.

Admittedly I hadn't really thought module arguments through that much
(would same arguments result in the same module object, etc etc), the
whole idea might be rubbish, but the main problem I was trying to
solve with them was automatic creation of dynamic modules so that you
wouldn't need a module like:

```js
// highPerformanceMath.mjs
fetch('.../math.wasm').then(response => response.arrayBuffer())
    .then(buffer => WebAssembly.instantiate(buffer, {}))
    .then(({ instance }) => {
        export({
            instance.exports as default
        })
        // or potentially named exports
        export({
            instance.exports.fastFourierTransform as fastFourierTransform
            ...
    })

```

for every single WebAssembly module you wanted to import and use
synchronously, although in retrospect you probably would still need to
*anyway* if you want to name the exports as the `export({...})` syntax
is still a static declaration of names (it's not an object you can
just populate with names).

> I can see a benefit for reducing files in the static export -- that suggestion has been a good example of existing problems with tree shaking d3, to which the response has been "design better exports". As for the multiple fetch part of the problem, HTTP/2 spec addresses the performance hit for that, and it's effectively what you're asking the "static" prefix to assert. Out of curiosity, how would you expect static to work in the first place? Who would do the assertion that it doesn't depend on any other symbol in the module it is a part of?

HTTP/2 is orthogonal to the goal of `static export ... from`,
basically HTTP/2 allows for serving all dependencies faster when the
dependency graph is known. My idea of `static export ... from` is
basically built-in tree shaking, if a name isn't imported then that
part of the module graph is simply not fetched/parsed/evaluated for
example:

```js
// Note that my suggestion *only* works with export-from
// it does not work with plain `export` as that is already
// fetched and parsed

// operators.mjs
static export { map } from "./operators/map.js"
static export { filter } from "./operators/filter.js"
static export { reduce } from "./operators/reduce.js"
static export { flatMap } from "./operators/flatMap.js"

// other file
import { map, flatMap } from "./operators.mjs"
// only ./operators/map.js and ./operators/flatMap.js
// will be fetched parsed and executed (assuming no other modules)

// another example
import * as operators from "./operators.mjs"
// we can't reason that some of the things might not be used
// so files are fetched/parsed/evaluated
```

> I feel like out of these, the solution is much closer to "Better library design". I'm still not 100% on how your dynamic example addresses "turns my code async".  Static export is an interesting one -- effectively asking for pure symbols. Maybe identify an entire file as "load only these symbols, ignore other source"?

The problem is while it's easy to design within your own code a good
API, if you include a classic script as part of the dependency graph
currently then that forces things to become async for example this
simple example:

```js
// classic script loaded to access functions
// as math.mjs
export default loadScript('./math.js')
    .then(_ => {
        const math = window.math
        delete window.math
        return math
    })

// cardinalSpline.mjs
import math from "./math.mjs"

// This function is needlessly async, if math.js were an ES module
// this function would easily be synchronous, only the
// fact that I had to load a classic script is this async
async function cardinalSpline(points, divisions) {
    const m = await math
    // compute cardinal spline points here
}
```

The worst part about this is if *any* module needs to load a classic
script it potentially explodes throughout the code base converting
many previously synchronous operations into needlessly asynchronous
ones.

The whole point of my dynamic module idea was so that a classic script
can be added as a dependency which is part of the module graph, but
doesn't cause an explosion where previously synchronous functions
become asynchronous just because of a classic script.

Now I've never actually let the explosion thing happen because instead
of turning all the codebase into async functions for otherwise
synchronous things I tend to just take the code of the library,
convert it to a module myself and then use it. But this is time
costly, converting all these classic scripts (or pulling out parts of
them I need) into ES modules is just cumbersome.

The fact that dynamic modules allow for potentially *any*
fetch/parse/evaluation desired (e.g. WebAssembly, HTML modules,
anything you want really) is just a nice consequence of the problem I
was trying to solve, the fact it allows for so many generic use cases
is why I suggested it should be part of the language itself.

If there's no interest in implementing dynamic modules then I might
just suggest the idea of `import math from "script:./math.js"` as part
of the HTML spec for loading classic scripts as part of the module
graph, but I think dynamic modules would be more powerful and useful.

On 8/21/17, Andrea Giammarchi <[hidden email]> wrote:

> I've solved this (for my needs) long time ago, the pattern is the
> following:
>
> ```js
> export default new Promise(async $export => {
>   // await anything that needs to be imported
>   // await anything that asynchronous
>   // finally export the module resolving the Promise
>   // as object, function, class, ... anything
>   $export(
>     {module: 'object'} ||
>     function () {}     ||
>     class Anything {}
>   );
> });
> ```
>
> You can do pretty much everything you need as both consumer or exporter.
>
> ```js
> // ES2017 Asynchronous Export
> // module.js
> export default new Promise(async $export => {
>   const module = await Promise.resolve(
>     {my: 'module'}
>   );
>   $export(module);
> });
>
> // - - - - - - - - - - - - - - - - - - - - - - - - - - - -
>
> // ES2015 consumer
> import module from './module.js';
>
> module.then(exports => {
>   // will log "module"
>   console.log(exports.my);
> });
>
> // - - - - - - - - - - - - - - - - - - - - - - - - - - - -
>
> // ES2017 consumer
> (async () => {
>   const module = await (
>     await import('./module.js')
>   ).default;
> })();
>
>
> // - - - - - - - - - - - - - - - - - - - - - - - - - - - -
>
> // ES2017 consumer and exporter
> export default new Promise(async $export => {
>   const module = await (
>     await import('./module.js')
>   ).default;
>   $export({module, method(){}});
> });
> ```
>
> The pattern easily inter-operate with CommonJS
>
> ```js
> // CommonJS consumer and/or importer
> module.exports = new Promise(async $export => {
>   const module = await require('./module');
>   $export({module, method(){}});
> });
> ```
>
> I still don't understand why it's difficult to imagine asynchronous exports
> when it's apparently normal to imagine asynchronous imports .... but that's
> another story.
>
> Best Regards
>
>
>
>
>
>
> On Sun, Aug 20, 2017 at 8:35 PM, dante federici
> <[hidden email]>
> wrote:
>
>>
>>    - Unable to load classic scripts (and other types of resources
>>    statically e.g. conditional modules) as part of the module graph
>>
>> How are conditional imports static? In both examples I see the module as
>> being async, and therefore every dependent module is async. Your "dynamic
>> but static" is explicitly using "then" -- or are you implying a module
>> exporting async resources is a better solution than an async module?
>>
>>
>>    - Unable to specify more specific behavior for a module to prevent
>>    duplication
>>
>> By passing arguments in, what do you expect to occur? Do you expect the
>> module itself to be run with those arguments, exporting a set of things?
>> How is that any better than just importing a constructor function from
>> the
>> module/library? This problem sounds like designing the library in a
>> better
>> way would make more sense than affording config to be passed into import,
>> which would mean each import would re-run the module, so no caching.
>>
>>
>>    - Either have to have lots of almost duplicate import declarations or
>>    have to load unnecessary files
>>
>> I can see a benefit for reducing files in the static export -- that
>> suggestion has been a good example of existing problems with tree shaking
>> d3, to which the response has been "design better exports". As for the
>> multiple fetch part of the problem, HTTP/2 spec addresses the performance
>> hit for that, and it's effectively what you're asking the "static" prefix
>> to assert. Out of curiosity, how would you expect static to work in the
>> first place? Who would do the assertion that it doesn't depend on any
>> other
>> symbol in the module it is a part of?
>>
>> I feel like out of these, the solution is much closer to "Better library
>> design". I'm still not 100% on how your dynamic example addresses "turns
>> my
>> code async".  Static export is an interesting one -- effectively asking
>> for
>> pure symbols. Maybe identify an entire file as "load only these symbols,
>> ignore other source"?
>>
>> _______________________________________________
>> es-discuss mailing list
>> [hidden email]
>> https://mail.mozilla.org/listinfo/es-discuss
>>
>>
>
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss