Answering for ES2015 modules (import
and export
, not require()
):
TL;DR - The whole module is loaded per spec, but an implementation (or bundler, if you use one) may be able to optimize when it can reliably determine that it can do so without violating the spec. (Whether it does so is another question.) Amongst other things, that means your code would need to avoid eval
or new Function
in code that can touch the imported module binding, and not use dynamic property access on it. (E.g., mod.foo
would be fine, but mod[name]
where name
is determined at runtime would not.) Personally, I lean toward importing what I need.
A bit more detail:
Let's say we have module1.js
:
import { a } from "./module2.js";
function main() {
a();
}
main();
...and module2.js
:
export function a() {
console.log("a called");
}
export function b() {
return c() * 2;
}
function c() {
return 42;
}
If module1
is the starting point, it goes roughly like this:
module1.js
's source text is parsed and its imports and exports are identified and used to fill in (conceptual) objects representing its live bindings for imports and exports; the values of those bindings are not initialized yet.
- Since it imports from
module2
, the same thing is done for module2.js
.
- Since that's the full graph, a depth-first evaluation process starts evaluating modules. Our deepest module is
module2
, so module2.js
's source text is evaluated (run), initializing its export bindings (and the other private things it creates and fills in).
- Then, all its dependencies satisfied, the same is done for
module1.js
.
(It gets complicated for cyclic-dependencies, let's leave those aside.)
Remember that bindings are live, so if (for instance) module2
changed the value of a
, that change would be reflected in the binding that module1
is using.
So at this point, both modules have been fully loaded and initialized, and that's where the spec leaves off.
Subject to adhering to the specification, implementations can do tree-shaking: Identifying things they can prune once the full tree is established, or even things they can avoid creating in the first place (though with JavaScript, static analysis can only take you so far; but avoiding eval
, new Function
, dynamic property access, and such helps). So in theory, a sufficiently-optimized engine could get rid of module2
's b
and c
since they aren't used by anything (a
doesn't reference them and doesn't use eval
or new Function
, and module1
only uses a
), or perhaps even avoid creating them at all.
Some bundlers also do tree-shaking, trying to determine what parts of a module aren't used anywhere and leaving those bits out of the bundled file.
Now suppose you imported "the whole module":
import * as mod2 from "./module2.js";
function main() {
mod2.a();
}
main();
I haven't gotten down-and-dirty with the details of JavaScript tree-shaking, but my sense is you've made life more difficult for a bundler or JavaScript engine to do tree-shaking, because mod2
has a property referencing b
and it has to prove that you never use it.
Note that if you really want the prefix, you can always rename on import:
import { a as mod2_a } from "./module2.js";
...though granted it's a lot more verbose.
There's been some talk on es-discuss about adding further syntax to import
to simplify that (though not a lot so far, I think the focus in this area is on import()
and import.meta
for now), so perhaps eventually you'll be able to do something where you list the things you want and a pseudo-object to put them on.