Skip to content

Instantly share code, notes, and snippets.

@MrJackdaw
Last active January 22, 2025 02:08
Show Gist options
  • Save MrJackdaw/d111b5bcdfc8b71afba8ba6c55b3168f to your computer and use it in GitHub Desktop.
Save MrJackdaw/d111b5bcdfc8b71afba8ba6c55b3168f to your computer and use it in GitHub Desktop.
Reusable class for loading external script files (e.g. from CDN)
// Example usage of Inject-Script.js
// Example 1: Load a script from an external URL. (preferred method)
// You can call this in a React/Vue/whatever component lifecycle, or wherever.
// makes sense. Window globals (e.g. `window.YT`) will be available after calling this.
await injectScriptBySrc("https://www.youtube.com/iframe_api", "yt");
// now you can use `window.YT` methods and properties in your code.
// Example 2: Load the raw text content of a script into a page
// You can also call this wherever it makes sense, though it is probably a
// terrible idea to trigger code this way, and you *should* likely be ashamed.
const myShamefulJS = `
const something = 1;
console.log( something );
`
injectScriptContents(myShamefulJS);
// logs "1" to console after the above line executes.
/**
* @file Inject-Script
* @description DYNAMICALLY INJECT EXTERNAL SCRIPT DEPENDENCIES INTO YOUR WEB APP
*
* NOTE: Track page-session scripts (i.e. scripts that should be reloaded with the page).
* You can track as few or as many script dependencies as needed: YouTube is provided as an
* example below.
*/
// (example for tracking YT script dependency)
let YouTubeLoaded = false;
// (example if loading Google Charts script into your app)
// let GoogleCharts = false;
/**
* Inject a `<script />` tag with the supplied contents into the page's
* html. This will execute `contents` immediately. You should ideally prefer
* `injectScriptBySrc( ... )` over this method. If it MUST be used, modify
* this file to track any required scripts that have to be loaded in this way.
* @param {string} contents Script tag contents
*/
export function injectScriptContents(contents) {
const scrpt = document.createElement('script');
scrpt.innerHTML = contents;
const lastElem = document.head.lastChild;
document.head.insertBefore(scrpt, lastElem);
// Update module state to track that this script has been loaded. e.g.:
//
// YouTubeLoaded = (key === "yt");
// GoogleChartsLoaded = (key === "gc"); // etc
}
/**
* Inject a `<script />` tag by `src` attribute into the page's
* html. This will execute `contents` immediately.
* @param {string} src External URL for Script source
* @param {string|undefined} key An internal identifier that helps track whether
* this script has been loaded in your app (so you don't load it multiple times)
*/
export async function injectScriptBySrc(src, key) {
// IMPORTANT: Check if script has been loaded; exit if true. For example:
// if (key === "yt" && YouTubeLoaded) return;
// Load script from source and inject into page
const scrpt = document.createElement('script');
scrpt.src = src;
scrpt.id = `${key}-script`;
const lastElem = document.head.lastChild;
document.head.insertBefore(scrpt, lastElem);
// Update module state to track that this script has been loaded. e.g.:
//
// YouTubeLoaded = (key === "yt");
// GoogleChartsLoaded = (key === "gc"); // etc
}
/**
* Identify and remove <script /> tag by its id from the page
* @param. {string} key Internal identifier for script, so that the module can
* track its loaded/unloaded status
*/
export function unmountScript(key: string) {
const id = `${key}-script`;
const script = document.getElementById(id);
if (script) script.remove();
// Update module state to track that this script has been unloaded. e.g.:
//
// switch (key) {
// case "gc": return void (GoogleChartsLoaded = false);
// case "yt": return void (YouTubeLoaded = false);
// default: break;
// }
}
@mrdark69
Copy link

Nice solution!!
But I have one issues when integrate with react-hot-loader.
Could you advise?

@MrJackdaw
Copy link
Author

Hey @mrdark69! I never saw your comment, so apologies for the extremely belated response.

I haven't used react-hot-loader before, so I'm not sure I would be able to help. (Please note I didn't create this script: just made a few modifications from the original, linked at the top.) I noticed however that the original gist had a bug, where nothing was happening in ScriptCache.onLoad because ...nothing was being called. I updated the code, though I'm not sure if it helps. If not, describe your issue here and I'd be happy to help take a look when I can.

@MrJackdaw
Copy link
Author

MrJackdaw commented Jan 22, 2025

Public Service Announcement

The gist and example above have been completely changed to reflect my latest approach to this problem.
While I still haven't used react-hot-loader, all comments prior to 2025 are aimed at a previous gist. See revisions for previous versions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment