> 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 <andrea.giammar...@gmail.com> 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
> <c.dante.feder...@gmail.com>
> 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
>> es-discuss@mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
>>
>>
>
_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to