JavaScript dynamic import() & export
Currently at stage 3, the next version of ECMAScript will likely bring in the ability to dynamically import, in a non blocking way, asynchronous modules.
The rationale behind is that, specially on the Web, you might want to import modules on demand, as opposite to requiring mandatory tooling to bundle all your scripts upfront.
While a similar approach, known as AMD, historically lost the battle against CommonJS bundles, today we have language features such async
and await
as well as better primitives such Promise, so that an asynchronous import can be as easy as: const module = await import('./module.js');
But what about asynchronous exports ?
For the same reason we have both import
and export
mechanisms to define our modules, we might want to also create modules that depend on conditional modules, capable to also export their functionality asynchronously.
A few use cases for this scenario are:
- polyfills based on features detection, importing arbitrary amount of modules before exporting their functionality
- Custom Elements that depends on other Custom Elements, because bundling them all at once makes components impossible to reuse
- Custom Elements also have already a Promise based mechanism such
customElements.whenDefined('comp-name').then(useIt)
; combining this with dynamic import that would resolve and register components once is the most natural step forward - actually exporting features once the DB connection has been made, once the remote file has been parsed, after any asynchronous operation essential to make the exported module usable is fulfilled.
Last point indeed doesn’t even need an asynchronous import
to be useful, it’s a quite common server-side use case already.
Previously, on this channel …
I’ve already posted about asynchronous import/export, and I’ve made a proposal to CommonJS and NodeJS folks, with many developers against the idea of an asynchronous import ’cause “nobody wants it”.
If worth nothing, I really do want it, and moving forward to a Web behind HTTP/2 and better caches mechanism, like the one provided by Service Workers, I don’t see any reason for not having asynchronous exports too.
I think indeed it’s time to let bundlers go, but we surely need to agree on a simple and universally compatible approach to export asynchronously a module.
An import(…) polyfill as playground
Even if I’ve stated myself that the import()
function is impossible to polyfill, I’ve actually managed to create a ~1KB bootstrap script capable of bringing both native and transpiled modules to the browser, injecting at runtime the import()
function in all its glory:
- compatible with relative, absolute, and remote paths
- compatible with both static and dynamic imports
- compatible with native JS or Babel transpiled code, aka compatible with every old and modern ES5 compatible browser
Everything is on GitHub, and to start playing with it you just need to include the import.js
script and define the data-main
attribute, pointing at your main/index JavaScript file.
There are a couple of live examples too, a transpiled one that should be green on every browser, and an example based on native ES2015 import/export features, requiring a compatible browser such Safari on macOS or GNOME Web on any Linux.
There is also a test folder with a more convoluted test comparing native and sync imports VS dynamic, preserving the asynchronous export pattern I am going to explain in the next paragraph.
Once async, All async
I’ve discussed this pattern already in the ES-Discuss ML, and while there’s room to bikeshed forever about patterns to export dynamically and/or partially a module, I’m always up for KISS and YAGNI approaches.
CommonJS taught us that …
Most common pattern to export modules in CommonJS is via module.exports = {...};
Functions, classes, objects, these are all defined as single entry of the module object, something “re-sugared” on ES2015 modules via export default {...};
So how about we export once asynchronously through a single default entry point?
JS Asynchronous Export in a nutshell
Congratulations, you’ve learned all it takes to export an async module!
Now, let’s quickly go through the features this pattern enables:
- you can still statically
import mod from './mod.js';
on the top level, whenever you have synchronous dependencies - you can
await anything;
inside the promise callback, including dynamicimport(...)
, db connections, remote url fetching, etc. - any module consumer can exploit the pattern as needed
- the pattern always works, no matter if you want to import or export asynchronously
The pattern can also easily inter-operate with CommonJS
which in turns it means it’s compatible even with bundled files, or more generically, this pattern is compatible with most common, real-world, modules use cases.
Simplifying the “await (await …)” bit
Funny enough, using this pattern in CommonJS looks easier and more straight forward than native ES2015 modules, and the reason is that the default
export in CommonJS is always implicit.
If we don’t use transpilers for our modules, and we target CommonJS as module system, there’s nothing we should do: all bundlers would just work, some might even decide to make the browserrequire
smarter and load asynchronously, instead of bundling all modules: it’s your call.
However, if we write code through transpilers, we probably want to simplify the import bit, including the ability to import static or more dynamic modules at once. Following all it takes to have such functionality.
As compact as it looks, the above module can simplify a lot of dynamic imports.
Future proof and future friendly
In the very same ML where I’ve raised questions about this pattern, somebody already mention the possibility to introduce export await
in all its forms, including default
.
This means that in the future even static imports will be compatible with the pattern mentioned in this post, and the only difference will be the keyword await
between default
and new Promise
.
… what about Custom Elements ?
You’re right, I’ve mentioned them but I haven’t provided any concrete example so here I come.