Yevhen Klymentiev
dark
light
console
darkness
y.klymentiev@gmail.com
Reusable Snippets|Practical utility code for everyday use — custom-built and ready to share

injectScript

Dynamically injects a script into the document.

TypeScript
Copied!
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 same src.

  • Customizable Loading Options

    Supports async flag and MIME type, 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

TypeScript
Copied!
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.

Codebase: Utilities -> Browser & DOM -> injectScript | Yevhen Klymentiev