James Browning (2017-08-21T02:00:09.000Z)
thejamesernator at gmail.com (2017-08-21T02:28:09.943Z)
> 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.mjs 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 staticness of names in ES modules. Concerning the other point modules in the browser are *already* async, it's just not 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's 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.
thejamesernator at gmail.com (2017-08-21T02:02:33.379Z)
> 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.mjs 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 staticness of names in ES modules. Concerning the other point modules in the browser are *already* async, it's just not 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.
thejamesernator at gmail.com (2017-08-21T02:01:51.285Z)
> 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.mjs 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 staticness of names in ES modules. 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.
thejamesernator at gmail.com (2017-08-21T02:01:10.886Z)
> 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.mjs 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.