Sometimes you need to dynamically add a script tag to HTML. But there are some gotchas that might get you if you don't know to look out for them.
You might ask, why add scripts dynamically at all? Well, here's two reasons:
- When you don't know what to load until runtime.
- When you want to load scripts without blocking the page load.
I recently needed to do both when loading external resources. The responses from earlier resources determined which resources were required, so I couldn't know what resources to get until runtime. Furthermore, I didn't want the resources to block the page from loading. Avoiding page load blocking was especially important as these were external resources, so they might fail or be particularly slow.
Gotcha #1: No script tags in innerHTML
It turns out that HTML5 does not allow script tags to be dynamically added using the innerHTML property. So the following will not execute and there will be no alert saying Hello World!
element.innerHTML = "<script>alert('Hello World!')</script>";
This is documented in the HTML5 spec:
Note: script elements inserted using innerHTML do not execute when they are inserted.
But beware, this doesn't mean innerHTML is safe from cross-site scripting. It is possible to execute JavaScript via innerHTML without using <script>
tags as illustrated on MDN's innerHTML page.
Solution: Dynamically adding scripts
To dynamically add a script tag, you need to create a new script element and append it to the target element.
You can do this for external scripts:
var newScript = document.createElement("script");
newScript.src = "http://www.example.com/my-script.js";
target.appendChild(newScript);
And inline scripts:
var newScript = document.createElement("script");
var inlineScript = document.createTextNode("alert('Hello World!');");
newScript.appendChild(inlineScript);
target.appendChild(newScript);
Gotcha #2: No document.write
It turns out that when scripts are loaded asynchronously they cannot call document.write
. The calls will simply be ignored and a warning will be written to the console.
Solutions: change, iframe, or redefine
The first and best solution is to change the script so that it doesn't call document.write
. Instead it can use DOM manipulation methods such as insertAfter
.
If it is not possible to change the script, then one option is to load the script into an <iframe>
. This was the solution in my scenario as the external resources were beyond my control.
A third option is to redefine document.write
to use DOM manipulation methods as HandyAndyShortStack did in this Stack Overflow answer. Note: I haven't tested this option.