Legacy-Javascript dynamisch nachladen

13.04.2021, 23:52

Leider ist es üblich, für Webanwendungen alle notwendigen Javascripte zu einer großen Javascript-Datei zusammen zu fassen. Das verringert im Idealfall etwas die Dateigröße und spart HTTP-Connections ein (letzteres ist mit HTTP/2 kein Argument mehr), dafür dauert das initiale Laden der Anwendung oder Webseite länger.

Mein Ansatz ist, Javascripte nachzuladen.

ES6 bringt dazu die import()-Anweisung mit. Das funktioniert aber nur für ES6-Module.

Legacy-Javascripte bindet man am besten weiterhin mit einem dynamischen Skript-Element ein. Ich habe hierfür eine Funktion geschrieben, die das ganze noch in eine Promise verpackt, die erfüllt ist, wenn das Skript geladen ist. Außerdem sorgt sie dafür, dass das Skript nur einmalig geladen wird.

/**
 * Adds a new Script to the DOM.
 *
 * @param id ID of element (must be unique in the DOM)
 * @param href Link to the stylesheet
 */
async addScript( id, href ) {

    return new Promise( (resolve,reject) => {
        let scriptEl = document.getElementById( id );

        if   ( ! scriptEl ) {
            // Script is not present, so inserting it into the DOM
            scriptEl = document.createElement( 'script' );
            scriptEl.setAttribute('id'  ,id                       );
            scriptEl.setAttribute('type','text/javascript'  );
            scriptEl.addEventListener('load',resolve);
            scriptEl.setAttribute('src',href );
            document.getElementsByTagName('head')[0].appendChild(scriptEl);
        } else {
            resolve(); // script is already there
        }
    } );
}
Das funktioniert mit Stylesheets genauso:
/**
* Adds a new Stylesheet to the DOM.
*
* @param id ID of element (must be unique in the DOM)
* @param href Link to the stylesheet
*/
async addStyle( id, href ) {

return new Promise( (resolve,reject) => {
    let styleEl = document.getElementById(id);

    if (!styleEl) {
        // Style is not present, so inserting it into the DOM
        styleEl = document.createElement('link');
        styleEl.addEventListener('load',resolve);
        styleEl.setAttribute('rel', 'stylesheet');
        styleEl.setAttribute('type', 'text/css');
        styleEl.setAttribute('href', href);
        styleEl.setAttribute('id', id);

        document.getElementsByTagName('head')[0].appendChild(styleEl);
    } else {
        resolve();
    }
} );

}

Durch die Promise braucht man beim Aufruf keine Callback-Funktion mehr, sondern schreibt einfach

await addStyle ('my-style-id'   ,'./style.css');
await addScript('jquery-slim-id','./jquery.min.js' );

Die übergebenen Ids müssen nur eindeutig sein.

Das funktioniert natürlich nur in einer Funktion, die als async markiert ist.