Bryan Hamilton / projects / sri-lazy-loader

bryanhamilton.info / open source / sri-lazy-loader

sri-lazy-loader Load it once. Load it safely.

Lazy-loading a library from a CDN takes about five lines of code, and then it breaks in production in four different ways. This tiny loader prevents all four. On The DJ Calendar it loads the Leaflet map library only on pages that show a map, and only once, no matter how many parts of the page ask for it.

  • Subresource Integrity
  • Race-safe, deduped
  • Zero dependencies
  • MIT license
In brief: sri-lazy-loader is a zero-dependency JavaScript utility for loading scripts once, safely. It deduplicates concurrent requests by URL, recognizes existing script tags, supports Subresource Integrity, and rejects timed-out loads with cleanup so retries can work.

Break it, then fix it

Four production failures, run for real in your browser

1The double-load race

Three widgets need the same library at the same moment. Naive code injects three script tags, and the library starts up three times.

  • Press a button to run it.

2The pre-existing tag

Another part of the site already loaded the library. Injecting it again is the same corruption, with extra steps.

  • Press a button to run it.

3The tampered CDN file

The file you asked for has been altered since you hashed it. The browser itself refuses to run it.

  • Press a button to run it.

4The request that fails

A request dies. A sloppy loader leaves a dead script tag behind and a promise that never lets you retry.

  • Press a button to run it.

Everything above really happens in your browser: real script tags, real SHA-384 hashes and the browser's own integrity check. The "library" is a tiny file generated on the spot, so nothing is downloaded.

Five lines of code, four ways to break

1. The double-load race
Two independent widgets, a city map and the "next show" radar, both need Leaflet and both add a script tag. The library starts up twice, and the second run wipes out the first: markers end up registered on a map that no longer exists.
2. The pre-existing tag
Some other code on the page loaded the library first. Adding it again causes the same corruption. The loader notices the existing tag and simply reports success.
3. The tampered CDN file
If a CDN is compromised or traffic is intercepted, the file you get isn't the file you tested. With an integrity hash, the browser blocks it before a single line runs. A supply-chain attack on a map library shouldn't become an incident on your site.
4. The hung or failed request
A stalled CDN request mustn't leave a zombie script tag and a promise that never settles. On failure or timeout, the tag is removed and the request is forgotten, so a later retry genuinely tries again.

The fix: one promise per file

The whole design rests on one idea: a small registry, keyed by URL, holds the promise for each file. Whoever asks first starts the download; everyone who asks while it's in flight gets the same promise back. One request, one script tag, one start-up, however many callers there are.

const inFlight = {};                              // url -> Promise: the source of truth

function loadScript(url, opts = {}) {
  if (inFlight[url]) return inFlight[url];            // 1. concurrent callers share one request
  const existing = document.querySelector(`script[src="${url}"]`);
  if (existing) return (inFlight[url] = Promise.resolve(existing));   // 2. already on the page

  return (inFlight[url] = new Promise((resolve, reject) => {
    const s = document.createElement('script');
    s.src = url;
    if (opts.integrity) { s.integrity = opts.integrity; s.crossOrigin = 'anonymous'; }   // 3. the browser verifies it
    const fail = err => { s.remove(); delete inFlight[url]; reject(err); };  // 4. clean up so retries work
    const timer = setTimeout(() => fail(new Error('timed out')), opts.timeoutMs || 12000);
    s.onload  = () => { clearTimeout(timer); resolve(s); };
    s.onerror = () => { clearTimeout(timer); fail(new Error('failed to load')); };
    document.head.appendChild(s);
  }));
}

A sketch of the pattern, matching what the demo above runs. The production module is sri-lazy-loader.js on GitHub.

Usage

loadScript('https://unpkg.com/leaflet@1.9.4/dist/leaflet.js', {
  integrity: 'sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=',
  crossOrigin: ''
}).then(function () {
  var map = L.map('map-canvas').setView([40.9, -74.1], 11);
});
OptionDefaultWhat it does
integrity—The SRI hash (sha384-… or sha256-…) for third-party scripts
crossOrigin'anonymous'Cross-origin mode for scripts with an integrity hash
timeoutMs12000How long to wait before giving up and cleaning up

It returns a promise that resolves with the script element, or rejects with an error on failure or timeout. It never leaves a dead tag or a dangling listener behind.

From the repo's README.

Getting a file's integrity hash

Most CDNs, cdnjs among them, list the SRI hash right next to each file. For any other file, one command produces it:

curl -s https://unpkg.com/leaflet@1.9.4/dist/leaflet.js | openssl dgst -sha384 -binary | openssl base64 -A

Put sha384- in front of the result and that's your integrity value. Pin an exact version in the URL too: a hash only matches one exact file, which is the whole point.

From production

On The DJ Calendar, every city page and the next-show radar map ask for Leaflet the first time a visitor interacts with them. Before this module shipped, the site occasionally ended up with two copies of Leaflet: one map would start up, then a second script tag would land and start the library again, corrupting the first map.

The bug was intermittent and impossible to reproduce on a development machine, because over a local connection everything loads too fast to race. The fix was this one small module, treating the promise registry as the single source of truth.

Design decisions

DecisionWhy
A promise registry keyed by URLDeduping concurrent callers is one line with a cache. Without it, every race is a bug that only appears sometimes.
Detect tags already on the pageThe page is shared state. Pretending otherwise is how double-initialization bugs ship.
SRI on everything third-partyThe loader is the last line of defense for supply-chain integrity, and it costs one hash.
Clean up on failureA dead script tag with a dangling error handler is a leak. A timeout must unblock retries, not just reject.
No bundler, no frameworkOne file that a plain script tag or any module system can use.

Frequently asked questions

What does sri-lazy-loader protect against?

It prevents duplicate script loads from concurrent callers, detects an existing script tag, supports Subresource Integrity for third-party scripts, and cleans up failed or timed-out requests.

How does it avoid duplicate script tags?

Callers loading the same URL share the in-flight Promise from a URL-keyed registry, so one script element serves concurrent requests.

What happens when a script load times out?

The Promise rejects with an error, the abandoned script element and listeners are cleaned up, and the in-flight entry is cleared so a later call can retry.

Does sri-lazy-loader need a framework or package install?

No. It has zero dependencies and can be used as a plain JavaScript file. The optional integrity hash lets the browser verify third-party script bytes before execution.

The bugs you never see

Bryan Hamilton is a full-stack web engineer with 25+ years in PHP and JavaScript, available immediately for remote, hybrid or on-site roles in the NYC metro area.