This document by Ben Newman, and the ensuing Twitter conversation. You should read those first.
I, Rich Harris, am
- just some guy
- not involved in standards efforts at all
- tbh I don't even work in the technology industry
- but I did write a couple of module bundlers (Esperanto and its successor, Rollup, both of which have had modest success)
- so I do have a dog in this fight
- though Ben is a much smarter programmer/human than me, so he's probably right and I'm probably wrong
Now that's out of the way: Ben has lots of good examples of code that is made cleaner and clearer by allowing import
declarations inside blocks. And this...
To put it in even stronger terms, if we do not allow ourselves to nest
import
declarations, then serious applications will never be able to stop using the CommonJSrequire
function, and JavaScript modules will remain an awkward hybrid of modern and legacy styles.
Is that a future we can tolerate?
...definitely makes you sit up and take notice. But it's not actually true:
if ( weNeedFoo ) {
loader.import( './foo.js' ).then( foo => {
// tada! conditional dynamic loading.
// note: i'm not at all sure that this is
// what it'll look like. will loader be
// called `loader`? will it take a single
// string as its argument? i actually have
// no idea. But you get the general point.
});
}
That's definitely not as pleasant or convenient (or as statically analysable, FWIW) as an import
declaration, but it's pretty clear what's going on here. No surprises about order of execution and whatnot.
Guy suggests that we could treat an inline import
declaration as syntactic sugar:
// this...
if ( weNeedFoo ) {
import foo from './foo.js';
}
// ...is equivalent to this:
if ( weNeedFoo ) {
const foo = await loader.import( './foo.js' );
}
But that's not true, because you can only use await
inside an async function. You could make the whole module async, but then you can't use import
inside a non-async function:
function doFoo () {
import foo from './foo.js';
foo();
}
// doThis and doThat happen in the same tick, which means
// we can't sit around and wait for `foo`
doThis();
doFoo();
doThat();
In Node, that's sort of okay, because (in spite of Noders constantly chastising developers for using synchronous functions where async equivalents exist) require
is synchronous. So we can just use that. But across a network? Nope.
So the implication is that we would make all the modules available (loaded, but not necessarily executed yet) at initial execution time. In other words, for that conditional import
to work in a browser, we'd have to defensively load the entire speculative dependency graph – all the modules that we might need, plus all the modules they might need, and so on – just so foo
could be there when we needed it.
This is how Browserify works, by the way – this code...
const lol = Math.random() < 0.5 ?
require( './foo.js' ) :
require( './bar.js' );
...will cause the contents of both foo.js
and bar.js
to be included in your bundle. Which probably isn't what you wanted.
Maybe we have nested imports, and just get into the habit of not using them (and using loader.import
instead) in code that's intended for the browser. But one of the great things about ES2015 modules is that they will make sharing code between server and client much easier. The only way nested import
declarations really make sense is if we have a node-centric view of the world.
It's entirely possible that I've just completely misunderstood the whole thing, in which case sorry. Like Ben I believe that native modules will have a more significant impact on developer happiness and productivity than any other recent or upcoming JavaScript feature, so it's important we get this stuff right.
What happens if you have some code that imports a module at the top level
but then some other code that imports the same module asynchronously?
Doesn't that force the module to be registered via the
loader
API anyway, and not inlined the way Rollup would currently handle the synchronous top-levelimport
declaration?If any subset of your modules might have to be registered in a way that allows for selective/lazy loading, then it seems Rollup is going to have to support that style one way or another, and then it's not a big leap to supporting nested
import
declarations.