> 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