Best Guide on Dynamic Import in JavaScript for Importing Modules

Robin
Updated on April 3, 2022

JavaScript modules were introduced in the version ES2015 also known as ES6. At first, we could use import and export statements to divide our code into smaller chunks.

By using JavaScript modules we can make our application easy to manage or update in the future. In ES2020, the dynamic import was added to the JavaScript modules which gave us more control.

In this article, I will show you how you can apply this dynamic import feature in JavaScript to your application and how it works.

Will dynamic import improve your website performance?

Let's answer all these questions one by one. But before that, let me give you a brief overview of static import and export statements. Because to understand the actual power of dynamic import you need to have the basic knowledge about them.

Also Read: Best Ways to Create Dynamic HTML Element & CSS in JavaScript

How does Import and Export Work in JavaScript?

Before importing something from another file, you need to export it. You can export anything from a JavaScript file like variables, functions, etc.

JavaScript modules system performs 2 things:

  • Importing code from another file.
  • Exporting code to use in other files.
          // utils.js

// Default export
export default () => {
    return 'This is default export';
}

// Named export
export const getBio = (firstName, age) => {
    return `${firstName} is ${age} years old.`;
};

export const getGreeting = (name) => {
    return `Hello ${name}!`;
};


        

You can export things in 2 ways, default export and named export. To export them you have to use the export keyword before them. You just have to use the default keyword after export keyword to make it the default export.

You can have only one default export from a JavaScript file. In the case of named export, there is no limit. You can have as many as you need.

For example, I am exporting getBio() and getGreeting() functions as named exports and another function as default export from utils.js file. Now I can import these functions into any JavaScript file to use.

          // main.js

import showMessage, { getBio, getGreeting } from './utils.js';

console.log(showMessage());
console.log(getBio('Max', 24));
console.log(getGreeting('Max'));

        

To import those functions, you have to use the import keyword. Here, I am importing the default function with the name showMessage in the main.js file. For the default module, you can give any name you want while importing.

To import named exports, you have to give the exact name of the exported modules in between curly brackets.

          <script src="./main.js"></script>
        

If you load the main.js file in your HTML file like this, it will not work. It will give you an error in your browser console.

Uncaught SyntaxError: Cannot use import statement outside a module

Because if you use module imports in your JavaScript file and try to load it in the HTML file, browsers can not understand it by default.

You have to tell the browsers that you have used module imports inside the JavaScript file. You can do that just by adding type attribute to the <script> tag.

          <script type="module" src="./main.js"></script>
        

The value of the type attribute will be module in this way browser will know that this JavaScript file contains module import syntax.

Also Read: When and How to Use Async Defer Attributes in Script Tag

What is Dynamic Import in JavaScript?

Dynamic import is the way to import some chunk of code on demand. In this way, you only load the code that you need. So, your initial bundle size will be smaller.

Basically, this technique ensures that certain modules are only loaded when they are required by the users. It is very useful for lazy-loading.

If it feels difficult to understand, don't worry I will explain everything step by step with real-world examples. You will know how and when you should use dynamic import in your application.

What is The Use of Dynamic Import?

You know the advantages of dynamic import how it makes our code more efficient and reduces the initial bundle size. But it does not mean that it is the replacement of static import and export.

Because both of them have their use-cases. That means, before using them you should know when to use what.

These are some of the reasons why you might want to use dynamic import instead of the static modules:

  • Dynamic import is mostly effective for large modules or files because for a small piece of code it does not seem any different.
  • If your application size is big and static imports are making the initial bundle size larger. In this case, you can use dynamic imports for some parts that are not needed initially.
  • When it is required to generate the import paths dynamically. You can not generate the import paths for static imports on runtime. You can easily do this using dynamic import.
  • If the static imports increase the use of memory usage by your application. In this case, you can make sure that your application downloads less code in the initial load.
  • Sometimes some parts of your application may not appear when the page loads. It does not make any sense to add those codes with the main bundle. Because your users may not use all those parts. So, it is best to download them on demand.
  • When you want to download some modules on the basis of runtime conditions or you want to load different versions of the module.

Does Dynamic Import Improve Performance?

This is one of the main reasons why developers use dynamic import. Because it reduces website load time by decreasing the main JavaScript bundle size.

That means your users have to download and run only the required amount of JavaScript code. Your web pages will load faster. After loading the page, users will be able to download additional scripts according to their actions.

Google also announced that loading speed is an indicator for ranking. If your website loads faster, it will provide a better user experience ultimately you will have a better chance of ranking in Google searches.

Dynamic import also helps to reduce memory usage. So, users with mobile or computer that has less power will get a smooth experience from your website.

As dynamic import downloads scripts on demand, it will save your bandwidth because your server doesn't have to send all the unnecessary code that users might not need. It will also save users' data.

To me, it's a win-win situation for both sides. Dynamic import will increase the performance if you can use it properly.

How to Use Dynamic Import in JavaScript for Importing Modules

You can use dynamic import in your app using the import keyword as a function. This function will accept the path of your JavaScript file.

It returns a promise and loads the modules asynchronously. If the promise resolves successfully, it will give all the module exported content.

But if the promise rejects for some reason, it will throw an error that you can catch and handle programmatically.

          // main.js

import { getBio } from './utils.js';
const showBio = document.getElementById('show-bio');

showBio.addEventListener('click', () => {
    const bio = getBio('John', 30);
    console.log(bio);
});

        

If you have a button that shows user details when someone clicks on it. When a user is clicking on the button, it will execute the getBio() function.

But as you are importing this function from the utils.js file using static import, the browser will import this module automatically even if the user does not click the button.

In such a scenario when you need something on-demand, dynamic import comes into the picture.

          // main.js

const showBio = document.getElementById('show-bio');

showBio.addEventListener('click', async () => {
    try {
        const { getBio } = await import('./utils.js');

        const bio = getBio('John', 30);

        console.log(bio);
    } catch (err) {
        // handle your error here (if any)
    }
});

        

You just have to use import as a function inside the event listener callback function. As this function will return a promise you can use async-await and if you need to handle the possible error then add try...catch for that.

Initially, the browser will not download this module automatically. When a user clicks the button, the module will be downloaded and executed otherwise your user will not get this module.

how dynamic import works in javascript

To confirm it, you can open the "DevTools" in your browser and go to the "Network" tab. In the initial load, the browser will download main.js file, but when the user clicks the button it will download utils.js because this is the file we were importing from the getBio() function.

          // main.js

const showBio = document.getElementById('show-bio');

showBio.addEventListener('click', () => {
    import('./utils.js')
        .then(({ getBio }) => {
            const bio = getBio('John', 30);

            console.log(bio);
        })
        .catch((error) => {
            // handle your error here (if any)
        });
});

        

You can also use .then and .catch instead of async-await if you want. In both cases, it will work in the same way.

Working with Default Export

If your module has a default export and you want to import that default property using dynamic import, you have to do it in a little different way.

You can destructure the object that you get from the promise, just you have to rename the default keyword from the return object. Because default is a reserved keyword in JavaScript so you can not use it as a variable name.

          // main.js

const showBio = document.getElementById('show-bio');

showBio.addEventListener('click', async () => {
    try {
        const { default: defaultFunc } = await import('./utils.js');

        console.log(defaultFunc());
    } catch (err) {
        // handle your error here (if any)
    }
});

        

Here, I renamed the default as defaultFunc but you can give any name you want. Now, you can use defaultFunc() function inside this file. It will be the default function that you have exported from utils.js file.

If you don't want to destructure your object, it is also fine. You can work with dynamic import without destructuring.

          // main.js

const showBio = document.getElementById('show-bio');

showBio.addEventListener('click', async () => {
    try {
        const myModule = await import('./utils.js');

        console.log(myModule.default());
    } catch (err) {
        // handle your error here (if any)
    }
});

        

Here, I am storing the returned object as myModule and I can access the default function as myModule.default() function.

Note: If your default export is a variable instead of function, you can access that as myModule.default without () parentheses at the end.

Working with Named Export

You can import named exports using dynamic import just as a normal JavaScript object. You have to provide the exact name of the function while destructuring. There is no other difference.

          // main.js

const showBio = document.getElementById('show-bio');

showBio.addEventListener('click', async () => {
    try {
        const { getBio, getGreeting } = await import('./utils.js');

        const bio = getBio('John', 30);

        console.log(bio);
        console.log(getGreeting('John'));
    } catch (err) {
        // handle your error here (if any)
    }
});

        

You can import as many properties as you want, separated by (,) commas.

          // main.js

const myModule = await import('./utils.js');

