injectScript
Dynamically injects a script into the document.
1/**
2 * Dynamically injects a script into the document.
3 *
4 * @param src - The URL of the script to load.
5 * @param async - Whether to load the script asynchronously. Defaults to true.
6 * @param type - MIME type of the script. Defaults to 'text/javascript'.
7 * @returns A Promise that resolves when the script is loaded, or rejects on error.
8 */
9export function injectScript(
10 src: string,
11 async: boolean = true,
12 type: string = 'text/javascript'
13): Promise<void> {
14 return new Promise((resolve, reject) => {
15 const existingScript = document.querySelector(`script[src="${src}"]`);
16 if (existingScript) {
17 resolve(); // script already exists
18 return;
19 }
20
21 const script = document.createElement('script');
22 script.src = src;
23 script.async = async;
24 script.type = type;
25
26 script.onload = () => resolve();
27 script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
28
29 document.head.appendChild(script);
30 });
31}
Promise-Based Loading
Returns a
Promise
, allowing clean async/await handling or chaining for dependent logic.Duplicate Prevention
Prevents loading the same script multiple times by checking for an existing
<script>
tag with the samesrc
.Customizable Loading Options
Supports
async
flag and MIMEtype
, making it adaptable to different script types and loading behaviors.Graceful Error Handling
Rejects the promise with a detailed error message if the script fails to load.
Non-Blocking by Default
Loads scripts asynchronously unless explicitly told not to, reducing render-blocking behavior.
Tests | Examples
1beforeEach(() => {
2 document.head.innerHTML = '';
3});
4
5test('injects a new script element and resolves on load', async () => {
6 const mockScript = document.createElement('script');
7 jest.spyOn(document, 'createElement').mockReturnValue(mockScript);
8
9 const promise = injectScript('https://example.com/script.js');
10
11 // Simulate load
12 setTimeout(() => {
13 mockScript.dispatchEvent(new Event('load'));
14 }, 0);
15
16 await expect(promise).resolves.toBeUndefined();
17 expect(mockScript.src).toContain('https://example.com/script.js');
18 expect(document.head.contains(mockScript)).toBe(true);
19});
20
21test('rejects on script error', async () => {
22 const mockScript = document.createElement('script');
23 jest.spyOn(document, 'createElement').mockReturnValue(mockScript);
24
25 const promise = injectScript('https://example.com/fail.js');
26
27 // Simulate error
28 setTimeout(() => {
29 mockScript.dispatchEvent(new Event('error'));
30 }, 0);
31
32 await expect(promise).rejects.toThrow('Failed to load script: https://example.com/fail.js');
33});
34
35test('resolves immediately if script already exists', async () => {
36 const existing = document.createElement('script');
37 existing.src = 'https://example.com/already-loaded.js';
38 document.head.appendChild(existing);
39
40 await expect(injectScript('https://example.com/already-loaded.js')).resolves.toBeUndefined();
41});
Common Use Cases
Third-Party Script Loading
Dynamically load external libraries like Google Maps, Stripe, or analytics tools only when needed.
Feature-Based Loading
Load optional scripts conditionally based on user actions or device capabilities.
Performance Optimization
Delay loading of non-critical scripts until after the page is interactive.
Client-Side Plugin Injection
Inject third-party widgets or tools (e.g. chatbots, tracking snippets) at runtime.
Lazy Loading of Modules
Defer loading scripts in single-page apps or modular UI components to reduce initial bundle size.