Every year, an edition of the ECMAScript Language Specification is released with the new proposals that are officially ready. In practical terms, the proposals are attached to the latest expected edition when they are accepted and reached stage 4 in the TC39 process:
In this article, we’re going to examine and explain the “Dynamic Import” proposal that has been reached stage 4 and belongs to ECMAScript 2020 - the 11th edition.
The content is available as a video as well:
Modules are merely different files containing split scripts including variables and functionality - instead of having them in a single tedious file. Technically, it’s being done by exporting the module features using the
export statement and importing them into other files using the
When using the
import statement that arrived with ES2015, the code is loaded statically - which means the exported module code is evaluated up front at load-time, before that the importer code is executed. This way is preferable when possible to load the initial modules and to allow static analysis including tree shaking.
That said, sometimes we might want to load a module on demand at runtime - such as when the module isn’t really needed for loading, the import specifier string should be computed dynamically, loading the module from a regular
<script> element and such.
So, let’s explain how we actually can make those possible based on the Dynamic Import proposal.
The proposal specifies a new syntactic keyword,
import(), that can be invoked dynamically from the code - and that’s why it’s also named “Dynamic Import”.
Let’s see the official definition of the specification:
The process of a dynamic import originally started by an import() call, resolving or rejecting the promise returned by that call as appropriate according to completion.
From the definition we directly understand that the
import() keyword acting like a function that returns a promise.
In detail, the function-like receives a specifier of a module to load and the returned promise resolves into an object containing all the exports of the module.
Note: The keyword isn’t a real function but rather a convenient syntactic form looking like one, which means it doesn’t inherit from
Function.prototype and methods such as
apply aren’t supported (and needed).
Loading at Runtime
ES2015 introduced the static
import statement allowing to load exports from a module by a string literal representing its specifier:
Clearly, assuming the functionality is truly exported using the
This works since when we import any exports from an external module - it’s actually attached into the current scope via a pre-runtime “linking” process. Although static importing is probably the way to go in most cases, it only can be used at the top level of the file. In other words, it doesn’t allow to import the module on demand or conditionally.
Well, this is the part that Dynamic Imports come into the picture - because the new
import() keyword can be invoked at any level of the file:
As said, the promise resolves into a module object containing its exports at runtime - so we can simply invoke our exported function down the file.
And going forward, the real power is the ability to load the code lazily on demand:
In the example we import the module only after a button is clicked. This absolutely might boost the load-time performance in case the module isn’t needed for loading but rather only after some trigger occurs.
Another thing to mention is that compared to static imports, we definitely can involve
async functions to make the syntax cleaner:
More than that, it’s also possible to condition the loading:
We can make the module be loaded only whether some condition is true, for example - if a feature is enabled by the user, a polyfill is necessary on legacy platforms or just when it brings heavy side-effects.
Dynamic Module Specifier
We already said that the static
import statement expects a string literal of the module specifier. This naturally means that the module specifier is fixed.
In contrast, the new
import() keyword allows computing the module specifier dynamically:
Basically in that example we simulate a router loading the modules lazily based on URL hashtag change events. Once
hashchange is fired, we extract the new “route” followed by the hashtag. Then, we simply resolve from it the module specifier and pass the computed value to the
Note: We assumed there are two module files corresponding to the hashtags.
Using Script Element
So far we could combine the
<script> element with
type="module" in order to use static import inside to load as top-level modules. In practice,
type="module" differentiates those scripts from regular scripts and instructs the browser that they are modules - which should be evaluated only once.
But now, with the new
import() keyword, we can load modules inside regular scripts in simplicity:
Here’s how it looks:
We covered today the motivation behind the “Dynamic Import” proposal and explained the possible abilities through concrete examples.
- The proposal belongs to ECMAScript 2020, which is the 11th edition
- When using the
importstatement, the code is evaluated statically up front at load-time - before that the importer code is executed
- When using the
importstatement, the module specifier is fixed
- The proposal specifies a new syntactic keyword written
import()acting like a function that returns a promise resolves into an object containing all its the exports of the module
import()can be invoked at any level of the file
import()allows to load a module on demand or conditionally at runtime
import()allows computing the module specifier at runtime
import()can be used inside regular
Here’s the repository containing the code of the final example.