TheDemystifier

Anki script - insert Script

Oct 4th, 2023 (edited)
213
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * Ensures a `<script type="module">` tag pointing at `path` exists in `<head>`, loading it as an
  3.  * ES module and resolving once it (or a fallback) has executed.
  4.  *
  5.  * Design notes:
  6.  * - Module scripts are cached by the browser's module map, keyed by the *resolved* URL. Once a
  7.  *   module has been fetched/instantiated, re-adding a tag with the same `src` (or clearing and
  8.  *   re-setting `src`, as classic scripts allow) does **not** re-run its top-level code — the
  9.  *   engine just reuses the cached module record. So, unlike a classic-script loader, we treat an
  10.  *   existing matching `<script>` tag as "already loaded" and resolve immediately, rather than
  11.  *   trying to force a reload.
  12.  * - The first load adds a `Date.now()` cache-busting query parameter to the primary URL so it is
  13.  *   fetched as a fresh module. Subsequent calls ignore that cache-busting parameter when checking
  14.  *   for an existing script, so the same script tag is reused rather than inserted again.
  15.  * - `script.src` is always reported by the DOM as an absolute URL, so `path` is resolved against
  16.  *   `document.baseURI` before comparing, otherwise a relative `path` would never match an
  17.  *   existing absolute `src` and we'd insert duplicate script tags.
  18.  * - If the primary `path` fails to load (e.g. a local/offline asset is missing), we transparently
  19.  *   retry once from `altURL` (e.g. a CDN mirror) before rejecting.
  20.  *
  21.  * @param {string} path - Primary module script URL to load (may be relative to the current document).
  22.  * @param {string} altURL - Fallback module script URL to try if `path` fails to load.
  23.  * @returns {Promise<void>} Resolves once a module script (primary or fallback) has loaded;
  24.  *   rejects only if both the primary and fallback loads fail.
  25.  */
  26. function insertScript(path, altURL) {
  27.     return new Promise((resolve, reject) => {
  28.         // Normalize to an absolute URL so it can be compared against existing <script src> values.
  29.         const resolvedPath = new URL(path, document.baseURI).href;
  30.  
  31.         // Reuse an already-inserted module tag instead of loading the same module twice.
  32.         const existingScript = Array.from(document.head.getElementsByTagName('script')).find(
  33.             (script) => {
  34.                 const existingURL = new URL(script.src, document.baseURI);
  35.                 existingURL.searchParams.delete('_cb');
  36.                 return existingURL.href === resolvedPath;
  37.             },
  38.         );
  39.  
  40.         if (existingScript) {
  41.             // See design notes above: re-triggering `src` would not re-execute an already-cached module.
  42.             resolve();
  43.             return;
  44.         }
  45.  
  46.         // Cache-bust the first load so the module is fetched as a fresh URL.
  47.         const cacheBustedPath = new URL(resolvedPath);
  48.         cacheBustedPath.searchParams.set('_cb', Date.now());
  49.  
  50.         loadModuleScript(cacheBustedPath.href, resolve, () => {
  51.             // Primary source failed — fall back to the alternate URL before giving up entirely.
  52.             loadModuleScript(altURL, resolve, reject);
  53.         });
  54.     });
  55.  
  56.     /**
  57.      * Creates, configures, and appends a single `<script type="module">` element.
  58.      *
  59.      * @param {string} src - The module URL to load.
  60.      * @param {() => void} onSuccess - Invoked once the module has loaded.
  61.      * @param {(error: Error) => void} onFailure - Invoked if the module fails to load.
  62.      * @returns {void}
  63.      */
  64.     function loadModuleScript(src, onSuccess, onFailure) {
  65.         const script = document.createElement('script');
  66.         // `type="module"` gives us native deferred, once-only execution and top-level `import`/`export`.
  67.         script.type = 'module';
  68.         script.src = src;
  69.         script.onload = () => onSuccess();
  70.         script.onerror = () => onFailure(new Error(`Failed to load module script: ${src}`));
  71.         document.head.appendChild(script);
  72.     }
  73. }
Advertisement
Add Comment
Please, Sign In to add comment