const bio = myModule.getBio('John', 30);

console.log(bio);
console.log(myModule.getGreeting('John'));
        

You can also import them without destructuring. In that case, you have to store the returned object from the promise in a variable and then you can access them from that object as normal methods.

Working with Mixed Export

If you want to import default and named exports at the same time, you have to follow both rules mentioned above.

          // main.js

const showBio = document.getElementById('show-bio');

showBio.addEventListener('click', async () => {
    try {
        const {
            default: defaultFunc,
            getBio,
            getGreeting,
        } = await import('./utils.js');

        const bio = getBio('John', 30);

        console.log(bio);
        console.log(getGreeting('John'));
        console.log(defaultFunc());
    } catch (err) {
        // handle your error here (if any)
    }
});

        

You can destructure both default and named exports together, separated by (,) commas.

Dynamically Import Multiple Modules

Sometimes we might need to import from multiple files. You can use import() function multiple times to do so.

          // main.js

const loadLanguages = async () => {
    try {
        const englishModule = await import('./english.js');
        const spanishModule = await import('./spanish.js');

        // Use the modules
    } catch (err) {
        // handle your error here (if any)
    }
};

        

In this case, the browser will download modules one by one. If modules are not dependent on each other you can use Promise.all() to load them separately at the same time. This will reduce the loading time of your modules.

          // main.js

const loadLanguages = async () => {
    try {
        const [englishModule, spanishModule] = await Promise.all([
            import('./english.js'),
            import('./spanish.js'),
        ]);

        // Use the modules
    } catch (err) {
        // handle your error here (if any)
    }
};

        

Generate ImportSpecifier or Path to Import Modules Dynamically

Another advantage we get by using dynamic import is that we can generate the import path or specifier on runtime according to any conditions.

          // main.js

const loadLanguages = async () => {
    try {
        let englishModule;

        if (version) {
            englishModule = await import(`./english.js/${version}`);
        } else {
            englishModule = await import('./english.js/1');
        }

        // Use the module
    } catch (err) {
        // handle your error here (if any)
    }
};


        

Here, I am checking if the user is giving any version number or not. If a user gives a version number, the browser will download only the script of that version. Otherwise, users will get version 1 by default.

In this way, users don't have to download all the versions unnecessarily. They can download exactly what they need by generating paths on runtime in dynamic import.

Dynamic Import with Vite and Webpack

If you are using any module bundler like Vite or Webpack, you can use dynamic import with them without any problem.

One of the main reasons, we use these tools to bundle our code from multiple files. When we use dynamic imports with them, they will divide those modules into smaller chunks by generating separate files for those modules.

By doing so browsers will be able to download those files separately on demand whenever a user needs them.

If you use Vite as your build tool, you don't have to do anything extra. You can use dynamic import exactly in the same way that I have shown you earlier.

          const myModule = await import("./utils.js");
        

When you import a module dynamically using Vite, it will create a separate file with the same name by default. In this case, Vite will create a file called utils.js when you build your project.

But in the case of Webpack, it will generate a separate file with a random file name. So, when you build your project with Webpack, it might create a 0.js file for your dynamic import module.

You can modify this behavior and name your file whatever you want using Webpack magic comments.

          const myModule = await import("./utils.js" /* webpackChunkName: "utils" */);
        

You just have to add a comment with a property called webpackChunkName inside import() function after the path. You can give any name you want. When Webpack builds your project, it will generate this dynamic module with the name that you gave.

Browser Support for Dynamic Import in JavaScript

If you think about the browse support for dynamic import() in JavaScript, you can see that almost all modern browsers support this feature. So, you don't have to worry about it.

Your application or website will work perfectly in most browsers. Globally more than 92% of users are using this feature in their browsers.

browser support of dynamic import in javascript

Conclusion

Dynamic import gives us many advantages in how we want to split our code along with how we want our users to download the code. In this way, we can send only the required codes to the end-users.

If you know when and how to use dynamic import in JavaScript, your application will be faster and more efficient. We all know everyone loves fast websites.

Website speed is also a ranking factor in Google searches. So it is the best time to use dynamic import on your website. Through this article, I have shown you all the possible ways you can use it.

I also have mentioned the advantages and in which situation you should dynamic imports. I hope you got a clear idea of this topic.

Thank you so much for reading. Happy coding :)

Related Posts