Explain Codes LogoExplain Codes Logo

Dynamically add script tag with src that may include document.write

javascript
async-await
script-loading
error-handling
Nikita BarsukovbyNikita Barsukov·Feb 6, 2025
TLDR

Loading a script that contains document.write? Here is how you can dynamically add it - with the help of a trusty script tag:

var script = document.createElement('script'); script.src = 'YOUR_SCRIPT_URL'; // <-- Insert witty URL joke here document.body.appendChild(script);

Dealing with document.write safely calls for an iframe sandbox:

var iframe = document.createElement('iframe'); document.body.appendChild(iframe); iframe.contentWindow.document.write('<script src="YOUR_SCRIPT_URL"><\/script>'); iframe.contentWindow.document.close();

For real-world scenarios where dynamic script loading scenarios may change, consider incorporating asynchronous loading approaches and Promise-based functions for greater code flex and error handling.

Deciding where to insert your scripts: head or body?

When dynamically adding your scripts, consider whether they should reside within the <head> or <body>. The <head> allows scripts to parse and execute ahead of the full document being loaded (think libraries that must initialize early). The <body> typically houses scripts that interact with fully-loaded elements.

Orchestrating your scripts harmoniously

When adding multiple scripts, handling script execution order can be challenging. A scriptLoader function to the rescue:

function loadScript(src, onLoadCallback) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = src; script.onload = () => { onLoadCallback?.(); // Callback? More like "call-often" resolve(); }; script.onerror = reject; document.head.appendChild(script); }); }

By integrating the async/await pattern within a try/catch block, you can maintain execution order and handle exceptions seamlessly.

Handling errors during dynamic script loading

Appropriate error handling is crucial to ensure a single failed script doesn't halt all operations on your page.

script.addEventListener('load', () => { // High-five! Script loaded successfully }); script.addEventListener('error', () => { // Whoops! Better take care of this load error });

Consider also integrating this error handling into your scriptLoader function to fully armor your script handling.

Async away!

To ensure scripts do not block rendering of page content, consider using the async attribute:

script.async = true;

This ensures that script execution runs parallel to page parsing, which can improve performance significantly. Keep in mind though, it's all fun and games until script order starts to matter. In that case, handle async with care.

The "write" way and the "wrong" way

document.write has a reputation of causing performance issues and unexpected behaviors when used in dynamically loaded scripts. It's like that one friend who's great fun but always finds trouble. Google Chrome even has document.write interventions to improve user experience.

The Swiss Army Knife of Dynamic Script Loading

We've discovered nuance and complexity in dynamic script loading. Let's put it all together for robust handling of the process:

async function loadScriptsInOrder(scripts) { for (const scriptSrc of scripts) { try { await loadScript(scriptSrc, () => console.log(`${scriptSrc} loaded`)); } catch (e) { console.error(`Error loading ${scriptSrc}: ${e.message}`); } } }

This allows for sequential script loading, error handling, and ensures a failed script load won't break your flow. The Swiss Army Knife of the JavaScript world, if you will.