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:
Använd ett bibliotek för att injicera beroenden (AMD- eller CommonJS-moduler)
Använd bara moduler. ES2015:import/export, eller CommonJS:require().- Skapa
script-taggen programmässigt och ställ in callbackonloadför att reagera när skriptet har laddats asynkront. Attributetasync=trueär inställt som standard. Om du har tillåtelse att ändra skripten för att injicera, lägg då till en rad i slutet av skripten med ettobjectellerarraysom håller reda på de skript som redan har laddats.- Du kan hämta skripten som text (
XMLHttpRequest), sedan bygga enstringmed skripten i den önskade ordningen och slutligen köra text-skriptet viaeval() - Och det mindre rekommenderade alternativet men ofta använda, är att sätta ett
setIntervalfö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 callbackonloadfö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);
});
}
