JavaScript Dynamic Imports

Dynamic imports are slightly different from static imports. While static imports have a range of limits, dynamic ones are more flexible and multidimensional. For instance, static imports are limited to the top level of the file, can’t be loaded conditionally (inside if), and the name of the package might not be determined during execution.

Instead, all the actions above can be done with dynamic imports.

Let’s start at the beginning. Export and import statements covered in the Chapter “Export and Import” are known as “static”. The syntax is quite strict and straightforward.

But, still, it won’t allow you to generate any parameters of import dynamically.

The module path should be a primitive string and not a function call. Let’s consider an example that is not valid:

import...from getModuleName(); // Error, only from "string" is allowed

Conditional importing or importing at runtime is not possible either:

if (...) {
  import...; // Error, not allowed
}
{
  import...; // Error, we can't put imports in any block
}

The reason is that the aim of import/export is providing a backbone for the code structure. It is very convenient as the structure of the code can be analyzed, the modules can be grouped (bundled) into a single file by specific tools, the untouched exports can be deleted (“tree-shaken”). The simplicity of the export/import structure makes all that possible.

Now, we are going to explore the ways of importing a module dynamically, on-demand.

The Import() Expression

The import(module) expression is aimed at loading the module and returning a promise, which returns into a module object that includes all its exports. You can call it from any place inside the code.

You can use it dynamically in any part of the code, as follows:

let module_Path = prompt("Which module to load?");
import (module_Path)
.then(obj => < module object > )
.catch(err => < loading error, if no such module > )

There is an alternative option, as well: using let module = await import(modulePath) inside an async function.

Let’s say you have the module welcome.js, like in the example below:

// welcome.js
export function welcome() {
  console.log(`Welcome`);
}
export function soon() {
  console.log(`See you soon`);
}

Now, you want to proceed with the dynamic import.

So, the example of the dynamic import will look like this:

let {
  welcome,
  soon
} = await
import ('./welcome.js');
welcome();
soon();

It can also be used for the default export. So, if the welcome.js has the default export, its example will be as follows:

// welcome.js
export default function () {
  console.log("Module loaded (export default).");
}

The next step should be accessing it. For that purpose, you can use the default property of the module object, like this:

let obj = await
import ('./welcome.js');
let welcome = obj.default;
// or, in one line: let {default: welcome} = await import('./welcome.js');
say();

Please, take into consideration that dynamic imports operate within regular scripts and don’t require script type="module".

Although import() is similar to a function call, it is a specific syntax that happens to use parentheses ( like super()). Therefore, it’s not possible to copy import to variable or implement call/apply with it; it is not a function.

In brief, dynamic imports are handy in situations when you intend to load a module conditionally, either on-demand. The static ones are useful for loading initial dependencies and can benefit from readily from “tree shaking” and tools of static analysis.




Do you find this helpful?

Related articles