Hier erfahren Sie, wie die Komponenten von RenderingNG aufgebaut sind und von der Rendering-Pipeline durchlaufen werden.
Auf der obersten Ebene sind das:
- Inhalte in Pixel auf dem Bildschirm rendern
- Visuelle Effekte animieren, mit denen die Inhalte von einem Zustand zum anderen geändert werden können.
- Scrollen Sie als Reaktion auf die Eingabe.
- Eingaben effizient an die richtigen Orte weiterleiten, damit Entwicklerskripts und andere Subsysteme antworten können
Der zu rendernde Inhalt besteht aus einem Frame-Baum für jeden Browsertab sowie der Browseroberfläche. und einen Strom von Roheingabeereignissen von Touchscreens, Mäusen, Tastaturen und anderen Hardwaregeräten.
Jeder Frame enthält:
- DOM-Status
- CSS
- Canvases
- Externe Ressourcen wie Bilder, Videos, Schriftarten und SVG
Ein Frame besteht aus einem HTML-Dokument und seiner URL. Eine Webseite, die in einem Browsertab geladen wird, hat einen Frame der obersten Ebene, untergeordnete Frames für jeden Iframe im übergeordneten Dokument und deren rekursive Iframe-Abkömmlinge.
Ein visueller Effekt ist ein grafischer Vorgang, der auf eine Bitmap angewendet wird, z. B. Scrollen, Transformieren, Zuschneiden, Filtern, Deckkraft oder Überblenden.
Architekturkomponenten
In RenderingNG werden diese Aufgaben logisch auf mehrere Phasen und Codekomponenten aufgeteilt. Die Komponenten landen in verschiedenen CPU-Prozessen, Threads und Unterkomponenten innerhalb dieser Threads. Jedes spielt eine wichtige Rolle bei der Gewährleistung von Zuverlässigkeit, skalierbarer Leistung und Erweiterbarkeit für alle Webinhalte.
Pipelinestruktur rendern
Das Rendering erfolgt in einer Pipeline mit einer Reihe von Phasen und Artefakten, die dabei erstellt werden. Jede Phase steht für Code, der eine klar definierte Aufgabe beim Rendering ausführt. Die Artefakte sind Datenstrukturen, die Eingaben oder Ausgaben der Phasen sind.
Die Phasen:
- Animieren:Berechnete Stile ändern und Property-Abbildungen im Zeitverlauf basierend auf deklarativen Zeitachsen verändern.
- Stil:CSS wird auf das DOM angewendet und berechnete Stile erstellt.
- Layout: Bestimmt die Größe und Position von DOM-Elementen auf dem Bildschirm und erstellt den unveränderlichen Fragmentbaum.
- Vorab-Rendering:Berechnen Sie Property-Bäume und invalidate
- Scrollen:Aktualisieren Sie den Scroll-Offset von Dokumenten und scrollbaren DOM-Elementen, indem Sie Eigenschaftenstrukturen ändern.
- Paint:Eine Anzeigeliste berechnen, in der beschrieben wird, wie GPU-Texturkacheln aus dem DOM gerastert werden.
- Commit: Kopiert die Property-Bäume und die Displayliste in den Compositor-Thread.
- Ebenen: Die Anzeigeliste wird in eine Liste mit zusammengesetzten Ebenen für unabhängige Rasterung und Animation aufgeteilt.
- Raster, Dekodieren und Paint Worklets:Hier werden Displaylisten, codierte Bilder und Paint Worklet-Code in GPU-Textur-Kacheln umgewandelt.
- Aktivieren:Erstellen Sie einen Compositor-Frame, der angibt, wie GPU-Kacheln zusammen mit visuellen Effekten auf dem Bildschirm gezeichnet und positioniert werden sollen.
- Zusammenfassen:Hier werden die Frames aller sichtbaren Compositoren zu einem einzigen globalen Compositor-Frame kombiniert.
- Zeichnen:Ausführen des aggregierten Frames des Renderers auf der GPU, um Pixel auf dem Bildschirm zu erstellen.
Phasen der Rendering-Pipeline können übersprungen werden, wenn sie nicht erforderlich sind. So können beispielsweise Animationen von visuellen Effekten und Scrollen das Layout überspringen, vormalen und malen. Deshalb sind Animation und Scrollen im Diagramm mit gelben und grünen Punkten markiert. Wenn Layout, Vorab-Paint und Paint für visuelle Effekte übersprungen werden können, können sie vollständig im Compositor-Thread ausgeführt und der Hauptthread übersprungen werden.
Das Browser-UI-Rendering wird hier nicht direkt dargestellt, kann aber als vereinfachte Version dieser Pipeline betrachtet werden. Tatsächlich wird bei der Implementierung viel Code gemeinsam genutzt. Video (auch nicht direkt dargestellt) wird in der Regel mit unabhängigem Code gerendert, der Frames in GPU-Texturkacheln decodiert, die dann in Frames des Compositors und in den Zeichenschritt eingebunden werden.
Prozess- und Threadstruktur
CPU-Prozesse
Durch die Verwendung mehrerer CPU-Prozesse wird eine Leistungs- und Sicherheitsisolierung zwischen Websites und dem Browserstatus sowie eine Stabilitäts- und Sicherheitsisolierung von der GPU-Hardware erreicht.
- Der Rendering-Prozess rendert, animiert, scrollt und leitet Eingaben für eine einzelne Kombination aus Website und Tab weiter. Es gibt mehrere Rendering-Prozesse.
- Der Browserprozess rendert, animiert und leitet Eingaben für die Browser-Benutzeroberfläche (einschließlich Adressleiste, Tabtitel und Symbole) weiter und leitet alle verbleibenden Eingaben an den entsprechenden Renderprozess weiter. Es gibt einen Browserprozess.
- Der Viz-Prozess aggregiert das Compositing aus mehreren Rendering-Prozessen sowie dem Browserprozess. Die Rasterung und Zeichnung erfolgt mit der GPU. Es gibt einen Viz-Prozess.
Websites werden immer in verschiedenen Rendering-Prozessen gerendert.
Mehrere Browser-Tabs oder ‑fenster derselben Website werden in der Regel in verschiedenen Rendering-Prozessen verarbeitet, es sei denn, die Tabs sind miteinander verknüpft, z. B. wenn einer den anderen öffnet. Bei hoher Speicherauslastung in der Desktopversion kann Chromium mehrere Tabs derselben Website in denselben Renderingprozess verschieben, auch wenn sie keinen Bezug zueinander haben.
Innerhalb eines einzelnen Browsertabs befinden sich Frames von verschiedenen Websites immer in unterschiedlichen Renderingprozessen, Frames von derselben Website jedoch immer im selben Renderingprozess. Aus Sicht des Renderings besteht der wichtige Vorteil mehrerer Renderingprozesse darin, dass websiteübergreifende iFrames und Tabs eine Leistungsisolierung voneinander erreichen. Außerdem können Ursprünge eine noch stärkere Isolierung aktivieren.
Es gibt genau einen Viz-Prozess für das gesamte Chromium, da es normalerweise nur eine GPU und einen Bildschirm gibt, auf die gezeichnet werden soll.
Wenn Sie die Visualisierung in einem eigenen Prozess ausführen, ist die Stabilität bei Fehlern in GPU-Treibern oder der Hardware höher. Außerdem eignet es sich gut für die Sicherheitsisolierung, was für GPU-APIs wie Vulkan und Sicherheit im Allgemeinen wichtig ist.
Da der Browser viele Tabs und Fenster haben kann und in allen die Browser-UI-Pixel gezeichnet werden müssen, fragen Sie sich vielleicht, warum es genau einen Browserprozess gibt. Der Grund dafür ist, dass nur einer von ihnen jeweils im Fokus ist. Nicht sichtbare Browser-Tabs sind in der Regel deaktiviert und geben ihren gesamten GPU-Speicher frei. Allerdings werden auch komplexe Funktionen zum Rendern der Browser-Benutzeroberfläche zunehmend in Rendering-Prozesse implementiert (WebUI). Dies geschieht nicht aus Gründen der Leistungsisolierung, sondern um die Benutzerfreundlichkeit der Web-Rendering-Engine von Chromium zu nutzen.
Auf älteren Android-Geräten werden der Rendering- und der Browserprozess gemeinsam verwendet, wenn sie in einem WebView verwendet werden. Dies gilt nicht allgemein für Chromium auf Android, sondern nur für WebView. Bei WebView wird der Browserprozess auch mit der Einbettungs-App geteilt und WebView hat nur einen Renderingprozess.
Manchmal gibt es auch einen Dienstprogrammprozess zum Dekodieren geschützter Videoinhalte. Dieser Prozess ist in den vorherigen Diagrammen nicht dargestellt.
Unterhaltungen
Threads tragen dazu bei, trotz langsamer Aufgaben, Pipeline-Parallelisierung und Mehrfachpufferung eine Leistungsisolierung und Reaktionsfähigkeit zu erreichen.
- Im Hauptthread werden Skripts, die Rendering-Ereignisschleife, der Dokumentlebenszyklus, Treffertests, die Weiterleitung von Skriptereignissen und das Parsen von HTML-, CSS- und anderen Datenformaten ausgeführt.
- Hilfsprogramme für den Hauptthread führen Aufgaben wie das Erstellen von Bild-Bitmaps und Blobs aus, die eine Codierung oder Decodierung erfordern.
- Web Worker führen ein Script und eine Rendering-Ereignisschleife für OffscreenCanvas aus.
- Der Compositor-Thread verarbeitet Eingabeereignisse, führt Scrollen und Animationen von Webinhalten durch, berechnet die optimale Schichtierung von Webinhalten und koordiniert Bilddecodierungen, Paint-Worklets und Rasteraufgaben.
- Compositor-Thread-Hilfsprogramme koordinieren Viz-Rasteraufgaben und führen Bilddekodierungsaufgaben, Paint-Worklets und Fallback-Raster aus.
- Medien-, Demuxer- oder Audioausgabe-Threads decodieren, verarbeiten und synchronisieren Video- und Audiostreams. Denken Sie daran, dass die Videoverarbeitung parallel zur Haupt-Rendering-Pipeline ausgeführt wird.
Die Trennung des Haupt- und des Kompositions-Threads ist entscheidend für die Leistungsisolation von Animationen und Scrollen von der Arbeit des Hauptthreads.
Es gibt nur einen Haupt-Thread pro Renderprozess, auch wenn mehrere Tabs oder Frames von derselben Website im selben Prozess landen können. Es gibt jedoch eine Leistungsisolierung von Arbeiten, die in verschiedenen Browser-APIs ausgeführt werden. So wird beispielsweise die Generierung von Bild-Bitmaps und -Blobs in der Canvas API in einem Hilfsthread des Hauptthreads ausgeführt.
Ebenso gibt es nur einen zusammengesetzten Thread pro Renderingprozess. Es ist im Allgemeinen kein Problem, dass es nur einen gibt, da alle wirklich teuren Vorgänge im Compositor-Thread entweder an Compositor-Arbeitsthreads oder an den Viz-Prozess delegiert werden. Diese Arbeit kann parallel zum Routing von Eingaben, zum Scrollen oder zur Animation ausgeführt werden. Compositor-Worker-Threads koordinieren Aufgaben, die im Viz-Prozess ausgeführt werden. Die allgemeine GPU-Beschleunigung kann jedoch aus Gründen, die außerhalb der Kontrolle von Chromium liegen, wie z. B. Treiberfehlern, fehlschlagen. In diesen Fällen führt der Worker-Thread die Arbeit im Fallback-Modus auf der CPU aus.
Die Anzahl der Compositor-Worker-Threads hängt von den Funktionen des Geräts ab. So werden auf Desktop-Computern in der Regel mehr Threads verwendet, da sie mehr CPU-Kerne haben und weniger Akku-Einschränkungen als Mobilgeräte haben. Dies ist ein Beispiel für die Skalierung nach oben und unten.
Die Threading-Architektur des Renderingprozesses ist eine Anwendung von drei verschiedenen Optimierungsmustern:
- Hilfsthreads: Senden langlaufender Teilaufgaben an zusätzliche Threads, damit der übergeordnete Thread für andere, gleichzeitige Anfragen reaktionsfähig bleibt. Gute Beispiele für diese Technik sind die Helper- und Compositor-Hilfsthreads.
- Mehrfache Zwischenspeichern: Beim Rendern neuer Inhalte werden zuvor gerenderte Inhalte angezeigt, um die Rendering-Latenz auszublenden. Der Compositor-Thread verwendet diese Technik.
- Pipeline-Parallelisierung:Die Rendering-Pipeline wird an mehreren Stellen gleichzeitig ausgeführt. So können Scrollen und Animation schnell erfolgen. Selbst wenn ein Rendering-Update des Hauptthreads ausgeführt wird, können Scrollen und Animation parallel ausgeführt werden.
Browserprozess
- Der Render- und Komposition-Thread reagiert auf Eingaben in der Browser-Benutzeroberfläche, leitet andere Eingaben an den richtigen Renderprozess weiter und legt die Browser-Benutzeroberfläche aus und malt sie.
- Die Render- und Kompositions-Thread-Hilfsfunktionen führen Aufgaben zur Bilddekodierung und Fallback-Raster- oder Dekodierung aus.
Der Render- und der Komposition-Thread des Browserprozesses ähneln dem Code und der Funktionalität eines Renderprozesses, mit der Ausnahme, dass der Haupt- und der Komposition-Thread zu einem einzigen Thread kombiniert werden. In diesem Fall ist nur ein Thread erforderlich, da keine Leistungsisolierung von langen Aufgaben im Hauptthread erforderlich ist, da es keine gibt.
Viz-Prozess
- Der GPU-Hauptthread rastert Displaylisten und Videoframes in GPU-Texturkacheln und zeichnet Renderer-Frames auf dem Bildschirm.
- Der Display-Compositor-Thread aggregiert und optimiert das Compositing aus jedem Rendering-Prozess sowie dem Browserprozess in einem einzigen Compositor-Frame für die Darstellung auf dem Bildschirm.
Raster und Zeichnungen werden im Allgemeinen im selben Thread ausgeführt, da beide GPU-Ressourcen von GPU-Ressourcen nutzen und es schwierig ist, die GPU zuverlässig Multithreads zu nutzen. Ein einfacherer Multithread-Zugriff auf die GPU ist eine Motivation für die Entwicklung des neuen Vulkan-Standards. In Android WebView gibt es aufgrund der Einbettung von WebViews in eine native App einen separaten Render-Thread auf Betriebssystemebene. Andere Plattformen werden in Zukunft wahrscheinlich auch einen solchen Thread haben.
Der Display-Compositor befindet sich in einem anderen Thread, da er jederzeit reaktionsschnell sein und keine möglichen Ursachen für eine Verlangsamung des GPU-Hauptthreads blockieren darf. Eine Ursache für eine Verlangsamung des GPU-Hauptthreads sind Aufrufe von nicht Chromium-Code, z. B. anbieterspezifische GPU-Treiber, die auf schwer vorhersehbare Weise langsam sein können.
Komponentenstruktur
Innerhalb jedes Haupt- oder Kompositionsleiters des Rendering-Prozesses gibt es logische Softwarekomponenten, die strukturiert miteinander interagieren.
Hauptthread-Komponenten des Rendering-Prozesses
Im Blink-Renderer:
- Das Fragment des lokalen Frame-Baums stellt den Baum der lokalen Frames und das DOM innerhalb der Frames dar.
- Die Komponente DOM- und Canvas-APIs enthält Implementierungen all dieser APIs.
- Der Document Lifecycle Runner führt die Schritte der Rendering-Pipeline bis einschließlich des Commit-Schritts aus.
- Die Komponente Treffertests und Weiterleitung von Eingabeereignissen führt Treffertests aus, um herauszufinden, auf welches DOM-Element ein Ereignis abzielt, und führt die Algorithmen und das Standardverhalten der Eingabeereignisse aus.
Der Scheduler und Runner für die Ereignisschleife entscheidet, was und wann in der Ereignisschleife ausgeführt wird. Das Rendering wird so geplant, dass es in einem Rhythmus erfolgt, der mit dem Gerätedisplay übereinstimmt.
Lokale Frame Tree-Fragmente sind etwas kompliziert. Ein Frame-Baum besteht aus der Hauptseite und ihren untergeordneten IFrames, rekursiv. Ein Frame ist lokal für einen Rendering-Prozess, wenn er in diesem Prozess gerendert wird. Andernfalls ist er remote.
Sie können sich vorstellen, Frames entsprechend ihrem Rendering-Prozess zu färben. Auf dem vorherigen Bild sind die grünen Kreise alle Frames in einem Rendering-Prozess, die orangefarbenen in einem zweiten und der blaue in einem dritten.
Ein lokales Frame-Baumfragment ist eine verbundene Komponente derselben Farbe in einem Frame-Baum. Auf dem Bild sind vier lokale Framebäume zu sehen: zwei für Standort A, einer für Standort B und einer für Standort C. Jeder lokale Frame-Baum erhält eine eigene Blink-Rendering-Komponente. Der Blink-Renderer einer lokalen Framestruktur kann sich im selben Renderingprozess befinden wie andere lokale Framestrukturen. Sie wird wie oben beschrieben durch die Art und Weise bestimmt, wie Renderingprozesse ausgewählt werden.
Aufbau des Compositor-Threads des Renderingprozesses
Die zusammengesetzten Komponenten des Renderingprozesses umfassen:
- Ein Datenhandler, der eine Liste der zusammengesetzten Ebenen, Anzeigelisten und Property-Bäume verwaltet.
- Ein Lebenszyklus-Ausführer, der die Schritte „animieren“, „scrollen“, „zusammensetzen“, „rastern“ und „decodieren“ und aktivieren der Rendering-Pipeline ausführt. Denken Sie daran, dass „animieren“ und „scrollen“ sowohl im Hauptthread als auch im Renderer erfolgen können.
- Ein Eingabe- und Treffertest-Handler führt die Eingabeverarbeitung und den Treffertest mit der Auflösung der zusammengesetzten Ebenen aus, um zu ermitteln, ob Scroll-Gesten im Compositor-Thread ausgeführt werden können und auf welchen Renderprozess die Treffertests ausgerichtet werden sollen.
Beispielarchitektur in der Praxis
In diesem Beispiel gibt es drei Tabs:
Tab 1: foo.com
<html>
<iframe id=one src="foo.com/other-url"></iframe>
<iframe id=two src="bar.com"></iframe>
</html>
Tab 2: bar.com
<html>
…
</html>
Tab 3: baz.com
html
<html>
…
</html>
Die Prozess-, Thread- und Komponentenstruktur für diese Tabs sieht so aus:
Sehen wir uns jeweils ein Beispiel für die vier Hauptaufgaben des Renderings an. Zur Erinnerung:
- Inhalte in Pixel auf dem Bildschirm rendern.
- Animate Sie visuelle Effekte auf die Inhalte von einem Zustand zum anderen.
- Je nach Eingabe wird gescrollt.
- Leiten Sie die Eingabe effizient an die richtigen Stellen weiter, damit Entwicklerscripts und andere Teilsysteme reagieren können.
So rendern Sie das geänderte DOM für Tab 1:
- Ein Entwicklerskript ändert das DOM im Renderprozess für foo.com.
- Der Blink-Renderer teilt dem Compositor mit, dass ein Rendering erforderlich ist.
- Der Compositor teilt Viz mit, dass ein Rendern erforderlich ist.
- Viz signalisiert dem Compositor den Beginn des Renderings.
- Der Compositor leitet das Startsignal an den Blink-Renderer weiter.
- Der Event-Loop-Ausführer des Hauptthreads führt den Dokumentlebenszyklus aus.
- Der Hauptthread sendet das Ergebnis an den Compositor-Thread.
- Der Runner für die Zusammensetzungsereignisschleife führt den Compositing-Lebenszyklus aus.
- Alle Rasteraufgaben werden an Viz für Raster gesendet (es gibt oft mehr als eine dieser Aufgaben).
- Viz-Rasterinhalte auf der GPU.
- Viz bestätigt den Abschluss der Rasteraufgabe. Hinweis: Chromium wartet oft nicht, bis der Raster fertig ist, sondern verwendet stattdessen ein sogenanntes Synchronisierungstoken, das von Rasteraufgaben aufgelöst werden muss, bevor Schritt 15 ausgeführt wird.
- Ein Compositor-Frame wird an Viz gesendet.
- Viz aggregiert die Compositor-Frames für den foo.com-Renderingprozess, den bar.com-iFrame-Renderingprozess und die Browser-UI.
- Viz plant eine Auslosung.
- Viz zeichnet den aggregierten Compositor-Frame auf den Bildschirm.
So animieren Sie einen CSS-Transformationsübergang auf Tab 2:
- Der Compositor-Thread für den Renderprozess von bar.com tickt eine Animation in seinem Compositor-Ereignis-Loop, indem er die vorhandenen Property-Bäume mutiert. Dadurch wird der Lebenszyklus des Renderers noch einmal durchlaufen. Raster- und Dekodierungsaufgaben können vorkommen, werden hier aber nicht dargestellt.
- Ein Compositor-Frame wird an Viz gesendet.
- Viz aggregiert die Compositor-Frames für den foo.com-Renderingprozess, den bar.com-Renderingprozess und die Browser-Benutzeroberfläche.
- Viz plant eine Auslosung.
- Viz zeichnet den zusammengefassten Renderer-Frame auf dem Bildschirm.
So scrollen Sie auf Tab 3 durch die Webseite:
- Eine Sequenz von
input
-Ereignissen (Maus, Touch oder Tastatur) wird an den Browserprozess gesendet. - Jedes Ereignis wird an den Kompositionsleiter des Renderprozesses von baz.com weitergeleitet.
- Der Compositor bestimmt, ob der Haupt-Thread über das Ereignis informiert werden muss.
- Das Ereignis wird bei Bedarf an den Hauptthread gesendet.
- Der Hauptthread löst
input
-Event-Listener (pointerdown
,touchstar
,pointermove
,touchmove
oderwheel
) aus, um zu sehen, ob ListenerpreventDefault
für das Ereignis aufrufen. - Der Hauptthread gibt zurück, ob
preventDefault
an den Compositor übergeben wurde. - Andernfalls wird das Eingabeereignis an den Browserprozess zurückgesendet.
- Der Browserprozess konvertiert sie in eine Scroll-Geste, indem sie mit anderen aktuellen Ereignissen kombiniert wird.
- Die Scroll-Geste wird noch einmal an den Compositor-Thread von baz.com gesendet,
- Dort wird das Scrollen angewendet und der Compositor-Thread für den Rendervorgang von bar.com tickt eine Animation in seinem Compositor-Ereignis-Loop.
Dadurch wird der Scroll-Offset in den Property-Bäumen verändert und der Lebenszyklus des Renderers wird noch einmal ausgeführt.
Außerdem wird der Hauptthread angewiesen, ein
scroll
-Ereignis auszulösen (nicht hier dargestellt). - Ein Compositor-Frame wird an Viz gesendet.
- Viz aggregiert die Frames des Renderers für den Renderingprozess von foo.com, den Renderingprozess von bar.com und die Browser-Benutzeroberfläche.
- Visualisierung plant ein Remis.
- Viz zeichnet den aggregierten Compositor-Frame auf den Bildschirm.
So leiten Sie ein click
-Ereignis über einen Hyperlink in iFrame 2 auf Tab 1 weiter:
- Ein
input
-Ereignis (Maus, Touch oder Tastatur) wird an den Browserprozess gesendet. Es wird ein ungefährer Treffertest durchgeführt, um festzustellen, ob der iFrame-Rendering-Prozess von bar.com den Klick erhalten soll. Wenn ja, wird er dorthin gesendet. - Der Compositor-Thread für bar.com leitet das
click
-Ereignis an den Hauptthread für bar.com weiter und plant eine Rendering-Ereignisschleife für die Verarbeitung. - Der Eingabeereignis-Prozessor für den Hauptthread von bar.com führt Treffertests durch, um zu ermitteln, auf welches DOM-Element im Iframe geklickt wurde, und löst ein
click
-Ereignis für Scripts aus. Wenn keinpreventDefault
erkannt wird, wird der Hyperlink aufgerufen. - Beim Laden der Zielseite des Hyperlinks wird der neue Status gerendert. Dabei werden ähnliche Schritte wie im vorherigen Beispiel „Rendern des geänderten DOM“ ausgeführt. (Die nachfolgenden Änderungen sind hier nicht enthalten.)
Fazit
Es kann viel Zeit dauern, sich zu merken und zu verinnerlichen, wie das Rendering funktioniert.
Das Wichtigste ist, dass die Rendering-Pipeline durch sorgfältige Modularisierung und Liebe zum Detail in eine Reihe von eigenständigen Komponenten aufgeteilt wurde. Diese Komponenten wurden dann auf parallele Prozesse und Threads aufgeteilt, um die skalierbare Leistung und die Erweiterbarkeit zu maximieren.
Jede Komponente spielt eine wichtige Rolle bei der Leistung und den Funktionen moderner Webanwendungen.
Lesen Sie weiter zu den Schlüsseldatenstrukturen, die für RenderingNG genauso wichtig sind wie Codekomponenten.
Illustrationen von Una Kravets