Modernes Skriptladen

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) { // notice the casing s.type = 'module' s.src = '/modern.js' } else { s.src = '/legacy.js' } document.head.appendChild(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) { // notice the casing s.type = 'module' s.src = '/modern.js' } else s.src = '/legacy.js' } document.head.appendChild(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?

Vorladen

Da 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:

Source: https://habr.com/ru/post/de473994/


All Articles