javascript - Laddningsordning för dynamiskt tillagda skript-taggar


Jag har en TypeScript-applikation som dynamiskt lägger till script-taggar som pekar på JS-filer. På grund av vissa begränsningar kan jag inte ha dessa script-taggar statiskt definierade i en HTML-fil, så jag lägger till dem dynamiskt genom TypeScript på detta sätt:

for (let src of jsFiles) {
  let sTag = document.createElement('script');
  sTag.type = 'text/javascript';
  sTag.src = src;
  sTag.defer = true;
  document.body.appendChild(script);
}

Nu märker jag att när jag lägger till skript-tagg dynamiskt verkar det inte finnas någon garanti för i vilken ordning de laddas. Tyvärr har jsFiles-arrayen skript som är beroende av varandra. Så det andra skriptet i arrayen kan bara laddas efter att det första är helt laddat. Det andra skriptet refererar till en funktion som definieras i det första. Finns det ett sätt att ange ordningen i vilken skript ska ordnas och köras när de läggs till dynamiskt (på samma sätt som ordningen bestäms när du statiskt definierar skript-tagg i en HTML-fil)?

P.S. Jag skulle vilja undvika att använda onload-callbacken för att lösa detta problem eftersom jag märkte en prestandanedgång med min applikation. Min andra skriptfil är mycket stor och jag antar att det orsakade nedgången.

Svar #1

Jag kan nämna några alternativ för att övervinna det kravet:

  1. Använd ett bibliotek för att injicera beroenden (AMD- eller CommonJS-moduler)
    Använd bara moduler. ES2015: import/export, eller CommonJS: require().
  2. Skapa script-taggen programmässigt och ställ in callback onload för att reagera när skriptet har laddats asynkront. Attributet async=true är inställt som standard.
  3. Om du har tillåtelse att ändra skripten för att injicera, lägg då till en rad i slutet av skripten med ett object eller array som håller reda på de skript som redan har laddats.
  4. Du kan hämta skripten som text (XMLHttpRequest), sedan bygga en string med skripten i den önskade ordningen och slutligen köra text-skriptet via eval()
  5. Och det mindre rekommenderade alternativet men ofta använda, är att sätta ett setInterval för att kontrollera om skriptet redan har körts.

Jag rekommenderar att gå på det första alternativet. Men för akademiska syften kommer jag att illustrera det andra alternativet:

Skapa script-taggen programmässigt och ställ in callback onload för att reagera när skriptet har laddats asynkront.

Jag vill rekommendera en läsning om skriptladdare: Djupt dyk i skriptladdningens grumliga vatten, en halvtimme värd att spendera!

Följande exempel är en liten modul för att hantera skriptinjektion, och detta är den grundläggande idén bakom den:

let _scriptsToLoad = [
  'path/to/script1.js',
  'path/to/script2.js',
  'path/to/script3.js'
];

function createScriptElement() {
  // gets the first script in the list
  let script = _scriptsToLoad.shift();
  // all scripts were loaded
  if (!script) return;
  let js = document.createElement('script');
  js.type = 'text/javascript';
  js.src = script;
  js.onload = onScriptLoaded;
  let s = document.getElementsByTagName('script')[0];
  s.parentNode.insertBefore(js, s);
}

function onScriptLoaded(event) {
  // loads the next script
  createScriptElement();
};

I denna plunker kan du testa injektion av skript asynkront i en specifik ordning:

  • https://plnkr.co/edit/b9O19f

Huvudidén var att skapa ett API som låter dig interagera med skripten för att injicera, genom att exponera följande metoder:

  • addScript: ta emot en URL eller en lista med URL:er för varje skript som ska laddas.
  • load: Kör uppgiften för att ladda skript i den angivna ordningen.
  • reset: Rensa arrayen med skript, eller avbryt laddningen av skript.
  • afterLoad: Callback som körs efter att varje skript har laddats.
  • onComplete: Callback som körs efter att alla skript har laddats.

Jag gillar Fluent Interface eller method chaining-tekniken, så jag byggde modulen på det sättet:

  scriptsLoader
    .reset()
    .addScript("script1.js")
    .addScript(["script2.js", "script3.js"])
    .afterLoad((src) => console.warn("> loaded from jsuLoader:", src))
    .onComplete(() => console.info("* ALL SCRIPTS LOADED *"))
    .load();

I koden ovan laddar vi först filen "script1.js" och kör callback-funktionen afterLoad(), nästa gör vi samma sak med "script2.js" och "script3.js" och efter att alla skript har laddats körs callback-funktionen onComplete().

Svar #2

Jag skulle vilja undvika att använda onload-callbacken för att lösa detta problem eftersom jag märkte en prestandanedgång i min applikation.

Om du vill ha filerna i ordning ... behöver du vänta på onload för varje fil. Ingen väg runt det.

Här är en hjälpfunktion som jag skrev en gång:

/**
 * Utility : Resolves when a script has been loaded
 */
function include(url: string) {
  return new Promise<{ script: HTMLScriptElement }>((resolve, reject) => {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;

    script.onload = function() {
      resolve({ script });
    };

    document.getElementsByTagName('head')[0].appendChild(script);
  });
}

Relaterade artiklar