Exploring ES2020 features - Dynamic Import

It’s 2020 and our beloved JavaScript is getting some new features. They are-

  • - String.prototype.matchAll
  • - import()
  • - BigInt
  • - Promise.allSettled
  • - globalThis
  • - for-in mechanics
  • - Optional Chaining
  • - Nullish coalescing Operator

In this post, we’re going to look at the import().

Import()

In ES2015 (popularly known as ES6), a built-in module system was introduced into the JavaScript. Since then, we’ve been statically loading modules natively into our JavaScript code. As time goes by, we’ve wondered if we could load modules asynchronously improving the performance. ES2020 introduces a “function-like” import() syntax to do just that.

The ES2015 module system has enabled us to import modules statically. Whether we need the module now or sometime later, we import it first. All imports need to be resolved before the JavaScript engine proceeds to code execution.

But we’ve also noticed that we do not need to load all the modules first. We’ll just be fine loading some of them during runtime according to the application requirement. It would improve the initial load times providing the performance boost. It would also enhance the application robustness by not failing to load a non-critical module during the initial load.

The syntax is import(module specifier). The module specifier can be string literal or template literals (commonly known as template string). It returns a promise after the evaluation of the module and its dependencies.

As of this writing, import() is supported in Chrome > 63, Firefox > 67, Safari > 11.1, Edge > 79. It can be used in Node v13.2.0 without the --experimental-module flag. The support for dynamic import syntax is available by default from Babel 7.5.0.

To use the ES2015 module system in the browser (without the bundling tools) in the script tag, we need to set the type attribute value to the module.

<script type="module">
  import add from './add.js';

  add(7, 4); // returns 11
</script>

We don’t need to set the type to use the ES2020 dynamic import. import() will work from both scripts and modules.

<!-- We don't need to set the type -->
<script>
  import('./add.js')
  .then(module => module.default(7, 4)) //returns 11
  .catch(error => // log error);
</script>

We can have two types of exports in the module system - default export and named export. The previous example shows how we import the default export (module.default). The named export can be imported in the following way.

// this code resides in subtract.js file
const subtract = (a, b) => a - b;

export { subtract };

// in index.js file
import('./subtract.js')
  .then(module => module.subtract(7, 4)) // returns 3
  .catch(error => // log error);

The module specifier can be specified by using the template literal too. This increases the flavor of dynamic importing. For example, we can let the user choose which operation they want to perform and load only that file. In our case, we can load add.js or subtract.js. Both files follow the default export.

<body>
  <form>
    <div>
      <input type="radio" id="add" value="add" name="operation">Add </input>
    </div>

    <div>
      <input type="radio" id="subtract" value="subtract" name="operation">Subtract</input>
    </div>

    <p>The operation is <span id="selectedOperation"></span> and the result is: <span id="result"></span></p>
  </form>

  <script>
    const addInput = document.getElementById('add');
    const subtractInput = document.getElementById('subtract');
    const selectedOperationEl = document.getElementById('selectedOperation');
    const resultEl = document.getElementById('result');

    const handleChange = event => {
      const { value } = event.target;

      selectedOperationEl.innerText = value.toUpperCase();

      import(`./${value}.js`) // using template literal
        .then(module => {
          resultEl.innerText = module.default(7, 4);
        })
        .catch(error => console.error(error));
    };

    addInput.addEventListener('click', handleChange);
    subtractInput.addEventListener('click', handleChange);
  </script>
</body>

The result will change based on the selected operation. The browser will evaluate and load the necessary script in runtime. I’m using Chrome 80 for this example. The codes are available here.

This just has the function like syntax, but not actually a function. It does not inherit from Function.prototype. So call, apply, bind does not work here.

Are you using the dynamic import already? Please share your experience in the comment.

Share on

Get the latest post in your inbox!

Kamal Sharif
Hi, I'm Md. Kamal Sharif. I'm a senior frontend developer based in Dhaka, Bangladesh.

Follow Blog

Posts you might like

Git Rebase - how to squash multiple commits into a single commitWhy I decided to become a software engineer at the age of 26Advance usage of Array.prototype.reduce() in JavaScript