How to detect when a dynamic module, and all its dependencies, have been loaded?

2.1k views Asked by At

Webpack supports the import() syntax that conforms to the ECMAScript proposal for dynamic imports. This syntax uses promises to asynchronously load modules.

The problem is that the promise is resolved as soon as the specific module is loaded, without waiting for the module's dependencies to load (which can be any type of asset, including JS & CSS).

Example code:

import('./myModule.js').then(myModule => {
    myModule.sayHello(); // This will be called before someCSS.css has been loaded
});

myModule.js

import './someCSS.css'; // <-- I need to know when this is loaded (there can be more than one asset)

export default class myModule {
    sayHello() {
        alert('Hello!');
    }
}

How can I detect when the module, and all related assets, have been loaded? Something like an onload event for async assets?

2

There are 2 answers

3
Aliaksandr Pitkevich On

The method returns Promise, which allows you to determine whether a script was loaded or an error occurred while loading (for example):

// utils.js
function insertJs({src, isModule, async, defer}) {
    const script = document.createElement('script');

    if(isModule){
      script.type = 'module';
    } else{
      script.type = 'application/javascript';
    }
    if(async){
      script.setAttribute('async', '');
    }
    if(defer){
      script.setAttribute('defer', '');
    }

    document.head.appendChild(script);

    return new Promise((success, error) => {
        script.onload = success;
        script.onerror = error;
        script.src = src;// start loading the script
    });
}

export {insertJs};

//An example of its use:

import {insertJs} from './utils.js'

// The inserted node will be:
// <script type="module" src="js/module-to-be-inserted.js"></script>
const src = './module-to-be-inserted.js';

insertJs({
  src,
  isModule: true,
  async: true
})
    .then(
        () => {
            alert(`Script "${src}" is successfully executed`);
        },
        (err) => {
            alert(`An error occured during the script "${src}" loading: ${err}`);
        }
    );
// module-to-be-inserted.js
alert('I\'m executed');
0
Yoav Kadosh On

It's possible to use document.styleSheets to check when all stylesheets have been loaded. A CSSStyleSheet object will contain a cssRules property only once the stylesheet has been loaded, so you can create a promise that checks against that:

export function awaitStylesheets() {
    let interval;
    return new Promise(resolve => {
        interval = setInterval(() => {
            for (let i = 0; i < document.styleSheets.length; i++) {
                // A stylesheet is loaded when its object has a 'cssRules' property
                if (typeof document.styleSheets[i].cssRules === 'undefined') {
                    return;
                }
            }

            // Only reached when all stylesheets have been loaded
            clearInterval(interval);
            resolve();
        }, 10);
    });
}