
Dieses Dokument enthält eine Liste von Praktiken, die uns helfen, die Leistung unserer Winkelanwendungen zu steigern. "Angular Performance Checkliste" deckt verschiedene Themen ab-von der serverseitigen Vorrenderung und der Bündelung unserer Anwendungen bis zur Laufzeitleistung und der Optimierung der vom Framework durchgeführten Änderungserkennung.
Das Dokument ist in zwei Hauptabschnitte unterteilt:
Einige Praktiken wirken sich auf beide Kategorien aus, sodass es eine leichte Schnittstelle geben kann. Die Unterschiede in den Anwendungsfällen und die Auswirkungen werden jedoch explizit erwähnt.
Die meisten Unterabschnitte listen Tools auf, die sich auf die spezifische Praxis beziehen, die uns durch die Automatisierung unseres Entwicklungsflusss effizienter machen können.
Beachten Sie, dass die meisten Praktiken sowohl für HTTP/1.1 als auch für HTTP/2 gültig sind. Praktiken, die eine Ausnahme ausmachen, werden erwähnt, indem angegeben wird, auf welche Version des Protokolls sie angewendet werden könnten.
enableProdModeChangeDetectionStrategy.OnPush*ngFor DirektivetrackBy -OptionEinige der Tools in diesem Abschnitt sind noch in der Entwicklung und ändern sich. Das Angular Core -Team arbeitet an der Automatisierung des Build -Prozesses für unsere Anwendungen so weit wie möglich, damit viele Dinge transparent passieren werden.
Die Bündelung ist eine Standardpraxis, mit der die Anzahl der Anfragen, die der Browser ausführen muss, reduzieren soll, um die vom Benutzer angeforderte Anwendung zu liefern. Im Wesentlichen erhält der Bundler als Eingabe eine Liste von Einstiegspunkten und produziert einen oder mehrere Bündel. Auf diese Weise kann der Browser die gesamte Anwendung abrufen, indem nur wenige Anfragen durchgeführt werden, anstatt jede einzelne Ressource separat anzufordern.
Wenn Ihre Anwendung alles in ein einzelnes großes Bündel bündelt, wäre es wieder kontraproduktiv. Erforschen Sie die Code -Spliting -Techniken mithilfe von WebPack.
Zusätzliche HTTP -Anfragen sind aufgrund der Server -Push -Funktion kein Problem mit HTTP/2.
Werkzeug
Tools, die es uns ermöglichen, unsere Anwendungen effizient zu bündeln, sind:
Ressourcen
Diese Praktiken ermöglichen es uns, den Bandbreitenverbrauch zu minimieren, indem wir die Nutzlast unserer Anwendung verringern.
Werkzeug
Ressourcen
Obwohl wir den Whitespace -Charakter (ein Zeichen, das mit der Regex des s ) nicht angezeigt wird, wird er immer noch durch Bytes dargestellt, die über das Netzwerk übertragen werden. Wenn wir die Whitespace von unseren Vorlagen auf das Minimum reduzieren, können wir die Bündelgröße des AOT -Codes noch weiter fallen lassen.
Zum Glück müssen wir das nicht manuell tun. Die ComponentMetadata -Schnittstelle enthält die Eigenschaft preserveWhitespaces false die standardmäßig einen Wert hat. Falls wir die Eigenschaft auf true Angular setzen, erhalten Sie die Whitespace.
Für die endgültige Version unserer Anwendungen verwenden wir normalerweise nicht den gesamten Code, der von Angular und/oder einer Bibliothek von Drittanbietern bereitgestellt wird, selbst die, die wir geschrieben haben. Dank der statischen Natur der ES2015 -Module können wir den Code loswerden, auf den in unseren Apps nicht verwiesen wird.
Beispiel
// foo.js
export foo = ( ) => 'foo' ;
export bar = ( ) => 'bar' ;
// app.js
import { foo } from './foo' ;
console . log ( foo ( ) ) ; Sobald wir app.js Baumschake und Bündel haben, bekommen wir:
let foo = ( ) => 'foo' ;
console . log ( foo ( ) ) ; Dies bedeutet, dass die nicht verwendete bar nicht in das endgültige Bündel aufgenommen wird.
Werkzeug
Hinweis: GCC unterstützt noch keinen export * , was für den Aufbau von Winkelanwendungen aufgrund der starken Verwendung des "Fass" -Musters essentiell ist.
Ressourcen
Seit der Veröffentlichung von Angular Version 6 stellte das Angular-Team eine neue Funktion zur Verfügung, mit der Dienste baS-satt werden können. Dies bedeutet, dass Ihre Dienste nicht in das endgültige Bundle aufgenommen werden, es sei denn, sie werden von anderen Diensten oder Komponenten verwendet. Dies kann dazu beitragen, die Bündelgröße zu reduzieren, indem ungenutzter Code aus dem Bundle entfernt wird.
Sie können Ihre Dienste baumwasch machen, indem Sie das providedIn Attribut verwenden, um zu definieren, wo der Dienst initialisiert werden soll, wenn @Injectable() Dekorateur verwendet werden soll. Anschließend sollten Sie es aus dem providers Ihrer NgModule -Deklaration sowie seiner Importanweisung wie folgt entfernen.
Vor:
// app.module.ts
import { NgModule } from '@angular/core'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import { environment } from '../environments/environment'
import { MyService } from './app.service'
@ NgModule ( {
declarations : [
AppComponent
] ,
imports : [
...
] ,
providers : [ MyService ] ,
bootstrap : [ AppComponent ]
} )
export class AppModule { } // my-service.service.ts
import { Injectable } from '@angular/core'
@ Injectable ( )
export class MyService { }Nach:
// app.module.ts
import { NgModule } from '@angular/core'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import { environment } from '../environments/environment'
@ NgModule ( {
declarations : [
AppComponent
] ,
imports : [
...
] ,
providers : [ ] ,
bootstrap : [ AppComponent ]
} )
export class AppModule { } // my-service.service.ts
import { Injectable } from '@angular/core'
@ Injectable ( {
providedIn : 'root'
} )
export class MyService { } Wenn MyService in keiner Komponente/eines Dienstes injiziert wird, wird es nicht in das Bundle enthalten.
Ressourcen
Eine Herausforderung für die verfügbaren Wildwerkzeuge (wie GCC, Rollup usw.) sind die HTML-ähnlichen Vorlagen der Winkelkomponenten, die nicht mit ihren Funktionen analysiert werden können. Dies macht ihre Baumschütteln weniger effizient, da sie nicht sicher sind, auf welche Richtlinien in den Vorlagen verwiesen werden. Der AOT-Compiler transpiliert die winkelartigen HTML-ähnlichen Vorlagen in JavaScript oder TypeScript mit ES2015-Modulimporten. Auf diese Weise können wir während der Bündelung alle nicht verwendeten Richtlinien, die durch eckige Bibliotheken von Drittanbietern oder von uns selbst definiert sind, effizient Baumschake effizient entfernen.
Ressourcen
Komprimierung der Nutzlast -Standardpraxis der Antworten für die Reduzierung der Gebrauchsnutzung der Bandbreite. Durch die Angabe des Werts des Header Accept-Encoding weist der Browser auf den Server hin, den Komprimierungsalgorithmen auf der Maschine des Clients verfügbar sind. Andererseits legt der Server den Wert für den Content-Encoding Header der Antwort fest, um den Browser mitzuteilen, welcher Algorithmus zur Komprimierung der Antwort ausgewählt wurde.
Werkzeug
Das Werkzeug hier ist nicht angularspezifisch und hängt ganz vom von uns verwendeten Web-/Anwendungsserver ab. Typische Komprimierungsalgorithmen sind:
Ressourcen
Ressourcenvorbereitungen sind eine großartige Möglichkeit, die Benutzererfahrung zu verbessern. Wir können entweder Assets (Bilder, Stile, Module, die faul geladen sind usw.) oder Daten vorab abgerufen werden. Es gibt verschiedene Strategien vor der Abnahme, aber die meisten von ihnen hängen von Einzelheiten der Anwendung ab.
Wenn die Zielanwendung eine riesige Codebasis mit Hunderten von Abhängigkeiten hat, können die oben aufgeführten Praktiken das Bündel möglicherweise nicht auf eine angemessene Größe reduzieren (angemessen ist möglicherweise 100.000 oder 2 m, es hängt wiederum vollständig von den Geschäftszielen ab).
In solchen Fällen könnte eine gute Lösung darin bestehen, einige der Module der Anwendung träge zu laden. Nehmen wir zum Beispiel an, wir bauen ein E-Commerce-System auf. In diesem Fall möchten wir das Administratorfeld möglicherweise unabhängig von der Benutzeroberfläche laden. Sobald der Administrator ein neues Produkt hinzufügen muss, möchten wir die dafür erforderliche Benutzeroberfläche bereitstellen. Dies kann je nach den Anforderungen an den Anwendungsfall/die Geschäfts-/Geschäftsanforderungen entweder nur die "Produktseite hinzufügen" oder das gesamte Verwaltungsbereich sein.
Werkzeug
Nehmen wir an, wir haben die folgende Routing -Konfiguration:
// Bad practice
const routes : Routes = [
{ path : '' , redirectTo : '/dashboard' , pathMatch : 'full' } ,
{ path : 'dashboard' , loadChildren : ( ) => import ( './dashboard.module' ) . then ( mod => mod . DashboardModule ) } ,
{ path : 'heroes' , loadChildren : ( ) => import ( './heroes.module' ) . then ( mod => mod . HeroesModule ) }
] ; Wenn der Benutzer zum ersten Mal die Anwendung mit der URL öffnet: https://example.com/, wird sie in /dashboard umgeleitet, wodurch die Lazy-Route mit dem Pfad dashboard ausgelöst wird. Um die Bootstrap -Komponente des Moduls zu rendern, muss das Datei dashboard.module und alle seine Abhängigkeiten herunterladen. Später muss die Datei vom JavaScript -VM analysiert und ausgewertet werden.
Das Auslösen von zusätzlichen HTTP -Anforderungen und die Durchführung unnötiger Berechnungen während der Last der ersten Seite ist eine schlechte Praxis, da es die Anfangsseitungs -Renderung verlangsamt. Erwägen Sie, die Standard-Seitenroute als nicht-faul zu deklarieren.
Caching ist eine weitere übliche Praxis, die beabsichtigt, unsere Bewerbung zu beschleunigen, indem sie die Heuristik ausnutzt, dass, wenn kürzlich eine Ressource angefordert wurde, in naher Zukunft erneut angefordert werden könnte.
Für das Zwischenspeichern von Daten verwenden wir normalerweise einen benutzerdefinierten Caching -Mechanismus. Für das Zwischenspeichern statischer Vermögenswerte können wir entweder das Standard -Browser -Caching- oder Servicemitarbeiter mit der Cachestorage -API verwenden.
Verwenden Sie eine Anwendungsschale, um die wahrgenommene Leistung Ihrer Anwendung schneller zu gestalten.
Die Anwendungsschale ist die minimale Benutzeroberfläche, die wir den Benutzern anzeigen, um anzugeben, dass die Anwendung in Kürze geliefert wird. Für die dynamische Generierung einer Anwendungsschale können Sie Angular Universal mit benutzerdefinierten Anweisungen verwenden, die Elemente abhängig von der verwendeten Rendering-Plattform bedingt anzeigen (dh alles ausblenden Sie alles außer der App-Shell, wenn Sie platform-server verwenden).
Werkzeug
Ressourcen
Wir können uns den Servicearbeiter als einen HTTP -Proxy vorstellen, der sich im Browser befindet. Alle vom Kunden gesendeten Anfragen werden zunächst vom Servicearbeiter abgefangen, der diese entweder umgehen oder über das Netzwerk weiterleiten kann.
Sie können Ihrem Angular -Projekt einen Servicearbeiter hinzufügen, indem Sie ng add @angular/pwa ausführen
Werkzeug
Ressourcen
Dieser Abschnitt enthält Praktiken, die angewendet werden können, um reibungslosere Benutzererfahrung mit 60 Bildern pro Sekunde (FPS) zu bieten.
enableProdModeIm Entwicklungsmodus führt Angular einige zusätzliche Überprüfungen durch, um zu überprüfen, ob die Durchführung der Änderungserkennung keine zusätzlichen Änderungen an einer der Bindungen führt. Auf diese Weise stellt die Frameworks sicher, dass der unidirektionale Datenfluss befolgt wurde.
Um diese Änderungen für die Produktion zu deaktivieren, vergessen Sie nicht, enableProdMode aufzurufen:
import { enableProdMode } from '@angular/core' ;
if ( ENV === 'production' ) {
enableProdMode ( ) ;
}AOT kann nicht nur hilfreich sein, um eine effizientere Bündelung durch Durchführung von Baumschütteln zu erreichen, sondern auch für die Verbesserung der Laufzeitleistung unserer Anwendungen. Die Alternative von AOT ist Just-in-Time Compilation (JIT), die Laufzeit durchgeführt wird. Daher können wir die Menge an Berechnungen reduzieren, die für die Rendern unserer Anwendung erforderlich sind, indem wir die Kompilierung als Teil unseres Build-Prozesses durchführen.
Werkzeug
ng serve --prodRessourcen
Das übliche Problem in der typischen einseitigen Anwendung (SPA) besteht darin, dass unser Code normalerweise in einem einzelnen Thread ausgeführt wird. Dies bedeutet, dass wir, wenn wir reibungslose Benutzererfahrung mit 60 fps erzielen möchten, höchstens 16 ms für die Ausführung zwischen den einzelnen Frames erhalten werden. Andernfalls werden sie um die Hälfte fallen.
In komplexen Anwendungen mit einem riesigen Komponentenbaum, bei dem die Änderungserkennung pro Sekunde Millionen von Schecks durchführen muss, ist es nicht schwierig, Frames zu fallen. Dank der Agnostizismus des Angulars und der Entkoppelung der Dom -Architektur ist es möglich, unsere gesamte Anwendung (einschließlich Änderungserkennung) in einem Webarbeiter auszuführen und den Haupt -UI -Thread nur für das Rendern verantwortlich zu lassen.
Werkzeug
Ressourcen
Ein großes Problem des traditionellen Spa ist, dass sie erst dann gerendert werden können, wenn das gesamte JavaScript für ihr anfängliches Rendering erforderlich ist. Dies führt zu zwei großen Problemen:
Das Server-Side-Rendering löst dieses Problem, indem Sie die angeforderte Seite auf dem Server vorab rendern und das Erzählen der gerenderten Seite während des Ladens der ersten Seite bereitstellen.
Werkzeug
Ressourcen
Bei jedem asynchronen Ereignis führt Angular die Änderungserkennung über den gesamten Komponentenbaum durch. Obwohl der Code, der Änderungen erkennt, für die Inline-Caching optimiert wird, kann dies in komplexen Anwendungen immer noch eine starke Berechnung sein. Eine Möglichkeit, die Leistung der Änderungserkennung zu verbessern, besteht darin, sie nicht für Unterbaume auszuführen, die aufgrund der jüngsten Aktionen nicht geändert werden sollen.
ChangeDetectionStrategy.OnPush Die OnPush -Änderungserkennungsstrategie ermöglicht es uns, den Änderungserkennungsmechanismus für Unterbäume des Komponentenbaums zu deaktivieren. Durch Festlegen der Änderungserkennungsstrategie auf eine beliebige Komponente auf den Wert ChangeDetectionStrategy.OnPush . Angular betrachtet die Eingaben als unterschiedlich, wenn es sie mit den vorherigen Eingaben durch Referenz vergleicht, und das Ergebnis der Referenzprüfung ist false . In Kombination mit unveränderlichen Datenstrukturen kann OnPush großartige Auswirkungen auf die Leistung für solche "reinen" Komponenten haben.
Ressourcen
Eine andere Möglichkeit, einen maßgeschneiderten Änderungserkennungsmechanismus zu implementieren, besteht darin, den Änderungsdetektor (CD) für eine gegebene Komponente detach und reattach zu wiederholen. Sobald wir detach führt der CD -Winkel keine Überprüfung für den gesamten Komponenten -Subtree durch.
Diese Praxis wird normalerweise verwendet, wenn Benutzeraktionen oder Interaktionen mit externen Diensten die Änderungserkennung häufiger als erforderlich auslösen. In solchen Fällen möchten wir möglicherweise in Betracht ziehen, den Änderungsdetektor abzulösen und es nur wieder zu wiederholen, wenn die Änderungserkennung erforderlich ist.
Der Änderungserkennungsmechanismus des Angulars wird dank Zone.js. Zone.js Monkey Patches alle asynchronen APIs im Browser und löst die Änderungserkennung am Ende der Ausführung eines asynchronen Rückrufs aus. In seltenen Fällen möchten wir möglicherweise, dass der angegebene Code außerhalb des Kontextes der Winkelzone und damit, ohne Änderungserkennungsmechanismus auszuführen, ausgeführt wird. In solchen Fällen können wir die Methode runOutsideAngular der NgZone -Instanz verwenden.
Beispiel
Im folgenden Ausschnitt sehen Sie ein Beispiel für eine Komponente, die diese Praxis verwendet. Wenn die Methode _incrementPoints bezeichnet wird, startet die Komponente alle 10 ms die Eigenschaft _points (standardmäßig). Die Inkrementierung macht die Illusion einer Animation. Da wir in diesem Fall den Änderungserkennungsmechanismus für den gesamten Komponentenbaum, alle 10 ms, nicht auslösen möchten, können wir _incrementPoints außerhalb des Kontextes der Angular -Zone ausführen und den DOM manuell aktualisieren (siehe points -Setter).
@ Component ( {
template : '<span #label></span>'
} )
class PointAnimationComponent {
@ Input ( ) duration = 1000 ;
@ Input ( ) stepDuration = 10 ;
@ ViewChild ( 'label' ) label : ElementRef ;
@ Input ( ) set points ( val : number ) {
this . _points = val ;
if ( this . label ) {
this . label . nativeElement . innerText = this . _pipe . transform ( this . points , '1.0-0' ) ;
}
}
get points ( ) {
return this . _points ;
}
private _incrementInterval : any ;
private _points : number = 0 ;
constructor ( private _ngZone : NgZone , private _pipe : DecimalPipe ) { }
ngOnChanges ( changes : any ) {
const change = changes . points ;
if ( ! change ) {
return ;
}
if ( typeof change . previousValue !== 'number' ) {
this . points = change . currentValue ;
} else {
this . points = change . previousValue ;
this . _ngZone . runOutsideAngular ( ( ) => {
this . _incrementPoints ( change . currentValue ) ;
} ) ;
}
}
private _incrementPoints ( newVal : number ) {
const diff = newVal - this . points ;
const step = this . stepDuration * ( diff / this . duration ) ;
const initialPoints = this . points ;
this . _incrementInterval = setInterval ( ( ) => {
let nextPoints = Math . ceil ( initialPoints + diff ) ;
if ( this . points >= nextPoints ) {
this . points = initialPoints + diff ;
clearInterval ( this . _incrementInterval ) ;
} else {
this . points += step ;
}
} , this . stepDuration ) ;
}
}Warnung : Verwenden Sie diese Praxis nur sehr sorgfältig, wenn Sie sicher sind, was Sie tun, denn wenn Sie nicht richtig verwendet werden, kann dies zu einem inkonsistenten Zustand des DOM führen. Beachten Sie auch, dass der obige Code nicht in Web Workers ausgeführt wird. Um es Webworker-kompatibel zu machen, müssen Sie den Wert des Etiketts mit dem Renderer des Angulars festlegen.
Angular verwendet Zone.js, um Ereignisse abzufangen, die in der Anwendung aufgetreten sind, und führt automatisch eine Änderungserkennung aus. Standardmäßig geschieht dies, wenn die Mikrotask -Warteschlange des Browsers leer ist, was in einigen Fällen redundante Zyklen aufrufen kann. Von V9 bietet Angular eine Möglichkeit, Ereigniswechsel -Erkennungen zu vereinen, indem ngZoneEventCoalescing eingeschaltet wird, d.
platformBrowser ( )
. bootstrapModule ( AppModule , { ngZoneEventCoalescing : true } ) ; In der obigen Konfiguration wird die Änderung der Erkennung mit requestAnimationFrame geplant, anstatt sich in die Mikrotask -Warteschlange anzuschließen, wodurch die Überprüfungen weniger häufig ausgeführt werden und weniger Rechenzyklen konsumiert werden.
WARNUNG: NgzoneEventCoalescing: TRUE kann vorhandene Apps brechen, die die konsequent ausgeführte Änderungserkennung weiterleiten.
Ressourcen
Als Argument akzeptiert der @Pipe Decorator ein Objektliteral mit dem folgenden Format:
interface PipeMetadata {
name : string ;
pure : boolean ;
}Die reine Flagge zeigt an, dass das Rohr nicht von einem globalen Zustand abhängt und keine Nebenwirkungen erzeugt. Dies bedeutet, dass das Rohr beim Aufrufen mit demselben Eingang die gleiche Ausgabe zurückgibt. Auf diese Weise kann Angular die Ausgänge für alle Eingangsparameter, mit denen das Rohr aufgerufen wurde, zwischenspeichern und sie wiederverwenden, um sie bei jeder Bewertung nicht neu zu berechnen.
Der Standardwert der pure Eigenschaft ist true .
*ngFor Direktive Die *ngFor wird zur Wiedergabe einer Sammlung verwendet.
trackBy -Option Standardmäßig identifiziert *ngFor identifiziert die Einzigartigkeit der Objekte durch Bezugnahme.
Dies bedeutet, wenn ein Entwickler den Hinweis auf Objekt während der Aktualisierung von Elements inhaltlich durchbricht, behandelt Angular es als Entfernung des alten Objekts und der Zugabe des neuen Objekts. Diese Auswirkungen auf die Zerstörung des alten Dom -Knotens in der Liste und das Hinzufügen eines neuen DOM -Knotens an seiner Stelle.
Der Entwickler kann einen Hinweis für Angular geben, wie die Einzigartigkeit von Objekten Identifizierung: benutzerdefinierte Tracking -Funktion als trackBy -Option für die *ngFor -Anweisung identifiziert werden kann. Die Tracking -Funktion nimmt zwei Argumente an: index und item . Angular verwendet den von der Tracking -Funktion zurückgegebenen Wert, um die Identität der Elemente zu verfolgen. Es ist sehr häufig, die ID des jeweiligen Datensatzes als eindeutigen Schlüssel zu verwenden.
Beispiel
@ Component ( {
selector : 'yt-feed' ,
template : `
<h1>Your video feed</h1>
<yt-player *ngFor="let video of feed; trackBy: trackById" [video]="video"></yt-player>
`
} )
export class YtFeedComponent {
feed = [
{
id : 3849 , // note "id" field, we refer to it in "trackById" function
title : "Angular in 60 minutes" ,
url : "http://youtube.com/ng2-in-60-min" ,
likes : "29345"
} ,
// ...
] ;
trackById ( index , item ) {
return item . id ;
}
} Das Rendern der DOM -Elemente ist normalerweise der teuerste Betrieb beim Hinzufügen von Elementen zur Benutzeroberfläche. Die Hauptarbeit wird normalerweise durch Einfügen des Elements in das DOM und die Anwendung der Stile verursacht. Wenn *ngFor viele Elemente rendert, können Browser (insbesondere ältere) langsamer werden und benötigen mehr Zeit, um die Renderung aller Elemente zu beenden. Dies ist nicht spezifisch für Winkel.
Um die Renderzeit zu verkürzen, versuchen Sie Folgendes aus:
*ngFor Abschnitt Ihrer Vorlage gerendert sind. Normalerweise entstehen unnötige/nicht verwendete DOM -Elemente, wenn die Vorlage immer wieder erweitert wird. Das Überdenken seiner Struktur macht die Dinge wahrscheinlich viel einfacher.ng-container wenn möglichRessourcen
*ngForAngular führt nach jedem Änderungserkennungszyklus Vorlagenausdrücke aus. Änderungserkennungszyklen werden durch viele asynchrone Aktivitäten wie Versprechensauflösungen, HTTP -Ergebnisse, Timer -Ereignisse, Tastaturen und Mausbewegungen ausgelöst.
Ausdrücke sollten schnell fertig werden oder die Benutzererfahrung kann sich anziehen, insbesondere auf langsameren Geräten. Berücksichtigen Sie das Zwischenspeicherwerte, wenn ihre Berechnung teuer ist.
Ressourcen
Die Liste der Praktiken wird sich im Laufe der Zeit mit neuen/aktualisierten Praktiken dynamisch entwickeln. Falls Sie etwas fehlen oder der Meinung sind, dass eine der Praktiken verbessert werden kann, zögern Sie nicht, ein Problem und/oder eine PR zu entlassen. Weitere Informationen finden Sie im folgenden Abschnitt "beitragen".
Falls Sie etwas fehlen, Unvollständiges oder falsches bemerken, wird eine Pull -Anfrage sehr geschätzt. Zur Diskussion von Praktiken, die nicht im Dokument enthalten sind, öffnen Sie bitte ein Problem.
MIT