Dynamic Imports in JavaScript
A review of the new ES 2020/ES 11 feature
Back in the day, RequireJS was a popular library that provided module system in JavaScript. It was an implementation of AMD (Asynchronous Module Definition). The library helped with organization of code and managed dependencies. It allowed defining and importing modules (either upfront or conditionally, on need basis). As JavaScript evolved, with ES 6 (also called ES 2015) native support for the modules was added to the language. Initially, support was only for static import, which need to be evaluated pre-runtime.
Many of us have used ES 6 import statements, which are placed on top of the file. All the imports need to be evaluated pre-runtime, which means, the module name cannot be dynamic. The complete module name string need to be specified — no interpolation with variables. It helped few features like JavaScript bundling, Tree shaking etc. However, one cannot import a module on the fly. Say, we want to import a module only on click of a button, when we launch a model dialog, on navigating to a page, these scenarios are not supported initially.
There has been a proposal in works to support dynamic imports. It’s a function like syntax to import new modules on-the-fly. TC 39 proposal for Dynamic imports is in Stage 4 now. Later in the article, a section details TC 39 proposals and the four stages.
Dynamic Imports is ready for prime time in ES 2020 (ES 11). It has been adapted by major browsers. See figure-1 for browser compatibility chart on Mozilla Web Docs.

Using import() in to-do code sample
Let’s look at the feature with the help of a use case. We will build couple of screens in a to-do application with JavaScript. The first screen shows list of to-dos. The second screen has a text field and a create button for a new to-do.

We use three modules.
- The first module, data-module.js provides list of to-dos. For simplicity, sample uses hard-coded JSON array.
- The second module, todo-list-module.js exports a function, getTodosMarkup(). The input argument for the function is to-do list data. It returns DOM string created with the data. Consider the following code snippet, which imports the two modules in index.js. It is the root module in the code sample.
import getData from './data-module.js';
import { getTodosMarkup } from './todo-list-module.js';let list = getData();
document.getElementById("todos").innerHTML = getTodosMarkup(list.todos);
- If user just works with list screen, no other module is imported. However, if user tries to navigate to Create To-do functionality, the third module is imported on the fly. Consider the following code snippet and figure-3. Notice, create-todo.js shows up in Google Chrome network tab only when user navigates to Create To-do screen.
const handler = () => {
import ('./create-todo.js').then((module) => {
// access any exported function in the module.
// following createTodo() generates DOM string for Create Todo
// set the DOM string on an element.
document.getElementById("createTodo").innerHTML = module.createTodo();
});
}// click event handler added on the hyperlink, “Create Todos”.
document
.getElementById("btnCreateTodo")
.addEventListener("click", handler);
The handler() function performs dynamic import. Notice, the import() with module name as a parameter. The function returns a promise, when resolved provides a module reference. On the module object, all exported functions can be accessed. In the code sample above, we use createTodo() function (exported from the module).

Code Sample
Follow the link for complete code sample. See the JavaScript modules data-module.js, todo-list-module.js and create-todo.js. Index.js is the main module that imports others modules.
For running the code sample on your machine, follow the instructions here.
Please note the following alternate syntax with async/await while using a promise. We may use the async/await approach for any JavaScript promise, need not be a dynamic import of a module. This was a feature of ES 8 (ES 2017). Read more about ES 8 features here.
const asyncHandler = async() => { if (isList) {
isList = false;
let module = await import ('./create-todo.js')
document.getElementById("createTodo").innerHTML =
module.createTodo();};
The import() function also supports run time string interpolation of the module name. Consider the following code snippet. The module name can be defined on-the-fly, based on a variable name. This does not work with static imports.
/* The module name is unavailable pre-runtime as it's created on the fly with using CREATE_TODO_URL */import (`./${CREATE_TODO_URL}.js`)
The TC 39 proposal for dynamic import has been there for a while. Going by the commits in TC 39 GitHub repository, it’s available from 2016. (link to the proposal). Chrome has been supporting the feature from version 63. However, the proposal is in Stage 4 as part for ECMA Script 2020 (ES 11).
Notes
Use the <script > element with type attribute for browsers to consider the JavaScript as a module.
<script type="module" src="./JS/index.js"></script>
Common JS is a popular choice for module system in Node.js and server-side JavaScript development. For native module system support in JavaScript, Node.js recommends naming the files with .mjs extension(instead of .js).
How are new features added to JavaScript?
For many other languages, it’s just one run-time or a compiler that need to be upgraded to support new features. JavaScript is unique; Features need to be implemented by various browsers. They are built and maintained by different teams owned by various organizations. Everyone has to come together for continuous upgrade to the language.
ECMA Script (ES for short) is a language specification created to standardize JavaScript. ES5 was a popular JavaScript version released in 2009. ES 6 was out in the year 2015. It was also referred to as ES 2015. ES 6 on-wards the upgrades have been continuous with an annual revision. As mentioned earlier, the Dynamic Import feature described in this article is part of ES 2020 or ES 11.
ECMA TC39 is a committee responsible for evolving ECMA Script. TC 39 uses the following stage definitions to move a JavaScript feature to one of the ECMA Script releases. Every new JavaScript proposal goes through the stages to be available on all browsers.
- Stage 0 — Strawperson or a new specification (was called strawman, renamed now — rightly so :)
- Stage 1 — Proposal. Demonstrate need for an addition, solution and challenges with formal spec language.
- Stage 2 — Draft. Describe syntax and Semantics.
- Stage 3 — Candidate. Allow users to experiment and provide feedback. The specification might be redefined based on feedback. Most browsers start adapting features at this stage. Feature might not be ready for prime time.
- Stage 4 — Finished. Feature is ready and available for all users.
Follow the link for TC 39 process documentation
A note from the Plain English team
Did you know that we have four publications? Show some love by giving them a follow: JavaScript in Plain English, AI in Plain English, UX in Plain English, Python in Plain English — thank you and keep learning!
Also, we’re always interested in helping to promote good content. If you have an article that you would like to submit to any of our publications, send an email to submissions@plainenglish.io with your Medium username and what you are interested in writing about and we will get back to you!