Es ist keine leichte Aufgabe, für jeden Browser den richtigen Code zu übergeben.
In diesem Artikel werden verschiedene Optionen betrachtet, wie dieses Problem gelöst werden kann.

Das Übergeben von modernem Code durch einen modernen Browser kann die Leistung erheblich verbessern. Ihre JavaScript-Pakete können eine kompaktere oder optimierte moderne Syntax enthalten und ältere Browser unterstützen.
Unter den Tools für Entwickler dominiert das
Modul- / Nomodelmuster des deklarativen Ladens von modernem oder Legacy-Code, das Browsern Quellen zur Verfügung stellt und es Ihnen ermöglicht, zu entscheiden, welche verwendet werden sollen:
<script type="module" src="/modern.js"></script> <script nomodule src="/legacy.js"></script>
Leider ist nicht alles so einfach. Der oben gezeigte HTML-Ansatz löst
ein erneutes Laden von Skripten in Edge und Safari aus .
Was kann getan werden?
Je nach Browser müssen wir eine der Optionen für kompilierte Skripte bereitstellen, aber einige alte Browser unterstützen nicht die gesamte dafür erforderliche Syntax.
Erstens gibt es
Safari Fix . Safari 10.1 unterstützt JS-Module, nicht das
nomodule
Attribut in Skripten, wodurch sowohl moderner als auch
nomodule
Code ausgeführt werden kann. Das von Safari 10 & 11 unterstützte nicht standardmäßige
beforeload
kann jedoch zum Polyfill-
nomodule
.
Methode Eins: Dynamischer Download
Sie können diese Probleme umgehen, indem Sie einen kleinen Skriptlader implementieren. Ähnlich wie
LoadCSS funktioniert. Anstatt auf die Implementierung von ES-Modulen und des
nomodule
Attributs in
nomodule
, können Sie versuchen, ein Modul-Skript als „Test mit einem Lackmustest“ auszuführen und basierend auf dem Ergebnis einen modernen oder älteren Code herunterzuladen.
<!-- use a module script to detect modern browsers: --> <script type="module"> self.modern = true </script> <!-- now use that flag to load modern VS legacy code: --> <script> addEventListener('load', function() { var s = document.createElement('script') if ('noModule' in s) { </script>
Bei diesem Ansatz müssen Sie jedoch warten, bis das Skript "Lackmus" -Modul abgeschlossen ist, bevor Sie das richtige Skript implementieren. Dies liegt daran, dass
<sript type="module">
immer asynchron arbeitet. Aber es gibt einen besseren Weg!
Sie können die unabhängige Option implementieren, indem Sie überprüfen, ob das
nomodule
im Browser unterstützt wird. Dies bedeutet, dass wir Browser wie Safari 10.1 als veraltet betrachten, auch wenn sie Module unterstützen. Aber es
könnte das
Beste sein . Hier ist der relevante Code:
var s = document.createElement('script') if ('noModule' in s) {
Dies kann schnell in eine Funktion umgewandelt werden, die modernen oder Legacy-Code lädt und auch asynchrones Laden von Code ermöglicht:
<script> $loadjs("/modern.js","/legacy.js") function $loadjs(src,fallback,s) { s = document.createElement('script') if ('noModule' in s) s.type = 'module', s.src = src else s.async = true, s.src = fallback document.head.appendChild(s) } </script>
Was ist hier der Kompromiss?
VorladenDa die Lösung vollständig dynamisch ist, kann der Browser unsere JavaScript-Ressourcen erst erkennen, wenn er den Bootstrapping-Code startet, den wir zum Einfügen moderner oder älterer Skripte geschrieben haben. In der Regel durchsucht ein Browser HTML nach Ressourcen, die er im Voraus herunterladen kann. Dieses Problem ist gelöst, aber nicht ideal: Sie können die moderne Version des Pakets in modernen Browsern mit
<link rl=modulpreload>
.
Leider unterstützt bisher
nur Chrome das modulepreload
.
<link rel="modulepreload" href="/modern.js"> <script type="module">self.modern=1</script> <!-- etc -->
Wenn diese Technik für Sie geeignet ist, können Sie die Größe des HTML-Dokuments reduzieren, in das Sie diese Skripte einbetten. Wenn Ihre Nutzdaten klein sind, z. B. ein Begrüßungsbildschirm oder ein Downloadcode für Clientanwendungen, ist es unwahrscheinlich, dass das Löschen des Preload-Scanners die Leistung beeinträchtigt. Wenn Sie auf dem Server viel wichtiges HTML zeichnen, um es an Browser zu senden, ist der Preload-Scanner für Sie hilfreich, und der beschriebene Ansatz ist nicht die beste Option für Sie.
So könnte diese Lösung im Einsatz aussehen:
<link rel="modulepreload" href="/modern.js"> <script type="module">self.modern=1</script> <script> $loadjs("/modern.js","/legacy.js") function $loadjs(e,d,c){c=document.createElement("script"),self.modern?(c.src=e,c.type="module"):c.src=d,document.head.appendChild(c)} </script>
Es sollte auch beachtet werden, dass die Liste der Browser, die JS-Module unterstützen, fast dieselbe ist wie die, die
<link rl=preload>
. Für einige Sites kann es angebracht sein,
<link rl=preload as=script crossorigin>
anstelle von
modulepreload
. Die Leistung kann sich verschlechtern, da das klassische
modulepreload
Skripten keine gleichmäßige Analyse im Laufe der Zeit impliziert, wie dies beim
modulepreload
der Fall
modulepreload
.
Methode 2: User Agent verfolgen
Ich habe kein geeignetes Codebeispiel, da das Verfolgen von User Agent keine triviale Aufgabe ist. Aber dann können Sie den ausgezeichneten
Artikel im Smashing Magazine lesen
.Tatsächlich beginnt alles mit dem gleichen
<scrit src=bundle.js>
in HTML für alle Browser. Wenn bundle.js angefordert wird, analysiert der Server die User Agent-Zeichenfolge des anfordernden Browsers und wählt aus, welches JavaScript zurückgegeben werden soll - modern oder alt, je nachdem, wie der Browser erkannt wurde.
Der Ansatz ist universell, hat jedoch schwerwiegende Konsequenzen:
- Da Smart Server erforderlich sind, funktioniert dieser Ansatz unter statischen Bereitstellungsbedingungen (statische Site-Generatoren, Netlify usw.) nicht.
- Das Caching für diese JavaScript-URLs hängt jetzt vom User Agent ab, der sehr volatil ist.
- Die Definition von UA ist schwierig und kann zu einer falschen Klassifizierung führen.
- Die User Agent-Zeichenfolge ist leicht zu fälschen und jeden Tag werden neue UAs angezeigt.
Eine Möglichkeit, diese Einschränkungen zu umgehen, besteht darin, das Modul- / Nomodelmuster mit der User Agent-Differenzierung zu kombinieren, um zu vermeiden, dass mehrere Versionen des Pakets an dieselbe Adresse gesendet werden. Dieser Ansatz reduziert die Seiten-Cachefähigkeit, bietet jedoch ein effizientes Vorladen: Der HTML-generierende Server weiß, wann das
modulepreload
und wann das
modulepreload
preload
.
function renderPage(request, response) { let html = `<html><head>...`; const agent = request.headers.userAgent; const isModern = userAgent.isModern(agent); if (isModern) { html += ` <link rel="modulepreload" href="modern.mjs"> <script type="module" src="modern.mjs"></script> `; } else { html += ` <link rel="preload" as="script" href="legacy.js"> <script src="legacy.js"></script> `; } response.end(html); }
Für Websites, die bereits als Antwort auf jede Anforderung HTML auf dem Server generieren, kann dies ein effektiver Übergang zum Herunterladen moderner Skripte sein.
Methode drei: feine alte Browser
Der negative Effekt des Modul- / Nomodule-Musters ist in älteren Versionen von Chrome, Firefox und Safari sichtbar - ihre Anzahl ist sehr gering, da die Browser automatisch aktualisiert werden. Bei Edge 16-18 ist die Situation anders, aber es gibt Hoffnung: Neue Versionen von Edge verwenden die Chromium-basierte Rendering-Engine, die keine derartigen Probleme aufweist.
Für einige Anwendungen wäre dies ein idealer Kompromiss: Laden Sie die moderne Version des Codes in 90% der Browser herunter und geben Sie den alten Code alten Code. Die Last in älteren Browsern wird zunehmen.
Übrigens nimmt keiner der User Agents, für die ein solcher Neustart ein Problem darstellt, keinen wesentlichen Anteil am Mobilfunkmarkt ein. Daher ist es unwahrscheinlich, dass die Quelle all dieser zusätzlichen Bytes mobile Geräte oder Geräte mit einem schwachen Prozessor sind.
Wenn Sie eine Site erstellen, auf die hauptsächlich mobile oder frische Browser zugreifen, ist für die meisten dieser Benutzer die einfachste Art von Modul- / Nomodelmuster geeignet. Stellen Sie einfach sicher, dass Sie das
Safari 10.1-Update hinzufügen, wenn ältere iOS-Geräte zu Ihnen kommen.
iOS-. <!-- polyfill `nomodule` in Safari 10.1: --> <script type="module"> !function(e,t,n){!("noModule"in(t=e.createElement("script")))&&"onbeforeload"in t&&(n=!1,e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove())}(document) </script> <!-- 90+% of browsers: --> <script src="modern.js" type="module'></script> <!-- IE, Edge <16, Safari <10.1, old desktop: --> <script src="legacy.js" nomodule async defer></script>
Methode 4: Paketbedingungen anwenden
Eine gute Lösung wäre die Verwendung von
nomodule
zum bedingten Herunterladen von Paketen mit Code, der in modernen Browsern wie Polyfills nicht benötigt wird. Bei diesem Ansatz wird im schlimmsten Fall die Polyfüllung geladen oder sogar ausgeführt (in Safari 10.1), die Auswirkung ist jedoch auf das „erneute Polyfüllen“ beschränkt. Angesichts der Tatsache, dass heute der Ansatz zum Herunterladen und Ausführen von Polyfills in allen Browsern vorherrscht, kann dies eine würdige Verbesserung sein.
<!-- newer browsers will not load this bundle: --> <script nomodule src="polyfills.js"></script> <!-- all browsers load this one: --> <script src="/bundle.js"></script>
Sie können die Angular-CLI so konfigurieren, dass dieser Ansatz mit Polyfills verwendet wird, wie
Minko Gachev
gezeigt hat . Nachdem ich diesen Ansatz kennengelernt hatte, wurde mir klar, dass Sie die automatische Polyfill-Injektion in preact-cli aktivieren können. Diese
PR zeigt, wie einfach es ist, diese Technik zu implementieren.
Und wenn Sie WebPack verwenden, gibt es ein
praktisches Plugin für das
html-webpack-plugin
, mit dem Sie Paketen mit Polyfills auf einfache Weise ein Nomodul hinzufügen können.
Also, was soll ich wählen?
Die Antwort hängt von Ihrer Situation ab. Wenn Sie eine Clientanwendung erstellen und Ihr HTML-
<sript>
etwas mehr als
<sript>
, benötigen Sie möglicherweise die
erste Methode .
Wenn Sie eine Site erstellen, die auf dem Server gerendert wird, und sich das Caching leisten können, ist die
zweite Methode möglicherweise
für Sie geeignet.
Wenn Sie
universelles Rendering verwenden, kann der Leistungsgewinn, der durch das Scannen vor dem Laden erzielt wird, sehr wichtig sein. Achten Sie daher auf die
dritte oder
vierte Methode. Wählen Sie, was zu Ihrer Architektur passt.
Persönlich wähle ich, wobei ich mich auf die Dauer des Parsens auf Mobilgeräten konzentriere und nicht auf die Kosten für das Herunterladen in Desktop-Versionen. Mobile Benutzer betrachten Parsing- und Datenübertragungskosten als tatsächliche Kosten (Batterieverbrauch und Datenübertragungsgebühren), während Desktop-Benutzer solche Einschränkungen nicht haben. Ich gehe auch von der Optimierung für 90% der Benutzer aus - das Hauptpublikum meiner Projekte verwendet moderne und / oder mobile Browser.
Was zu lesen
Möchten Sie mehr über dieses Thema erfahren? Sie können von hier aus beginnen: