Follow-up to Top-level await is a footgun – maybe read that first
Here are some things I believe to be true:
- Static module syntax is beneficial in lots of ways – code is easier to write (you get better linting etc) and easier to optimise (tree-shaking and other things that are only really possible with static syntax), and most importantly, faster to load (it's trivial for a module loader to load multiple dependencies concurrently when they're declared with a static syntax – not so with imperative statements like
require(...)
orawait import(...)
). - App startup time is perhaps when performance is most critical. (You already know this, I don't need to cite the studies.)
- If you're in favour of constructs that jeopardise app startup time, you are anti-user. Top-level
await
is such a construct.
But the primary motivation for top-level await – loading modules dynamically depending on the environment – is an important and legitimate one. How do we support it with static syntax?
With config. Here's some pseudo-code:
<script>
System.paths = {
d3: 'https://unpkg.com/d3@5',
utils: isModernBrowser() ? '/js/utils-modern.js' : '/js/utils-legacy.js'
};
System.import('/app.js');
</script>
In this example, we have all the power we need to load modules conditionally, without sacrificing the benefits of static syntax.
The difference of course is that some fourth-order transitive dependency can't conditionally load different versions of itself unless you've configured that in your paths
config as well. But that's a good thing! The last thing you want to worry about when developing an app is that some utility from the dark corners of npm that you didn't even know about has the power to bork up your otherwise carefully-engineered app loading experience.
A more advanced variant of this approach would involve a function hook that resolves a module ID programmatically.
When the user clicks on the 'about' page, you can still load it asynchronously:
async function loadAboutPage () {
const view = await import('/js/views/about.js');
view.render( document.querySelector( 'main' );
}
If you're into PRPL then of course you can also prefetch that module when you get a spare moment. The point is, this is purely about not introducing ways for modules to sludge up the initial load. Top-level await doesn't give us any new expressive power, it just introduces slightly nicer syntax but with a real cost.
Between JavaScript modules and HTTP2, we have the potential to make it really easy to write fast-loading apps. Top-level await could undermine that. Let's not do it.
God, thanks! I've been wondering about how to do dynamic loading and this fits my use-cases just fine.
I wonder if it matters tho. It seems modules simply are not anywhere near ready. And not clear the System object will ever exist, with config. Certainly the initial module implementation with be <script type = module ...>. God knows what node plans, they certainly have argued for quite a while (.jsm??).