├── .gitignore
├── LICENSE
├── README.es-ES.md
├── README.ja-JP.md
├── README.md
├── README.pt-BR.md
├── README.ru-RU.md
├── README.zh-CN.md
└── assets
├── flash.jpg
└── flash.png
/.gitignore:
--------------------------------------------------------------------------------
1 | ### macOS ###
2 | *.DS_Store
3 | .AppleDouble
4 | .LSOverride
5 |
6 |
7 | # Thumbnails
8 | ._*
9 |
10 | # Files that might appear in the root of a volume
11 | .DocumentRevisions-V100
12 | .fseventsd
13 | .Spotlight-V100
14 | .TemporaryItems
15 | .Trashes
16 | .VolumeIcon.icns
17 | .com.apple.timemachine.donotpresent
18 |
19 | # Directories potentially created on remote AFP share
20 | .AppleDB
21 | .AppleDesktop
22 | Network Trash Folder
23 | Temporary Items
24 | .apdisk
25 |
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Minko Gechev
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.es-ES.md:
--------------------------------------------------------------------------------
1 | # Angular Performance Checklist
2 |
3 |
4 |
5 | - [中文版](./README.zh-CN.md)
6 | - [Русский](./README.ru-RU.md)
7 | - [Português](./readme-pt-BR.md)
8 | - [Español](./readme-es-ES.md)
9 |
10 | ## Introducción
11 |
12 | Este documento contiene una lista de prácticas las cuales nos ayudarán a aumentar el rendimiento de nuestras aplicaciones Angular. "Angular performance Checklist" cubre diferentes temas - desde server-side pre-rendering y bundle de nuestras aplicaciones, hasta rendimiento en ejecución y optimización de la detección del cambio realizada por el framework.
13 |
14 | El documento se divide en dos secciones principales:
15 |
16 | - Rendimiento de la red - lista de prácticas que mejorarán principalmente el tiempo de carga de nuestra aplicación. Incluyen métodos de latencia y reducción de ancho de banda.
17 | - Rendimiento en ejecución - Prácticas que mejoran el rendimiento en ejecución de nuestra aplicación. Incluyen principalmente optimizaciones en la detección del cambio y relacionadas con el renderizado.
18 |
19 | Algunas prácticas impactan en ambas categorías por lo que podría haber un ligero punto de encuentro, sin embargo, la diferencia en los casos de uso y las implicaciones serán explícitamente mencionadas.
20 |
21 | La mayoría de lassubsecciones enumeran herramientas, relacionadas a la práctica específica, que nos harán más eficiente la automatización de nuestro entorno de desarrollo.
22 |
23 | Tenga en cuenta que la mayoría de prácticas son válidas para HTTP/1.1 y HTTP/2. Se mencionarán las prácticas que sean la excepción especificando a qué versión del protocolo podrían aplicarse.
24 |
25 | ## Índice
26 |
27 | - [Angular Performance Checklist](#angular-2-performance-checklist)
28 | - [Introducción](#Introducción)
29 | - [Índice](#Índice)
30 | - [Rendimiento de red](#rendimiento-de-red)
31 | - [Bundling](#bundling)
32 | - [Minification and Dead code elimination](#minificación-y-eliminación-de-código-no-utilizado-Dead-code)
33 | - [Remove template whitespace](#eliminar-espacios-en-blanco-de-las-plantillas)
34 | - [Tree-shaking](#tree-shaking)
35 | - [Tree-shakeable providers](#tree-shakeable-providers)
36 | - [Ahead-of-Time (AoT) Compilation](#compilación-Ahead-of-Time-AoT)
37 | - [Compression](#compresión)
38 | - [Pre-fetching Resources](#precarga-de-recursos-Pre-fetching)
39 | - [Lazy-Loading of Resources](#carga-diferida-de-recursos-Lazy-load)
40 | - [Don't lazy-load default route](#no-cargar-de-forma-diferida-la-ruta-por-defecto)
41 | - [Caching](#caché)
42 | - [Use Application Shell](#shell-de-la-aplicación)
43 | - [Use Service Workers](#service-workers)
44 | - [Optimizaciones en ejecución](#optimizaciones-en-ejecución)
45 | - [Utilizar enableProdMode](#enableProdMode)
46 | - [Compilación Ahead-of-Time](#compilación-ahead-of-time)
47 | - [Web Workers](#web-workers)
48 | - [Server-Side Rendering](#server-side-rendering)
49 | - [Detección del cambio](#detección-del-cambio)
50 | - [ChangeDetectionStrategy.OnPush](#changedetectionstrategyonpush)
51 | - [Desacoplando el detector de cambios](#desacoplando-el-detector-de-cambios)
52 | - [Ejecución fuera de Angular](#ejecución-fuera-de-Angular)
53 | - [Pipes puros](#pipes-puros)
54 | - [Directiva *ngFor](#directiva-ngFor)
55 | - [Utilizar opción trackBy](#utilizar-opción-trackBy)
56 | - [Minimizar elementos del DOM](#minimizar-elementos-del-DOM)
57 | - [Optimizar expresiones en plantilla](#optimizar-expresiones-en-plantilla-Template-expressions)
58 | - [Conclusión](#conclusión)
59 | - [Contribuyendo](#contribuyendo)
60 |
61 | ## Rendimiento de red
62 |
63 | Algunas de las herramientas en esta sección están aún en desarrollo y sujetas a cambios. El equipo de "Angular core" está trabajando en automatizar el proceso de compilación de nuestras aplicaciones tanto como sea posible para que muchas cosas ocurran de forma transparente.
64 |
65 | ### Bundling
66 |
67 | Bundling o empaquetado es una práctica estándar que permite reducir el número de solicitudes que el navegador necesita para entregar la aplicación solicitada por el usuario. En esencia, el "bundler" recibe una lista de puntos de entrada y los junta en uno o más bundles. De esta manera, el navegador puede obtener la aplicación completa realizando solo unas pocas solicitudes, en vez de ir solicitando de forma separada cada fichero.
68 |
69 | Así como crezca tu aplicación, empaquetar todo en un único fichero puede ser contraproducente. Vea las técnicas de división de código usando Webpack.
70 |
71 | **Las solicitudes HTTP adicionales no serán una preocupación con HTTP/2 gracias a la característica [server push](https://http2.github.io/faq/#whats-the-benefit-of-server-push)**
72 |
73 | **Herramientas**
74 |
75 | Las herramientas que nos permiten empaquetar nuestras aplicaciones de forma eficiente son:
76 |
77 | - [Webpack](https://webpack.js.org) - Ofrece un bundle eficiente mediante la realización de [tree-shaking](#tree-shaking).
78 | - [Webpack Code Splitting](https://webpack.js.org/guides/code-splitting/) - Técnicas para dividir el código.
79 | - [Webpack & http2](https://medium.com/webpack/webpack-http-2-7083ec3f3ce6#.46idrz8kb) - Necesario para dividir el código usando HTTP/2.
80 | - [Rollup](https://github.com/rollup/rollup) - Ofrece un bundle eficiente haciendo uso de "tree-shaking", aprovechando la naturaleza estática de los módulos ES2015.
81 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - realiza un montón de optimizaciones y proporciona soporte para el bundle. Originalmente escrito en Java, desde hace poco también tiene una [version JavaScript](https://www.npmjs.com/package/google-closure-compiler) la cual puede encontrarse [aquí](https://www.npmjs.com/package/google-closure-compiler).
82 | - [SystemJS Builder](https://github.com/systemjs/builder) - Proporciona la generación de un único archivo para módulos mixtos de inyección de dependencias de SystemJS.
83 | - [Browserify](http://browserify.org/).
84 | - [ngx-build-modern](https://github.com/manfredsteyer/ngx-build-plus/tree/master/ngx-build-modern) - plugin para Angular-CLI el cual genera paquetes de dos maneras:
85 | 1. Para navegadores modernos con módulos ES2015 y polyfills específicos ofreciendo un paquete de menor tamaño.
86 | 2. Añade ficheros polyfill para navegadores más antiguos (tal y como es por defecto).
87 |
88 | **Recursos**
89 |
90 | - ["Compilando una aplicación angular para producción"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
91 | - ["Aplicaciones 2.5 veces más pequeñas usando Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
92 |
93 | ### Minificación y eliminación de código no utilizado (Dead code)
94 |
95 | Estas prácticas nos permiten minimizar el consumo de ancho de banda reduciendo la carga (payload) de nuestra aplicación
96 |
97 | **Herramientas**
98 |
99 | - [Uglify](https://github.com/mishoo/UglifyJS) - Realiza la minificación como el nombre de las variables, elimina comentarios y espacios en blanco, elimina código no utilizado (dead code), etc. Escrito completamente en JavaScript, tiene varios plugins para todos los task runners más populares.
100 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - Realiza la minificación de forma similar a uglify. En modo avanzado, transforma el AST (Sintaxis abstracta del árbol) de nuestro programa de forma agresiva para poder realizar optimizaciones aún más sofisticadas. También tiene una [versión JavaScript](https://www.npmjs.com/package/google-closure-compiler) que puedes [encontrar aquí](https://www.npmjs.com/package/google-closure-compiler). GCC también soporta *la sintaxis de la mayoría de módulos ES2015* por lo que puede [implementar tree-shaking](#tree-shaking).
101 |
102 | **Recursos**
103 |
104 | - ["Compilando una aplicación angular para producción"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
105 | - ["Aplicaciones 2.5 veces más pequeñas usando Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
106 |
107 | ### Eliminar espacios en blanco de las plantillas
108 |
109 | Aunque no veamos el carácter de espacio en blanco (Un caracter que coincida con la expresión regular `\s`) todavía está representado por bytes que son transferidos a través de la red. Si reducimos los espacios en blanco de nuestras plantillas al mínimo podremos reducir el tamaño de nuestro código AoT aún más.
110 |
111 | Afortunadamente, no tenemos que hacer esto manualmente. La Interface `ComponentMetadata` ofrece la propiedad `preserveWhitespaces` la cual por defecto tiene valor `false`, porque eliminando los espacios en blanco siempre puede modificar la estructura del DOM. En el caso que cambiemos la propiedad a `true` Angular eliminará los espacios en blancos innecesarios, disminuyendo el tamaño final del bundle.
112 |
113 | - [preserveWhitespaces en la documentación de Angular](https://angular.io/api/core/Component#preserveWhitespaces)
114 |
115 | ### Tree-shaking
116 |
117 | Para la versión final de nuestra aplicación normalmente no usaremos el código entero que ofrece Angular y/u otras librerías de terceros, o incluso el que hemos escrito nosotros. Gracias a la naturaleza estática de los módulos ES2015, tenemos la posibilidad de deshacernos del código que no usamos en nuestras aplicaciones.
118 |
119 | **Ejemplo**
120 |
121 | ```javascript
122 | // foo.js
123 | export foo = () => 'foo';
124 | export bar = () => 'bar';
125 |
126 | // app.js
127 | import { foo } from './foo';
128 | console.log(foo());
129 | ```
130 | Una vez aplicado "tree-shake" en el bundle `app.js` obtendremos:
131 |
132 | ```javascript
133 | let foo = () => 'foo';
134 | console.log(foo());
135 | ```
136 |
137 | Esto significa que la exportación `bar` no utilizada no estará incluida en el bundle final.
138 |
139 | **Herramientas**
140 |
141 | - [Webpack](https://webpack.js.org) - Proporciona un bundling eficiente mediante el [tree-shaking](#tree-shaking). Una vez la aplicación ha sido empaquetada, ésta no exporta el código no utilizado con lo que podremos considerar de manera segura la eliminación de código no utilizado con Uglify.
142 | - [Rollup](https://github.com/rollup/rollup) - Ofrece un bundle eficiente haciendo uso de "tree-shaking", aprovechando la naturaleza estática de los módulos ES2015.
143 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - realiza un montón de optimizaciones y proporciona soporte para el bundle. Originalmente escrito en Java, desde hace poco también tiene una [version JavaScript](https://www.npmjs.com/package/google-closure-compiler) la cual puede encontrarse [aquí](https://www.npmjs.com/package/google-closure-compiler).
144 |
145 | *Nota:* GCC todavía no sporta `export *` , el cual es fundamental para la construcción de aplicaciones angular por el amplío uso del patrón "barrel".
146 |
147 | **Recursos**
148 |
149 | - ["Compilando una aplicación angular para producción"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
150 | - ["Aplicaciones 2.5 veces más pequeñas usando Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
151 | - ["Uso de operadores encadenados (pipeable) en RxJS"](https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md)
152 |
153 | ### Tree-Shakeable Providers
154 |
155 | Desde el lanzamiento de la versión 6 de Angular, el equipo de angular ofrece una nueva característica la cual nos permite que los servicios sean "tree-shakeable", esto significa que los servicios no estarán incluidos en la versión final de nuestro bundle a no ser que estén siendo utilizados por otros servicios o componentes. Esto puede ayudar a reducir el tamaño del bundle eliminando el código no utilizado en el bundle.
156 |
157 | Puedes hacer tus servicios "tree-shakeables" utilizando el atributo `provideIn` para definir donde el servicio debe ser inicializado utilizando el decorador `@Injectable()`. De esta forma debes eliminar los servicios del atributo `providers` de la declaración de tu `ngModule` de la siguiente forma:
158 |
159 | Antes:
160 |
161 | ```ts
162 | // app.module.ts
163 | import { NgModule } from '@angular/core'
164 | import { AppRoutingModule } from './app-routing.module'
165 | import { AppComponent } from './app.component'
166 | import { environment } from '../environments/environment'
167 | import { MyService } from './app.service'
168 |
169 | @NgModule({
170 | declarations: [
171 | AppComponent
172 | ],
173 | imports: [
174 | ...
175 | ],
176 | providers: [MyService],
177 | bootstrap: [AppComponent]
178 | })
179 | export class AppModule { }
180 | ```
181 |
182 | ```ts
183 | // my-service.service.ts
184 | import { Injectable } from '@angular/core'
185 |
186 | @Injectable()
187 | export class MyService { }
188 | ```
189 |
190 | Después:
191 |
192 | ```ts
193 | // app.module.ts
194 | import { NgModule } from '@angular/core'
195 | import { AppRoutingModule } from './app-routing.module'
196 | import { AppComponent } from './app.component'
197 | import { environment } from '../environments/environment'
198 |
199 | @NgModule({
200 | declarations: [
201 | AppComponent
202 | ],
203 | imports: [
204 | ...
205 | ],
206 | providers: [],
207 | bootstrap: [AppComponent]
208 | })
209 | export class AppModule { }
210 | ```
211 |
212 | ```ts
213 | // my-service.service.ts
214 | import { Injectable } from '@angular/core'
215 |
216 | @Injectable({
217 | providedIn: 'root'
218 | })
219 | export class MyService { }
220 | ```
221 |
222 | Si `MyService` no está incluido en ningún componente/servicio, entonces no estará incluido en el bundle.
223 |
224 | **Recursos**
225 |
226 | - [Angular Providers](https://angular.io/guide/providers)
227 |
228 | ### Compilación Ahead-of-Time (AoT)
229 |
230 | El desafío para las herramientas existentes (como GCC, Rollup, etc.) son las plantillas tipo-HTML de los componentes de Angular, las cuales no pueden ser analizadas con sus capacidades. Esto hace que el soporte para "tree-shaking" sea menos eficiente porque éstas no están seguras de qué directivas son utilizadas dentro de sus plantillas. El compilador AoT transpila las plantillas de Angular para JavaScript o TypeScript con el sistema de importación de los módulos ES2015. De esta manera somos capaces de aplicar "tree-shake" eficientemente durante el proceso de compilación y eliminar todas las directivas no utilizadas definidas por Angular, librerías de terceros o por nosotros mismos.
231 |
232 | **Recursos**
233 |
234 | - ["Compilación Ahead-of-Time en Angular"](http://blog.mgechev.com/2016/08/14/ahead-of-time-compilation-angular-offline-precompilation/)
235 |
236 | ### Compresión
237 |
238 | Comprimir las respuestas del servidor es una práctica estándar para reducir el uso de ancho de banda. Al especificar el valor de la cabecera `Accept-Encoding`, el navegador sugiere al servidor qué algoritmos de compresión están disponibles en la máquina del cliente. Por otro lado, el servidor establece el valor de la cabecera `Content-Encoding` de la respuesta para indicar al navegador qué algoritmo se ha elegido para comprimir la respuesta.
239 |
240 | **Herramientas**
241 |
242 | Las herramientas aquí no son específicas de Angular y dependen completamente del servidor web / de aplicaciones que estamos usando. Algoritmos típicos de compresión son:
243 |
244 |
245 | - deflate - un algoritmo de compresión de datos y un formato de archivo asociado que utiliza una combinación del algoritmo LZ77 y la codificación de Huffman.
246 | - [brotli](https://github.com/google/brotli) - un algoritmo de compresión sin pérdida de propósito genérico que comprime datos usando una combinación de una variante moderna del algoritmo LZ77, codificación de Huffman y modelado de contexto de segundo orden, con una relación de compresión comparable a la de los mejores métodos de compresión de propósito general actualmente disponibles. Es similar en velocidad a deflate pero ofrece una compresión más densa.
247 |
248 | **Recursos**
249 |
250 | - ["Mejor compresión que Gzip con Brotli"](https://hacks.mozilla.org/2015/11/better-than-gzip-compression-with-brotli/)
251 | - ["Aplicaciones 2.5 veces más pequeñas usando Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
252 |
253 | ### Precarga de recursos (Pre-fetching)
254 |
255 | La precarga de recursos es una gran manera de mejorar la experiencia de usuario. Podemos precargar assets (imágenes, estilos, modulos que serán cargados de [forma diferida](#lazy-loading-of-resources) (Lazy Load), etc.) o datos. Hay diferentes estrategias de precarga pero la mayoría de ellas dependen de cada aplicación.
256 |
257 | ### Carga diferida de recursos (Lazy load)
258 |
259 | En caso de que la aplicación tenga una cantidad de código enorme con cientos de dependencias, es posible que las prácticas enumeradas anteriormente no nos ayuden a reducir el bundle a un tamaño razonable (razonable debe ser entre 100K o 2M, de nuevo, depende completamente de los requisitos de negocio.)
260 |
261 | En tales casos, una buena solución podría ser cargar algunos de los módulos de la aplicación de forma diferida (lazy). Por ejemplo, supongamos que estamos construyendo un sistema de comercio electrónico. En este caso, podríamos querer cargar el panel de administración independientemente de la interfaz de usuario orientada al usuario. Una vez que el administrador tenga que agregar un nuevo producto, querremos proporcionar la interfaz requerida para eso. Esto podría ser solo la "Página de añadir producto" o el panel de administración completo, dependiendo de nuestro caso de uso / requisitos de negocio.
262 |
263 | **Herramientas**
264 |
265 | - [Webpack](https://github.com/webpack/webpack) - permite la carga de módulos de forma asíncrona.
266 | - [ngx-quicklink](https://github.com/mgechev/ngx-quicklink) - router preloading strategy which automatically downloads the lazy-loaded modules associated with all the visible links on the screen
267 | - [ngx-quicklink](https://github.com/mgechev/ngx-quicklink) - Estrategía de precarga basada en el enrutado la cual descarga de forma diferida (lazy load) los módulos asociados a los links visibles en pantalla.
268 |
269 | ### No cargar de forma diferida la ruta por defecto
270 |
271 | Supongamos que tenemos la siguiente configuración de enrutado:
272 |
273 | ```ts
274 | // Bad practice
275 | const routes: Routes = [
276 | { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
277 | { path: 'dashboard', loadChildren: './dashboard.module#DashboardModule' },
278 | { path: 'heroes', loadChildren: './heroes.module#HeroesModule' }
279 | ];
280 | ```
281 |
282 | La primera vez que el usuario abre la aplicación usando la url: https://example.com/ será redirigido hacia `/dashboard` la cual disparará la carga diferida (lazy-route) con la ruta `dashboard`. Para que Angular renderice el componente del módulo, deberá descargar el fichero `dashboard.module` y todas sus dependencias. Después, el archivo será parseado por la JavaScript VM y será evaluado.
283 |
284 | Disparar solicitudes HTTP extra y ejecutar cálculos innecesarios durante la carga inicial es una mala práctica y ralentizará el renderizado de la página inicial. Considere declarar la ruta de la página predeterminada como no diferida (non-lazy).
285 |
286 | ### Caché
287 |
288 | El almacenamiento en caché es otra práctica común que pretende acelerar nuestra aplicación aprovechando la heurística de que si se solicitó un recurso recientemente, podría solicitarse nuevamente en un futuro próximo.
289 |
290 | Para almacenar datos en caché normalmente usamos un mecanismo de almacenamiento en caché personalizado. Para el almacenamiento en caché de assets, podemos utilizar el almacenamiento en caché estándar del navegador o el uso de Service Workers con el [API CacheStorage](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
291 |
292 | ### Shell de la Aplicación
293 |
294 | Para hacer más rápido el rendimiento percibido de su aplicación, usar una [Application Shell](https://developers.google.com/web/updates/2015/11/app-shell).
295 |
296 | El shell de la aplicación es la interfaz de usuario mínima que mostramos a los usuarios para indicarles que la aplicación se entregará pronto. Para la generación dinámica del shell de la aplicación puedes utilizar Angular Universal con directivas personalizadas que de forma condicional mostrarán elementos dependiendo de la plataforma de renderizado (por ejemplo, ocultar todo excepto el shell cuando usemos `platform-server`).
297 |
298 | **Herramientas**
299 |
300 | - [Angular Service Worker](https://angular.io/guide/service-worker-intro) - Tiene como objetivo automatizar el proceso de gestión de los Service Workers. También contiene Service Workers para el almacenamiento en caché de assets, y uno [para generar el shell de la aplicación](https://developers.google.com/web/updates/2015/11/app-shell?hl=es).
301 | - [Angular Universal](https://github.com/angular/angular/tree/master/packages/platform-server) - Soporte universal (isomórfico) para Angular.
302 |
303 | **Recursos**
304 |
305 | - ["Instant Loading Web Apps with an Application Shell Architecture"](https://developers.google.com/web/updates/2015/11/app-shell)
306 |
307 | ### Service Workers
308 |
309 | Podemos pensar de los Service Worker como un proxy HTTP que está en el navegador. Todas las peticiones enviadas desde el cliente son interceptadas primero por el Service Worker el cual puede procesarlas o enviarlas a través de la red.
310 |
311 | Puedes añadir un Service Worker a tu proyecto Angular ejecutando ``` ng add @angular/pwa ```
312 |
313 | **Herramientas**
314 |
315 | - [Angular Service Worker](https://angular.io/guide/service-worker-intro) - Tiene como objetivo automatizar el proceso de gestión de los Service Workers. También contiene Service Workers para el almacenamiento en caché de assets, y uno [para generar el shell de la aplicación](https://developers.google.com/web/updates/2015/11/app-shell?hl=es).
316 | - [Offline Plugin for Webpack](https://github.com/NekR/offline-plugin) - Plugin para Webpack que añade soporte para el Service Worker con soporte alternativo para AppCache.
317 |
318 | **Recursos**
319 |
320 | - ["The offline cookbook"](https://jakearchibald.com/2014/offline-cookbook/)
321 | - ["Empezando con service workers"](https://angular.io/guide/service-worker-getting-started)
322 |
323 | ## Optimizaciones en ejecución
324 |
325 | Esta sección incluye prácticas que podrán ser aplicadas con el fin de proporcionar una experiencia de usuario más suave con 60fps (Frames por segundo).
326 |
327 | ### Utilizar `enableProdMode`
328 |
329 | En el ambiente de desarrollo, Angular realiza algunas comprobaciones adicionales para verificar que la detección del cambio no produce ninguna diferencia para alguno de los bindings. De esta manera el framework garantiza que el flujo unidireccional de los datos ha sido seguido.
330 |
331 | Para deshabilitar estos cambios para producción no olvide habilitar `enableProdMode`:
332 |
333 | ```typescript
334 | import { enableProdMode } from '@angular/core';
335 |
336 | if (ENV === 'production') {
337 | enableProdMode();
338 | }
339 | ```
340 |
341 | ### Compilación Ahead-of-Time
342 |
343 | Aot puede ser beneficioso no solo para asegurarnos bundles más eficientes aplicando "tree-shaking", sino también para mejorar el rendimiento en tiempo de ejecución de nuestras aplicaciones. La alternativa de Aot es la compilación Just-in-Time (JiT) que se realiza en tiempo de ejecución, por lo tanto, podemos reducir la cantidad de cálculos necesarios para la representación de nuestra aplicación al realizar la compilación como parte de nuestro proceso de construcción.
344 |
345 | **Herramientas**
346 |
347 | - [angular2-seed](https://github.com/mgechev/angular2-seed) - Proyecto inicial que incluye soporte para la compilación AoT.
348 | - [angular-cli](https://cli.angular.io) Utilizando `ng serve --prod`
349 |
350 | **Recursos**
351 |
352 | - ["Compilación Ahead-of-Time en Angular"](http://blog.mgechev.com/2016/08/14/ahead-of-time-compilation-angular-offline-precompilation/)
353 |
354 | ### Web Workers
355 |
356 | El problema habitual de las aplicaciones Single-Page-Applicattion (SPA) es que nuestro código generalmente se ejecuta en un solo hilo. Esto significa que si queremos lograr una experiencia de usuario fluida con 60 fps tenemos **a lo sumo 16ms** para la ejecución entre los frames individuales que se están procesando, de lo contrario, se reducirán a la mitad.
357 |
358 | En aplicaciones complejas con un gran arbolado de componentes, donde la detección de cambios necesita realizar millones de verificaciones por segundo, no será difícil comenzar a eliminar frames. Gracias al agnosticismo de la plataforma de Angular y su desacoplamiento de la arquitectura DOM, es posible ejecutar toda nuestra aplicación (incluida la detección de cambios) en un Service Worker y dejar el hilo principal de la UI responsable solo de la renderización
359 |
360 | **Herramientas**
361 |
362 | - El módulo que nos permite ejecutar nuestra aplicación en un Web Worker es apoyado por el equipo do Angular Core. Ejemplos de cómo puede ser utilizado, pueden [encontrarse aquí](https://github.com/angular/angular/tree/master/modules/playground/src/web_workers).
363 | - [Webpack Web Worker Loader](https://github.com/webpack/worker-loader) - A Web Worker Loader for webpack.
364 |
365 | **Recursos**
366 |
367 | - ["Utilizando Web Workers para aplicaciones más responsive"](https://www.youtube.com/watch?v=Kz_zKXiNGSE)
368 |
369 | ### Server-Side Rendering
370 |
371 | Un gran problema para las tracidionales SPA es que no pueden ser renderizadas hasta que esté disponible todo el código JavaScript solicitado. Esto lleva a dos grandes problemas:
372 |
373 | - No todos los motores de búsqueda ejecutan el JavaScript asociado a la página, por lo que no pueden indexar correctamente el contenido de las aplicaciones dinámicas.
374 | - Mala experiencia del usuario, ya que el usuario no verá más que una pantalla en blanco / cargando hasta que se descargue, analice y ejecute el JavaScript asociado con la página.
375 |
376 | Server-side rendering soluciona estos problemas pre-renderizando la página requerida en el servidor y proporcionando el marcado de la página durante la carga incial de la página.
377 |
378 | **Herramientas**
379 |
380 | - [Angular Universal](https://github.com/angular/angular/tree/master/packages/platform-server) - Soporte universal (isomórfico) para Angular.
381 | - [Preboot](https://github.com/angular/preboot) - Librería para ayudar a gestionar la transición del estado (por ej. eventos, focus, datos) desde un servidor-generado web view a un cliente-generado web view.
382 |
383 | **Recursos**
384 |
385 | - ["Angular Server Rendering"](https://www.youtube.com/watch?v=0wvZ7gakqV4)
386 | - ["Angular Universal Patterns"](https://www.youtube.com/watch?v=TCj_oC3m6_U)
387 |
388 | ### Detección del cambio
389 |
390 | En cada evento asíncrono, Angular realiza la detección de cambios en todo el árbol de componentes. Aunque el código que detecta cambios está optimizado por [inline-caching](http://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html), Esto puede ser un cálculo pesado en aplicaciones complejas. Una forma de mejorar el rendimiento de la detección de cambios es no realizarla en subárboles que no deben modificarse en función de las acciones recientes.
391 |
392 | #### `ChangeDetectionStrategy.OnPush`
393 |
394 | La estrategia de detección de cambios `OnPush` nos permite deshabilitar el mecanismo de detección de cambios para subárboles del árbol de componentes. Al establecer la estrategia de detección de cambios en cualquier componente con el valor `ChangeDetectionStrategy.OnPush`, hará que la detección de cambios se realice **solo** cuando el componente haya recibido diferentes entradas. Angular considerará las entradas como diferentes cuando las compare con las entradas anteriores por referencia, y el resultado de la verificación de referencia es `false`. En combinación con [estructuras de datos inmutables](https://facebook.github.io/immutable-js/) `OnPush` puede traer grandes implicaciones de rendimiento para tales componentes "puros".
395 |
396 | **Recursos**
397 |
398 | - ["Detección del cambio en Angular"](https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c)
399 | - ["Todo lo que necesitas saber sobre la detección del cambio en Angular"](https://blog.angularindepth.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f)
400 |
401 | #### Desacoplando el detector de cambios
402 |
403 | Otra forma de implementar un mecanismo de detección de cambios personalizado es mediante la separación (detach) y la reinserción (reattach) del detector de cambios (CD) para un componente determinado. Una vez que 'separemos' (`detach`) el CD Angular no realizará la verificación de todo el subárbol de componentes.
404 |
405 | Esta práctica se usa normalmente cuando las acciones del usuario o las interacciones con servicios externos activan la detección de cambios con más frecuencia de la necesaria. En tales casos, es posible que desee considerar separar el detector de cambios y volver a instalarlo solo cuando se requiere la detección de cambios.
406 |
407 | #### Ejecución fuera de Angular
408 |
409 | El mecanismo de detección de cambios de Angular se dispara gracias a [zone.js](https://github.com/angular/zone.js). Zone.js monkey parchea todas las API asíncronas en el navegador y activa la detección de cambios al final de la ejecución de cualquier devolución de llamada asíncrona. En **casos raros** podemos querer que un código dado se ejecute fuera del contexto Angular Zone y, por lo tanto, sin ejecutar el mecanismo de detección de cambios. En tales casos, podemos usar el método `runOutsideAngular` de la instancia de 'NgZone`.
410 |
411 | **Example**
412 |
413 | En el ejemplo de abajo, puede ver un componente que utiliza esta práctica. Cuando el método `_incrementPoints` es llamado el componente comenzará a incrementar la propiedad `_points` cada 10ms (por defecto). Al incrementar tendremos la ilusión de una animación. Ya que en este caso no queremos activar el mecanismo de detección de cambios para todo el árbol de componentes, cada 10ms, podemos ejecutar `_incrementPoints` fuera del contexto de Angular´s Zone y actualizar el DOM manualmente (ver método set de `points`).
414 |
415 | ```ts
416 | @Component({
417 | template: ''
418 | })
419 | class PointAnimationComponent {
420 |
421 | @Input() duration = 1000;
422 | @Input() stepDuration = 10;
423 | @ViewChild('label') label: ElementRef;
424 |
425 | @Input() set points(val: number) {
426 | this._points = val;
427 | if (this.label) {
428 | this.label.nativeElement.innerText = this._pipe.transform(this.points, '1.0-0');
429 | }
430 | }
431 | get points() {
432 | return this._points;
433 | }
434 |
435 | private _incrementInterval: any;
436 | private _points: number = 0;
437 |
438 | constructor(private _zone: NgZone, private _pipe: DecimalPipe) {}
439 |
440 | ngOnChanges(changes: any) {
441 | const change = changes.points;
442 | if (!change) {
443 | return;
444 | }
445 | if (typeof change.previousValue !== 'number') {
446 | this.points = change.currentValue;
447 | } else {
448 | this.points = change.previousValue;
449 | this._ngZone.runOutsideAngular(() => {
450 | this._incrementPoints(change.currentValue);
451 | });
452 | }
453 | }
454 |
455 | private _incrementPoints(newVal: number) {
456 | const diff = newVal - this.points;
457 | const step = this.stepDuration * (diff / this.duration);
458 | const initialPoints = this.points;
459 | this._incrementInterval = setInterval(() => {
460 | let nextPoints = Math.ceil(initialPoints + diff);
461 | if (this.points >= nextPoints) {
462 | this.points = initialPoints + diff;
463 | clearInterval(this._incrementInterval);
464 | } else {
465 | this.points += step;
466 | }
467 | }, this.stepDuration);
468 | }
469 | }
470 | ```
471 |
472 | **Warning**: Use esta práctica **con mucho cuidado solo cuando esté seguro de lo que está haciendo** porque, si no se usa correctamente, puede llevar a un estado inconsistente del DOM. También tenga en cuenta que el código anterior no se ejecutará en WebWorkers. Para que sea compatible con WebWorker, debe establecer el valor de la etiqueta `label` utilizando el renderizador de Angular.
473 |
474 | ### Pipes puros
475 |
476 | El decorador `@Pipe` acepta como argumento un objeto utilizando el siguiente formato:
477 |
478 | ```typescript
479 | interface PipeMetadata {
480 | name: string;
481 | pure: boolean;
482 | }
483 | ```
484 |
485 | El flag "pure" indica que el Pipe no depende de ningún estado global y no producirá efectos colaterales (side-effects). Esto significa que el Pipe devolverá el mismo resultado cuando sea llamada con la misma entrada. De esta manera, Angular puede almacenar en caché las salidas de todos los parámetros de entrada con los que se ha invocado el Pipe y reutilizarlos para no tener que volver a calcularlos en cada evaluación.
486 |
487 | El valor por defecto de la propiedad `pure` es `true`.
488 |
489 | ### Directiva `*ngFor`
490 |
491 | La directiva `*ngFor` es utilizada para renderizar una colección.
492 |
493 | #### Utilizar opción `trackBy`
494 |
495 | Por defecto `*ngFor` identifica objetos únicos por referencia.
496 |
497 | Lo que significa que cuando el desarrollador rompe la referencia al objeto durante la actualización del contenido del elemento, Angular lo trata como la eliminación del objeto anterior y la agregación del nuevo objeto. Esto afecta a la destrucción del antiguo nodo DOM en la lista y la agregación de un nuevo nodo DOM en su lugar.
498 |
499 | El desarrollador puede ofrecer una pista a Angular sobre cómo identificar de forma única el objeto: función de seguimiento personalizado como la opción `trackBy` para la directiva` * ngFor`. La función trackBy recibe dos argumentos: `índice` (index) y `elemento` (item). Angular utiliza el valor devuelto por la función para llevar un seguimiento
500 | Tracking function takes two arguments: `index` and `item`. Angular usa el valor devuelto por la función de seguimiento para rastrear la identidad de los elementos. Es muy común usar la ID del registro particular como clave única.
501 |
502 | **Example**
503 |
504 | ```typescript
505 | @Component({
506 | selector: 'yt-feed',
507 | template: `
508 |
Your video feed
509 |
510 | `
511 | })
512 | export class YtFeedComponent {
513 | feed = [
514 | {
515 | id: 3849, // note "id" field, we refer to it in "trackById" function
516 | title: "Angular in 60 minutes",
517 | url: "http://youtube.com/ng2-in-60-min",
518 | likes: "29345"
519 | },
520 | // ...
521 | ];
522 |
523 | trackById(index, item) {
524 | return item.id;
525 | }
526 | }
527 | ```
528 |
529 | #### Minimizar elementos del DOM
530 |
531 | La representación de los elementos DOM suele ser la operación más costosa al agregar elementos a la interfaz de usuario. El trabajo principal generalmente se debe al insertar el elemento en el DOM y aplicar los estilos. Si `*ngFor` renderiza muchos elementos, los navegadores (especialmente antiguos) pueden disminuir la velocidad y necesitar más tiempo para finalizar la representación de todos los elementos. Esto no es específico para Angular.
532 |
533 | Para reducir el tiempo de renderizado, prueba lo siguiente:
534 | - Aplicar scroll virtual a través de [CDK](https://material.angular.io/cdk/scrolling/overview) o [ngx-virtual-scroller](https://github.com/rintoj/ngx-virtual-scroller)
535 | - Reducir la cantidad de elementos DOM representados en la sección `* ngFor` de su plantilla. Por lo general, los elementos DOM innecesarios / no utilizados surgen al ampliar la plantilla una y otra vez. Volviendo a pensar su estructura probablemente hará las cosas más fáciles.
536 | - Utilizar [`ng-container`](https://angular.io/guide/structural-directives#ngcontainer) cuando sea posible.
537 |
538 | **Recursos**
539 |
540 | - ["directiva NgFor"](https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html) - official documentation for `*ngFor`
541 | - ["Mejorar rendimiento de Angular utilizando trackBy"](https://netbasal.com/angular-2-improve-performance-with-trackby-cc147b5104e5) - muestra algunos GIFs con esta práctica.
542 | - [Component Dev Kit (CDK) Virtual Scrolling](https://material.angular.io/cdk/scrolling/overview) - API description
543 | - [ngx-virtual-scroller](https://github.com/rintoj/ngx-virtual-scroller) - Muestra una lista "virtual" infinita.
544 |
545 | ### Optimizar expresiones en plantilla (Template expressions)
546 |
547 | Angular ejecuta expresiones de plantilla después de cada ciclo de detección de cambios. Los ciclos de detección de cambios se activan mediante muchas actividades asíncronas, como resoluciones de promesa (promise), resultados http, eventos de temporizador (timer), pulsaciones de teclas y movimientos del ratón.
548 |
549 | Las expresiones deben terminar rápidamente o la experiencia de usuario puede empeorar, especialmente en dispositivos más lentos. Considere cachear los valores cuando su cálculo es costoso.
550 |
551 | **Recursos**
552 | - [quick-execution](https://angular.io/guide/template-syntax#quick-execution) - Documentación oficial para expresiones en plantilla (template expressions)
553 | - [Increasing Performance - more than a pipe dream](https://youtu.be/I6ZvpdRM1eQ) - ng-conf video in youtube. Using pipe instead of function in interpolation expression
554 | - [Increasing Performance - more than a pipe dream](https://youtu.be/I6ZvpdRM1eQ) - Vídeo en youtube de ng-conf. Usando Pipe en vez de funciones de interpolación.
555 |
556 | # Conclusión
557 |
558 | La lista de prácticas evolucionará dinámicamente a lo largo del tiempo con prácticas nuevas / actualizadas. En caso de que note que falta algo o si cree que se puede mejorar cualquiera de las prácticas, no dude en abrir un issue y/o una PR. Para más información, por favor, eche un vistazo a la sección inferior "[Contribuyendo](#contributing)".
559 |
560 | # Contribuyendo
561 |
562 | En caso de que note que falta algo, incompleto o incorrecto, una "pull request" será muy apreciada. Para comentar las prácticas que no están incluidas en el documento, por favor [abra una issue](https://github.com/mgechev/angular2-performance-checklist/issues).
563 |
564 | # License
565 |
566 | MIT
567 |
--------------------------------------------------------------------------------
/README.ja-JP.md:
--------------------------------------------------------------------------------
1 | # Angular パフォーマンスチェックリスト
2 |
3 |
4 |
5 | - [English](./README.md)
6 | - [中文版](./README.zh-CN.md)
7 | - [Русский](./README.ru-RU.md)
8 | - [Português](./README.pt-BR.md)
9 | - [Español](./README.es-ES.md)
10 |
11 | ## 序論
12 |
13 | このドキュメントは、Angularアプリケーションのパフォーマンスを向上させるのに役立つ実践方法の一覧を含みます。
14 | 「Angular パフォーマンスチェックリスト」では、実行時パフォーマンスとフレームワークによって実行される変更検出の最適化のための、サーバーサイドでのプリレンダリングならびにアプリケーションのバンドルについて説明しています。
15 |
16 | この文章は大きく分けて2つの章に分かれています。
17 |
18 | - ネットワークパフォーマンス - 主に私たちのアプリケーションのロード時間を改善するための一覧です。これは待機時間と帯域幅を削減するための方法が含まれています。
19 | - 実行時のパフォーマンス - アプリケーション実行時のパフォーマンスを向上させる一覧です。主に変更の検出とレンダリング関連の最適化が含まれます。
20 |
21 | いくつかの項目は両方に影響するため、多少被っている可能性がありますが、ユースケースの違いと理由については明確に言及します。
22 |
23 | ほとんどの節では、特定の実践方法に関連したツール一覧を書いています、それにより開発フローを自動化し、効率を良くします。
24 |
25 | ほとんどの内容はHTTP/1.1 と HTTP/2 の両方に有効です。
26 | 将来的に適用される可能性のあるプロトコルのバージョンは、節ごとに適宜指定することにより、例外的な実践方法についても別記します。
27 |
28 | ## 目次
29 |
30 | - [Angular パフォーマンスチェックリスト](#angular-2-performance-checklist)
31 | - [序論](#introduction)
32 | - [目次](#table-of-content)
33 | - [ネットワークパフォーマンス](#network-performance)
34 | - [バンドリング](#bundling)
35 | - [ミニファイと不要コードの削除](#minification-and-dead-code-elimination)
36 | - [テンプレート中の空白を削除](#remove-template-whitespace)
37 | - [ツリーシェイキング](#tree-shaking)
38 | - [ツリーシェイキング可能なプロバイダ](#tree-shakeable-providers)
39 | - [Ahead-of-Time (AoT) コンパイル](#ahead-of-time-aot-compilation)
40 | - [圧縮](#compression)
41 | - [リソースの事前読込み](#pre-fetching-resources)
42 | - [リソースの遅延読み込み](#lazy-loading-of-resources)
43 | - [デフォルトのルートを遅延読み込みにしない](#dont-lazy-load-the-default-route)
44 | - [キャッシング](#caching)
45 | - [アプリケーションシェルを使う](#use-application-shell)
46 | - [サービスワーカーを使う](#use-service-workers)
47 | - [実行時の最適化](#runtime-optimizations)
48 | - [`enableProdMode`を使う](#use-enableprodmode)
49 | - [Ahead-of-Time コンパイル](#ahead-of-time-compilation)
50 | - [Webワーカー](#web-workers)
51 | - [サーバサイドレンダリング](#server-side-rendering)
52 | - [変更検出](#change-detection)
53 | - [`ChangeDetectionStrategy.OnPush`](#changedetectionstrategyonpush)
54 | - [Change Detectorの切り離し](#detaching-the-change-detector)
55 | - [Run outside Angular](#run-outside-angular)
56 | - [純粋なpipeを使う](#use-pure-pipes)
57 | - [`*ngFor` ディレクティブ](#ngfor-directive)
58 | - [`trackBy`のオプションを使う](#use-trackby-option)
59 | - [DOM要素を小さくする](#minimize-dom-elements)
60 | - [テンプレート中の式を最適化する](#optimize-template-expressions)
61 | - [終わりに](#conclusion)
62 | - [貢献する](#contributing)
63 |
64 | ## ネットワークパフォーマンス
65 |
66 | この章にあるいくつかのツールはまだ開発中なので、変更される可能性があります。
67 | Angularコアチームは、可能な限りアプリケーションのビルドプロセス自動化に取り組んでいるため、多くのことが透過的に行われています。
68 |
69 | ### バンドリング
70 |
71 | バンドリングは、ユーザーにアプリケーションを配信するために、ブラウザ側が実行するリクエストの数を減らすことを目的とした、標準的な方法です。
72 | 本質的に、バンドラーは入力としてエントリーポイントの一覧を受け取り、1つ以上のバンドルを生成します。
73 | そうすることで、ブラウザが個々のリソースを個別に要求するのではなく、少ないリクエストでアプリケーション全体を取得することを可能にします。
74 |
75 | アプリケーションが大きくなるにつれ、すべてを1つバンドルに纏めていくのは逆効果になる可能性があります。
76 | その場合、Webpackを使ったコード分割を行う必要が出てきます。
77 |
78 | **サーバプッシュによりhttp/2はhttpリクエストを含みません。[server push](https://http2.github.io/faq/#whats-the-benefit-of-server-push) 機能を参照してください**
79 |
80 | **ツール一覧**
81 |
82 | アプリケーションを効率的にバンドルできるツールは次のようなものがあります:
83 |
84 | - [Webpack](https://webpack.js.org) - [ツリーシェイキング](#tree-shaking)を行うことによって効率的なバンドリングを提供します。
85 | - [Webpack Code Splitting](https://webpack.js.org/guides/code-splitting/) - コード分割のためのテクニック。
86 | - [Webpack & http2](https://medium.com/webpack/webpack-http-2-7083ec3f3ce6#.46idrz8kb) - http2を使った分割する時に必要です。
87 | - [Rollup](https://github.com/rollup/rollup) - ES2015モジュールの静的な性質を利用し、効率的なツリーシェイキングによるバンドルを提供します。
88 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - 十分な最適化と、バンドルのサポートを提供します。
89 | 元となるJavaで書かれたものがあり、[ここ](https://www.npmjs.com/package/google-closure-compiler)には最近[JavaScriptで書かれたバージョン](https://www.npmjs.com/package/google-closure-compiler)もあります。
90 | - [SystemJS Builder](https://github.com/systemjs/builder) - 依存関係の混在したモジュールツリーとなる、SystemJSベースとなる単一ファイルのビルド構成を提供します。
91 | - [Browserify](http://browserify.org/).
92 | - [ngx-build-modern](https://github.com/manfredsteyer/ngx-build-plus/tree/master/ngx-build-modern) - Angular-CLI用のプラグインで、アプリケーションのバンドルを2種類にビルドできます:
93 |
94 | 1. ES2015モジュールと、必要となるpolyfillだけを搭載した、最新のブラウザでのみ動作するバンドルが小さい構成。
95 | 2. 多くのpolyfillと幅広いコンパイラターゲットを使用する、追加のレガシーバージョン構成(基本の設定)。
96 |
97 | **参考文献**
98 |
99 | - ["プロダクション用Angularアプリケーション構築"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
100 | - ["Google Closure Compilerを利用した2.5倍小さいアプリケーション"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
101 |
102 | ### ミニファイと不要コードの削除
103 |
104 | この方法により、アプリケーションのペイロードを減らすことができ、通信量を最小限に抑えることができます。
105 |
106 | **ツール一覧**
107 |
108 | - [Uglify](https://github.com/mishoo/UglifyJS) - 変数の名前修飾(mangling)、コメントと空白の削除、不要なコードの削除などでファイルサイズの縮小を実行します。すべてJavaScriptで書かれており、ほとんどのタスクランナーのためのプラグインが用意されています。
109 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - uglifyによるファイルサイズの縮小と動作が似ています。高度な利用方法には洗練された最適化を実行できるように、プログラムのASTを積極的に変換します。[JavaScriptのバージョン](https://www.npmjs.com/package/google-closure-compiler)が存在し、[ここで](https://www.npmjs.com/package/google-closure-compiler)詳細を見ることができます。
110 | GCCはほとんどのES2015モジュール構文をサポートしているため、[ツリーシェイキングで実行](#tree-shaking)ができます。
111 |
112 | **参考文献**
113 |
114 | - ["プロダクション用Angularアプリケーション構築"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
115 | - ["Google Closure Compilerを利用した2.5倍小さいアプリケーション"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
116 |
117 | ### テンプレート中の空白を削除
118 |
119 | 空白文字(正規表現の`\s`と言った文字)は見えませんが、ネットワーク上で転送されるバイト数として数えられます。空白をテンプレートからなるべく減らすことで、AoTコードのバンドルサイズをさらに減らすことが可能になります。
120 |
121 | 幸いなことに、これは手動で行う必要はありません。
122 | `ComponentMetadata`インターフェースは初期値として`false`を持つプロパティ`preserveWhitespaces`を提供します。空白を取り除くことは常にDOM側のレイアウトに影響するかもしれないからです。
123 | このプロパティを `true`に設定した場合、Angularは不要な空白を削除してバンドルサイズをさらに縮小します。
124 |
125 | - [AngularドキュメントのpreserveWhitespaces](https://angular.io/api/core/Component#preserveWhitespaces)
126 |
127 | ### ツリーシェイキング
128 |
129 | 私たちのアプリケーションの最終版では、Angularやサードパーティのライブラリ、そして私たちが書いたものでさえ全てのコードを使うことは通常ありません。
130 | ES2015モジュールの静的な性質のおかげで、アプリで参照のないコードを取り除くことができます。
131 |
132 | **サンプルコード**
133 |
134 | ```javascript
135 | // foo.js
136 | export foo = () => 'foo';
137 | export bar = () => 'bar';
138 |
139 | // app.js
140 | import { foo } from './foo';
141 | console.log(foo());
142 | ```
143 |
144 | `app.js`をツリーシェイキングでバンドルすると、以下のようになります。
145 |
146 | ```javascript
147 | let foo = () => 'foo';
148 | console.log(foo());
149 | ```
150 |
151 | これは、未使用のエクスポート`bar`が最終的なバンドルに含まれないことを意味します。
152 |
153 | **ツール一覧**
154 |
155 | - [Webpack](https://webpack.js.org) - [ツリーシェイキング](#tree-shaking)を実行することで効率的なバンドリングを提供します。 アプリケーションがバンドルされる時に、未使用のコードはエクスポートされず不要なコードと見なされ、結果としてUglifyによって削除されます。
156 | - [Rollup](https://github.com/rollup/rollup) - ES2015モジュールの静的な性質を利用して、効率的なツリーシェイキングを実行したバンドルを提供します。
157 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - 十分な最適化と、バンドルのサポートを提供します。
158 | 元となるJavaで書かれたものがあり、[ここ](https://www.npmjs.com/package/google-closure-compiler)には最近[JavaScriptで書かれたバージョン](https://www.npmjs.com/package/google-closure-compiler)もあります。
159 |
160 | **注意:** GCCはまだ export * をサポートしていません。これは"バレル"パターンが多用されるAngularアプリケーションの構築には重要な要素です。
161 |
162 | **参考文献**
163 |
164 | - ["プロダクション用Angularアプリケーション構築"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
165 | - ["Google Closure Compilerを利用した2.5倍小さいアプリケーション"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
166 | - ["RxJSでパイプ可能演算子を使用する"](https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md)
167 |
168 | ### ツリーシェイキング可能なプロバイダ
169 |
170 | Angularのバージョン 6 以降のリリースでは、Angularチームはサービスをツリーシェイキング可能にするための新機能を提供しました。
171 | つまり、他のサービスまたはコンポーネントによって使用されていない限り、そのサービスは最終的なバンドルに含まれません。
172 | これは、バンドルから未使用のコードを削除することによってバンドルサイズを減らすのに役立ちます。
173 |
174 | `providedIn`属性を使ってサービスをツリーシェイキング可能にするには、`@Injectable()`デコレータを使う時にサービスをどこに初期化するかを定義します。
175 | 次に、 `NgModule`宣言の`provider`属性とimport文から以下のように削除します。
176 |
177 | 修正前:
178 |
179 | ```ts
180 | // app.module.ts
181 | import { NgModule } from '@angular/core'
182 | import { AppRoutingModule } from './app-routing.module'
183 | import { AppComponent } from './app.component'
184 | import { environment } from '../environments/environment'
185 | import { MyService } from './app.service'
186 |
187 | @NgModule({
188 | declarations: [
189 | AppComponent
190 | ],
191 | imports: [
192 | ...
193 | ],
194 | providers: [MyService],
195 | bootstrap: [AppComponent]
196 | })
197 | export class AppModule { }
198 | ```
199 |
200 | ```ts
201 | // my-service.service.ts
202 | import { Injectable } from '@angular/core'
203 |
204 | @Injectable()
205 | export class MyService { }
206 | ```
207 |
208 | 修正後:
209 |
210 | ```ts
211 | // app.module.ts
212 | import { NgModule } from '@angular/core'
213 | import { AppRoutingModule } from './app-routing.module'
214 | import { AppComponent } from './app.component'
215 | import { environment } from '../environments/environment'
216 |
217 | @NgModule({
218 | declarations: [
219 | AppComponent
220 | ],
221 | imports: [
222 | ...
223 | ],
224 | providers: [],
225 | bootstrap: [AppComponent]
226 | })
227 | export class AppModule { }
228 | ```
229 |
230 | ```ts
231 | // my-service.service.ts
232 | import { Injectable } from '@angular/core'
233 |
234 | @Injectable({
235 | providedIn: 'root'
236 | })
237 | export class MyService { }
238 | ```
239 |
240 | `MyService`がどのコンポーネントやサービスにも注入されていない場合、バンドルには含まれません。
241 |
242 | **参考文献**
243 |
244 | - [Angular Providers](https://angular.io/guide/providers)
245 |
246 | ### Ahead-of-Time (AoT) コンパイル
247 |
248 | 既存のツール(GCC、Rollupなど)に対する課題は、コンポーネント中にあるHTMLテンプレートです。これらは既存ツールの機能では分析できません。
249 | そしてこれは、どのディレクティブがテンプレート内で参照されているのかわからないため、ツリーシェイキングのサポートを非効率にしてしまいます。
250 | AoTコンパイラは、AngularのHTMLテンプレートをES2015モジュールのインポートを利用しJavaScriptまたはTypeScriptに変換します。
251 | このようにして、バンドル中に効率的にツリーシェイキングを行い、Angularのサードパーティ製ライブラリや自分たちの未使用ディレクティブを削除することができます。
252 |
253 | **参考文献**
254 |
255 | - ["AngularのAhead-of-Timeコンパイル"](http://blog.mgechev.com/2016/08/14/ahead-of-time-compilation-angular-offline-precompilation/)
256 |
257 | ### 圧縮
258 |
259 | レスポンスのデータ転送量の圧縮は、帯域幅使用量を減らすための標準的な方法です。
260 | ヘッダに `Accept-Encoding`の値を指定することで、ブラウザはどの圧縮アルゴリズムがクライアントのマシンで利用可能であるかをサーバに教えます。
261 | 一方、サーバーは、レスポンスを圧縮するためにどのアルゴリズムが選択されたかをブラウザに伝えるために、レスポンスの `Content-Encoding`ヘッダに値を設定します。
262 |
263 | **ツール一覧**
264 |
265 | このツールはAngular固有のものではなく、私たちが使用しているWeb/アプリケーションサーバーに完全に依存します。
266 | 代表的な圧縮アルゴリズムは次のとおりです:
267 |
268 | - deflate - LZ77アルゴリズムとハフマン符号化の組み合わせを使用するデータ圧縮アルゴリズムおよび関連ファイル形式。
269 | - [brotli](https://github.com/google/brotli) - 最新のLZ77アルゴリズムの変種、ハフマン符号化、および2次コンテキストモデリングの組み合わせを使用して、現在利用可能な最善の汎用圧縮方法に匹敵する圧縮率でデータを圧縮できる汎用目的の可逆圧縮アルゴリズム。 それはdeflateと同じくらいの速度だが、より高密度な圧縮を提供します。
270 |
271 | **参考文献**
272 |
273 | - ["Gzip圧縮より優れているBrotli"](https://hacks.mozilla.org/2015/11/better-than-gzip-compression-with-brotli/)
274 | - ["Google Closure Compilerを利用した2.5倍小さいアプリケーション"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
275 |
276 | ### リソースの事前読込み
277 |
278 | リソースの事前読込みは、ユーザーエクスペリエンスを向上させるための優れた方法です。
279 | アセット(画像、スタイル、[遅延読み込み](#lazy-loading-of-resources)されるモジュールなど)やデータは事前読み込みすることができます。
280 | さまざまな事前読みの戦略がありますが、それらのほとんどはアプリケーションの仕様に依存します。
281 |
282 | ### リソースの遅延読み込み
283 |
284 | ターゲットのアプリケーションが数百の依存関係を持つ巨大なコードベースを持っている場合、上記のプラクティスは私たちのバンドルを合理的なサイズにするのを助けることができないかもしれません。(妥当サイズは100K~2M辺りですが、これも完全にビジネス目標に依存します)
285 |
286 | そのような場合の良い解決策はアプリケーションのモジュールのいくつかを遅延してロードすることです。
287 | たとえば、電子商取引システムを構築しているとします。
288 | この時にユーザー向けのUIとは別に管理パネルをロードできるようにておくことで、管理者が新しい製品を追加する必要なときだけに必要なUIを提供できます。
289 | この提供する内容は要件次第で「製品ページの追加」か「管理パネル全体」のいずれかになるでしょう。
290 |
291 | **ツール一覧**
292 |
293 | - [Webpack](https://github.com/webpack/webpack) - 非同期モジュールの読み込みを可能にします。
294 | - [ngx-quicklink](https://github.com/mgechev/ngx-quicklink) - 画面に表示されているリンクに関連付けられている遅延ロードモジュールを自動的にダウンロードするルーター事前ロード戦略
295 |
296 | ### デフォルトのルートを遅延読み込みにしない
297 |
298 | 次のようなルーティング設定があるとしましょう:
299 |
300 | ```ts
301 | // 良くない例
302 | const routes: Routes = [
303 | { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
304 | { path: 'dashboard', loadChildren: () => import('./dashboard.module').then(mod => mod.DashboardModule) },
305 | { path: 'heroes', loadChildren: () => import('./heroes.module').then(mod => mod.HeroesModule) }
306 | ];
307 | ```
308 |
309 | ユーザーが初めて url:`https://example.com/` にアクセスしてアプリケーションを開くと、パス`dashboard`の遅延ルートをトリガーする`/dashboard`にリダイレクトされます。 Angularがモジュールのブートストラップコンポーネントをレンダリングするためには、ファイル `dashboard.module`とそのすべての依存関係をダウンロードした後に、ファイルをJavaScript VMで解析し評価する必要があります。
310 |
311 | 最初のページのロード中に余分なHTTPリクエストをトリガーして不必要な計算を実行すると、最初のページレンダリングが遅くなるため、あまり良くありません。
312 | デフォルトのページルートを遅延しないように宣言することを検討してください。
313 |
314 | ### キャッシング
315 |
316 | キャッシングとは、あるリソースが最近要求された場合、近い将来また要求される可能性があるという推測に基づいてアプリケーションを高速化する一般的な方法です。
317 |
318 | データをキャッシュするためには、一般的に独自のキャッシュの仕組みを使います。
319 | 静的資産のキャッシュには、標準のブラウザキャッシュまたはService Workersと[CacheStorage API](https://developer.mozilla.org/en-US/docs/Web/API/Cache)を使用することができます。
320 |
321 | ### アプリケーションシェルを使う
322 |
323 | アプリケーションのパフォーマンスをより速くするためには、[アプリケーションシェル](https://developers.google.com/web/updates/2015/11/app-shell)を使用してください。
324 |
325 | アプリケーションシェルは、ユーザーにアプリケーションがもう間もなく動作しはじめる事を伝えるための最小のユーザーインターフェイスです。
326 | アプリケーションシェルを動的に生成するため、使用するレンダリングプラットフォーム毎に要素の表示を切り替えるカスタムディレクティブを付けてAngular Universalを使用できます。(つまり、`platform-server`を使うときはApp Shell以外のものはすべて隠蔽してください)
327 |
328 | **ツール一覧**
329 |
330 | - [Angular Service Worker](https://angular.io/guide/service-worker-intro) - サービスワーカーの管理プロセスを自動化することを目指しており、静的な資産をキャッシュするためのサービスワーカーと、[アプリケーションシェルの生成](https://developers.google.com/web/updates/2015/11/app-shell?hl=ja)も行います。
331 | - [Angular Universal](https://github.com/angular/angular/tree/master/packages/platform-server) - Angularに対する Universal (isomorphic) JavaScriptをサポートします。
332 |
333 | **参考文献**
334 |
335 | - ["アプリケーションシェルアーキテクチャを使用したWebアプリケーションの即時ロード"](https://developers.google.com/web/updates/2015/11/app-shell)
336 |
337 | ### サービスワーカーを使う
338 |
339 | サービスワーカーは、ブラウザ内にあるHTTPプロキシのようなものです。クライアントから送信されたすべての要求は最初にサービスワーカーによって傍受され、サービスワーカーは受け取ったデータを処理、またはネットワークを介して引き渡します。
340 |
341 | 次のコマンドを実行するだけで、Angularプロジェクトにサービスワーカーを追加できます。
342 | ```ng add @angular/pwa```
343 |
344 | **ツール一覧**
345 |
346 | - [Angular Service Worker](https://angular.io/guide/service-worker-intro) - サービスワーカーの管理プロセスを自動化することを目指しており、静的な資産をキャッシュするためのサービスワーカーと、[アプリケーションシェルの生成](https://developers.google.com/web/updates/2015/11/app-shell?hl=ja)も行います。
347 | - [Offline Plugin for Webpack](https://github.com/NekR/offline-plugin) - AppCacheにフォールバックしてサービスワーカーのサポートを追加するWebpack用のプラグイン。
348 |
349 | **参考文献**
350 |
351 | - ["オフラインクックブック"](https://jakearchibald.com/2014/offline-cookbook/)
352 | - ["サービスワーカーを始めよう"](https://angular.io/guide/service-worker-getting-started)
353 |
354 | ## 実行時の最適化
355 |
356 | このセクションには、毎秒60フレーム(fps)のスムーズなユーザーエクスペリエンスを提供するために利用できる実践的な方法が含まれています。
357 |
358 | ### `enableProdMode`を使う
359 |
360 | 開発モードのAngularは、変更検出を実行した時のバインディングが追加の変更が加えられないことを確認するために、いくつかの追加チェックを実行します。 これにより、フレームワークは単方向データフローに従ったことを保証します。
361 |
362 | 本番では開発モードを無効化する必要があるため、忘れずに`enableProdMode`を実行するようにしてください。
363 |
364 | ```typescript
365 | import { enableProdMode } from '@angular/core';
366 |
367 | if (ENV === 'production') {
368 | enableProdMode();
369 | }
370 | ```
371 |
372 | ### Ahead-of-Time コンパイル
373 |
374 | AoTは、ツリーシェイキングを実行してより効率的なバンドリングを実現するだけでなく、アプリケーション実行時のパフォーマンスを向上させます。
375 | AoTではない時に実行されているのはJust-in-Timeコンパイル(JiT)です。
376 | つまり、ビルドプロセスの一部としてコンパイルを実行することで、アプリケーションのレンダリングに必要な計算量を減らすことができます。
377 |
378 | **ツール一覧**
379 |
380 | - [angular2-seed](https://github.com/mgechev/angular2-seed) - AoTコンパイルのサポートを含むスタータープロジェクト。
381 | - [angular-cli](https://cli.angular.io) `ng serve --prod`コマンドを使う
382 |
383 | **参考文献**
384 |
385 | - ["AngularのAhead-of-Timeコンパイル"](http://blog.mgechev.com/2016/08/14/ahead-of-time-compilation-angular-offline-precompilation/)
386 |
387 | ### Webワーカー
388 |
389 | 一般的なシングルページアプリケーション(SPA)の問題は、通常のコードがシングルスレッドで動作することにあります。
390 | つまり、60fpsのスムーズなユーザーエクスペリエンスを実現したい場合は、個々のフレームの間隔である最大**16ms**の間に処理を完了しなければならず、出来ない場合ユーザーエクスペリエンスは悪くなってしまうでしょう。
391 |
392 | 巨大なコンポーネントツリーを使用した複雑なアプリケーションでは、変更検出で毎秒数百万のチェックをしているので、フレームを減らし始めるのは難しくありません。
393 | DOMアーキテクチャーはAngularのプラットフォームにより切り離されているため、Webワーカーでアプリケーション全体(変更検出を含む)を実行し、レンダリングのみをメインのUIスレッドに任せることができます。
394 |
395 | **ツール一覧**
396 |
397 | - コアチームによってサポートされているウェブワーカーでアプリケーションを実行できるようにするモジュールです。 使い方はこちらを[参照](https://github.com/angular/angular/tree/master/modules/playground/src/web_workers)してください。
398 | - [Webpack Web Worker Loader](https://github.com/webpack/worker-loader) -webpack用のウェブワーカーローダー
399 |
400 | **参考文献**
401 |
402 | - ["レスポンシブアプリのためにWebワーカーを使う"](https://www.youtube.com/watch?v=Kz_zKXiNGSE)
403 |
404 | ### サーバーサイドレンダリング
405 |
406 | 従来からあるSPAの大きな問題として、最初のレンダリングに必要なJavaScriptを読み込み終わるまで画面の表示ができませんでした。 これは2つの大きな問題を引き起こします:
407 |
408 | - すべての検索エンジンがそのページ内のJavaScriptを実行しているわけではないので、アプリケーションの動的コンテンツに適切にインデックスを付けることはできません。
409 | - ページ内のJavaScriptをダウンロード、解析、実行されるまで、ユーザーには空白/ロード中の画面しか表示されず、ユーザーエクスペリエンスが低下します。
410 |
411 | サーバーサイドレンダリングは、要求されたページをサーバー上で事前にレンダリングし、最初のページの読み込み時に描画が完了したマークアップを提供することによってこの問題を解決します。
412 |
413 | **ツール一覧**
414 |
415 | - [Angular Universal](https://github.com/angular/angular/tree/master/packages/platform-server) - Angularに対する Universal (isomorphic) JavaScriptをサポートします。
416 | - [Preboot](https://github.com/angular/preboot) - サーバーで生成されたWebビューからクライアントで生成されたWebビューへの状態(イベント、フォーカス、データ)の移行を管理するのに役立つライブラリ。
417 |
418 | **参考文献**
419 |
420 | - ["Angularのサーバーレンダリング"](https://www.youtube.com/watch?v=0wvZ7gakqV4)
421 | - ["Angularのユニバーサルパターン"](https://www.youtube.com/watch?v=TCj_oC3m6_U)
422 |
423 | ### 変更検出
424 |
425 | 非同期イベントが発生する毎に、Angularはコンポーネントツリー全体の変更検出を行います。
426 | 変更検出するコードは[インラインキャッシュ](http://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html)用に最適化されていますが、複雑なアプリケーションでは重い処理になることがあります。
427 | 変更検出のパフォーマンスを向上させる方法は、直近の動作で変更のされないサブツリーに対しては実行しないことです。
428 |
429 | #### `ChangeDetectionStrategy.OnPush`
430 |
431 | `OnPush`変更検出戦略により、コンポーネントツリーのサブツリーに対する変更検出メカニズムを無効にすることができます。 任意のコンポーネントに対する変更検出方法を`ChangeDetectionStrategy.OnPush`と設定することで、コンポーネントが異なる入力を受け取ったときにのみ変更検出を**実行します**。 Angularは参照によって以前の入力と比較した場合に入力が異なると見なし、参照チェックの結果は「false」になります。 不変のデータ構造と組み合わせると、`OnPush`はこのような「純粋なコンポーネント」に大きなパフォーマンス改善をもたらすことができます。
432 |
433 | **Resources**
434 |
435 | - ["Angularの変更検出"](https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c)
436 | - ["Angularの変更検出について知るべき一通りのこと"](https://blog.angularindepth.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f)
437 |
438 | #### Change Detectorの切り離し
439 |
440 | カスタム変化検出メカニズムを実装するもう一つの方法として、 特定のコンポーネントの変化検出の機能(CD)の`切り離し`と`再接続`を行うことができます。AngularのCDが一度`切り離される`と、コンポーネントサブツリー全体のチェックは実行されません。
441 |
442 | この方法は一般的に、ユーザーの操作や外部サービスとのやり取りによって、必要以上の変更検出が行われる場合に使用します。
443 | この場合、必要な変更検出を実行する必要がある場合にだけ変更検出機能を再接続することを検討してください。
444 |
445 | #### Run outside Angular
446 |
447 | Angularの変化検出メカニズムは[zone.js](https://github.com/angular/zone.js)によって実行されます。
448 | Zone.jsのモンキーパッチは、ブラウザ内のすべての非同期APIにパッチを適用し、非同期コールバックの実行終了時に変更の検出が引き起します。
449 | **レアケースとして**、変更検出メカニズムを実行したくないために、Angular Zoneのコンテキスト外で特定のコードを実行したい場合があります。
450 | そんな時は、`NgZone`インスタンスのメソッド`runOutsideAngular`を使うことができます。
451 |
452 | **例**
453 |
454 | 以下の小さなコードサンプルで、この方法を使ったコンポーネントの具体例を見ることができます。
455 | `_incrementPoints`メソッドが呼ばれると、コンポーネントは(基本的に)10ms毎に`_points`プロパティの増加を始めていきます。
456 | 値の増加はアニメーションのような錯覚をさせるでしょう。
457 | この時に、10msごとにコンポーネントツリー全体の変更検出メカニズムを起動したくないので、Angular Zoneのコンテキスト外で `_incrementPoints`を実行してDOMを手動で更新することができます。(`points` setter を参照)
458 |
459 | ```ts
460 | @Component({
461 | template: ''
462 | })
463 | class PointAnimationComponent {
464 |
465 | @Input() duration = 1000;
466 | @Input() stepDuration = 10;
467 | @ViewChild('label') label: ElementRef;
468 |
469 | @Input() set points(val: number) {
470 | this._points = val;
471 | if (this.label) {
472 | this.label.nativeElement.innerText = this._pipe.transform(this.points, '1.0-0');
473 | }
474 | }
475 | get points() {
476 | return this._points;
477 | }
478 |
479 | private _incrementInterval: any;
480 | private _points: number = 0;
481 |
482 | constructor(private _zone: NgZone, private _pipe: DecimalPipe) {}
483 |
484 | ngOnChanges(changes: any) {
485 | const change = changes.points;
486 | if (!change) {
487 | return;
488 | }
489 | if (typeof change.previousValue !== 'number') {
490 | this.points = change.currentValue;
491 | } else {
492 | this.points = change.previousValue;
493 | this._ngZone.runOutsideAngular(() => {
494 | this._incrementPoints(change.currentValue);
495 | });
496 | }
497 | }
498 |
499 | private _incrementPoints(newVal: number) {
500 | const diff = newVal - this.points;
501 | const step = this.stepDuration * (diff / this.duration);
502 | const initialPoints = this.points;
503 | this._incrementInterval = setInterval(() => {
504 | let nextPoints = Math.ceil(initialPoints + diff);
505 | if (this.points >= nextPoints) {
506 | this.points = initialPoints + diff;
507 | clearInterval(this._incrementInterval);
508 | } else {
509 | this.points += step;
510 | }
511 | }, this.stepDuration);
512 | }
513 | }
514 | ```
515 |
516 | **警告**: 正確に実行されないとDOMの状態に矛盾が生じる可能性があります、**自分が何をしているか確実に理解できるときだけ**この方法を使用してください。
517 | また、上記のコードはウェブワーカーでは実行できません。
518 | ウェブワーカー互換にするためには、Angularのレンダラを使用して表示するラベルの値を設定する必要があります。
519 |
520 | ### 純粋なpipeを使う
521 |
522 | `@Pipe`デコレータは引数に以下のような形式のオブジェクトリテラルを受け取れます。
523 |
524 | ```typescript
525 | interface PipeMetadata {
526 | name: string;
527 | pure: boolean;
528 | }
529 | ```
530 |
531 | pureフラグは、パイプがどのグローバルの状態にも依存してなく、副作用を引き起こさないことを示します。
532 | つまり、同じ入力で呼び出された場合、どんな時もパイプは同じ出力を返します。
533 | こうすることでAngularはパイプが呼び出されたすべての入力パラメータの出力をキャッシュすることができ、それぞれの評価時に再計算するせず再利用を可能にしています。
534 |
535 | `pure`プロパティの初期値は`true`です。
536 |
537 | ### `*ngFor` ディレクティブ
538 |
539 | `*ngFor`ディレクティブはコレクション(データやオブジェクトなどをまとめて格納するデータ構造)を表示するために使われます。
540 |
541 | #### `trackBy` オプションを使う
542 |
543 | 基本的なふるまいとして`*ngFor`は参照によってオブジェクトの一意性を識別します。
544 |
545 | つまり、開発者がアイテムのコンテンツ更新中にオブジェクトへの参照を破棄した場合、Angularは古いオブジェクトが削除されて別のオブジェクトが生成されたと判断します。
546 | これは、一覧にある古いDOMノードを破棄し、その場所に新しいDOMノードを追加するといった影響として現れます。
547 |
548 | 開発者は、オブジェクトの一意性を識別するためにAngularへヒントを教えることができます: `*ngFor`ディレクティブの`trackBy`オプションへカスタムトラッキング関数を設定します。
549 | トラッキング関数には2つの引数があります: `index`と`item`です。
550 | Angularは、トラッキング関数から返された値を使用してアイテムの識別情報を追跡します。
551 | 固有のキーとして特定のレコードのIDを使用することはよくある例です。
552 |
553 | **例**
554 |
555 | ```typescript
556 | @Component({
557 | selector: 'yt-feed',
558 | template: `
559 | Your video feed
560 |
561 | `
562 | })
563 | export class YtFeedComponent {
564 | feed = [
565 | {
566 | id: 3849, // "id"フィールドに注意してください、"trackById"関数でそれを参照します
567 | title: "Angular in 60 minutes",
568 | url: "http://youtube.com/ng2-in-60-min",
569 | likes: "29345"
570 | },
571 | // ...
572 | ];
573 |
574 | trackById(index, item) {
575 | return item.id;
576 | }
577 | }
578 | ```
579 |
580 | #### DOM要素を小さくする
581 |
582 | DOMのレンダリングは、要素をUIに追加する時に最もコストのかかる操作です。
583 | よくある操作は、DOMに要素を挿入しスタイルを適用することによって起こります。
584 | `*ngFor`で大量の要素を描画する時、ブラウザ(特に古いもの)は遅くなり、すべての要素の表示に多くの時間が必要となってしまうかもしれません。
585 | そしてこれはAngularに限った話ではありません。
586 |
587 | 描画時間を短くするために、次のことを試してください:
588 |
589 | - [CDK](https://material.angular.io/cdk/scrolling/overview)か[ngx-virtual-scroller](https://github.com/rintoj/ngx-virtual-scroller)を使い仮想スクロールを適用します。
590 | - テンプレートの `*ngFor`セクションで表示されるDOM要素の量を減らす。 不要または未使用のDOM要素はテンプレートを幾度となく拡張することによって発生します。その構造を再考すれば、もっとシンプルにすることができるでしょう。
591 | - 可能であれば[`ng-container`](https://angular.io/guide/structural-directives#ngcontainer)使ってください。
592 |
593 | **参考文献**
594 |
595 | - ["NgForディレクティブ"](https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html) - `*ngFor`の公式ドキュメント
596 | - ["Angular - trackByでパフォーマンスを向上させる"](https://netbasal.com/angular-2-improve-performance-with-trackby-cc147b5104e5) - 取り組みについてのgif動画が見れます
597 | - [Component Dev Kit (CDK) Virtual Scrolling](https://material.angular.io/cdk/scrolling/overview) - API description
598 | - [ngx-virtual-scroller](https://github.com/rintoj/ngx-virtual-scroller) - 仮想**無限**リストをを見ることが出来ます
599 |
600 | ### テンプレート中の式を最適化する
601 |
602 | Angularは全ての変更検知サイクルを実行した後、テンプレートの式を評価します。
603 | 変更検知サイクルは、promiseの解決、httpの結果、タイマーイベント、キー操作、マウスの動きなどの様々な非同期となる動作によって引き起こされます。
604 |
605 | 式はすぐに終了する必要があります。そうしないと、特に遅いデバイスではユーザーエクスペリエンスが悪化する可能性があります。
606 | 計算にコストがかかる場合は値をキャッシュすることを検討してください。
607 |
608 | **参考文献**
609 |
610 | - [quick-execution](https://angular.io/guide/template-syntax#quick-execution) - テンプレート式の公式文書
611 | - [Increasing Performance - more than a pipe dream](https://youtu.be/I6ZvpdRM1eQ) - YouTubeのng-conf動画。補間式で関数の代わりにパイプを利用する
612 |
613 | # 終わりに
614 |
615 | これらの実践方法の一覧は、新たな/更新され、時間が経つにつれてダイナミックに進化します。
616 | 何かが足りない事に気付いた場合や、慣習のいずれかを改善できると思えた場合は、躊躇わずissuesやPRをしてください。
617 | 詳細については、次の「[貢献する](#contributing)」セクションをご覧ください。
618 |
619 | # 貢献する
620 |
621 | 何かのミス、中途半端なもの、または間違いに気付いた場合はプルリクエストをいただければ幸いです。
622 | 文書に含まれていない慣習についての議論は[issueを開いてください](https://github.com/mgechev/angular2-performance-checklist/issues)。
623 |
624 | # License
625 |
626 | MIT
627 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular Performance Checklist
2 |
3 |
4 |
5 | - [中文版](./README.zh-CN.md)
6 | - [Русский](./README.ru-RU.md)
7 | - [Português](./README.pt-BR.md)
8 | - [Español](./README.es-ES.md)
9 | - [日本語](./README.ja-JP.md)
10 |
11 | ## Introduction
12 |
13 | This document contains a list of practices that will help us boost the performance of our Angular applications. "Angular Performance Checklist" covers different topics - from server-side pre-rendering and bundling of our applications to runtime performance and optimization of the change detection performed by the framework.
14 |
15 | The document is divided into two main sections:
16 |
17 | - Network performance - lists practices that are going to improve mostly the load time of our application. They include methods for latency and bandwidth reduction.
18 | - Runtime performance - practices that improve the runtime performance of our application. They include mostly change detection and rendering related optimizations.
19 |
20 | Some practices impact both categories so there could be a slight intersection, however, the differences in the use cases and the implications will be explicitly mentioned.
21 |
22 | Most subsections list tools, related to the specific practice, that can make us more efficient by automating our development flow.
23 |
24 | Note that most practices are valid for both HTTP/1.1 and HTTP/2. Practices which make an exception will be mentioned by specifying to which version of the protocol they could be applied.
25 |
26 | ## Table of Content
27 |
28 | - [Angular Performance Checklist](#angular-2-performance-checklist)
29 | - [Introduction](#introduction)
30 | - [Table of Content](#table-of-content)
31 | - [Network performance](#network-performance)
32 | - [Bundling](#bundling)
33 | - [Minification and Dead code elimination](#minification-and-dead-code-elimination)
34 | - [Remove template whitespace](#remove-template-whitespace)
35 | - [Tree-shaking](#tree-shaking)
36 | - [Tree-shakeable providers](#tree-shakeable-providers)
37 | - [Ahead-of-Time (AoT) Compilation](#ahead-of-time-aot-compilation)
38 | - [Compression](#compression)
39 | - [Pre-fetching Resources](#pre-fetching-resources)
40 | - [Lazy-Loading of Resources](#lazy-loading-of-resources)
41 | - [Don't lazy-load default route](#dont-lazy-load-the-default-route)
42 | - [Caching](#caching)
43 | - [Use Application Shell](#use-application-shell)
44 | - [Use Service Workers](#use-service-workers)
45 | - [Runtime Optimizations](#runtime-optimizations)
46 | - [Use `enableProdMode`](#use-enableprodmode)
47 | - [Ahead-of-Time Compilation](#ahead-of-time-compilation)
48 | - [Web Workers](#web-workers)
49 | - [Server-Side Rendering](#server-side-rendering)
50 | - [Change Detection](#change-detection)
51 | - [`ChangeDetectionStrategy.OnPush`](#changedetectionstrategyonpush)
52 | - [Detaching the Change Detector](#detaching-the-change-detector)
53 | - [Run outside Angular](#run-outside-angular)
54 | - [Coalescing event change detections](#coalescing-event-change-detections)
55 | - [Use pure pipes](#use-pure-pipes)
56 | - [`*ngFor` directive](#ngfor-directive)
57 | - [Use `trackBy` option](#use-trackby-option)
58 | - [Minimize DOM elements](#minimize-dom-elements)
59 | - [Optimize template expressions](#optimize-template-expressions)
60 | - [Conclusion](#conclusion)
61 | - [Contributing](#contributing)
62 |
63 | ## Network performance
64 |
65 | Some of the tools in this section are still in development and are subject to change. The Angular core team is working on automating the build process for our applications as much as possible so a lot of things will happen transparently.
66 |
67 | ### Bundling
68 |
69 | Bundling is a standard practice aiming to reduce the number of requests that the browser needs to perform in order to deliver the application requested by the user. In essence, the bundler receives as an input a list of entry points and produces one or more bundles. This way, the browser can get the entire application by performing only a few requests, instead of requesting each individual resource separately.
70 |
71 | As your application grows bundling everything into a single large bundle would again be counterproductive. Explore Code Splitting techniques using Webpack.
72 |
73 | **Additional http requests will not be a concern with HTTP/2 because of the [server push](https://http2.github.io/faq/#whats-the-benefit-of-server-push) feature.**
74 |
75 | **Tooling**
76 |
77 | Tools which allows us to bundle our applications efficiently are:
78 |
79 | - [Webpack](https://webpack.js.org) - provides efficient bundling by performing [tree-shaking](#tree-shaking).
80 | - [Webpack Code Splitting](https://webpack.js.org/guides/code-splitting/) - Techniques to split your code.
81 | - [Webpack & http2](https://medium.com/webpack/webpack-http-2-7083ec3f3ce6#.46idrz8kb) - Need for splitting with http2.
82 | - [Rollup](https://github.com/rollup/rollup) - provides bundling by performing efficient tree-shaking, taking advantage of the static nature of the ES2015 modules.
83 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - performs plenty of optimizations and provides bundling support. Originally written in Java, since recently it also has a [JavaScript version](https://www.npmjs.com/package/google-closure-compiler) that can be [found here](https://www.npmjs.com/package/google-closure-compiler).
84 | - [SystemJS Builder](https://github.com/systemjs/builder) - provides a single-file build for SystemJS of mixed-dependency module trees.
85 | - [Browserify](http://browserify.org/).
86 |
87 | **Resources**
88 |
89 | - ["Building an Angular Application for Production"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
90 | - ["2.5X Smaller Angular Applications with Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
91 |
92 | ### Minification and dead code elimination
93 |
94 | These practices allow us to minimize the bandwidth consumption by reducing the payload of our application.
95 |
96 | **Tooling**
97 |
98 | - [Uglify](https://github.com/mishoo/UglifyJS) - performs minification such as mangling variables, removal of comments & whitespace, dead code elimination, etc. Written completely in JavaScript, has plugins for all popular task runners.
99 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - performs similar to uglify type of minification. In advanced mode, it transforms the AST of our program aggressively in order to be able to perform even more sophisticated optimizations. It has also a [JavaScript version](https://www.npmjs.com/package/google-closure-compiler) that can be [found here](https://www.npmjs.com/package/google-closure-compiler). GCC also supports *most of the ES2015 modules syntax* so it can [perform tree-shaking](#tree-shaking).
100 |
101 | **Resources**
102 |
103 | - ["Building an Angular Application for Production"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
104 | - ["2.5X Smaller Angular Applications with Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
105 |
106 | ### Remove template whitespace
107 |
108 | Although we don't see the whitespace character (a character matching the `\s` regex) it is still represented by bytes which are transferred over the network. If we reduce the whitespace from our templates to the minimum we will be respectively able to drop the bundle size of the AoT code even further.
109 |
110 | Thankfully, we don't have to do this manually. The `ComponentMetadata` interface provides the property `preserveWhitespaces` which by default has value `false` meaning that by default the Angular compiler will trim whitespaces to further reduce the size of our application. In case we set the property to `true` Angular will preserve the whitespace.
111 |
112 | - [preserveWhitespaces in the Angular docs](https://angular.io/api/core/Component#preserveWhitespaces)
113 |
114 | ### Tree-shaking
115 |
116 | For the final version of our applications, we usually don't use the entire code which is provided by Angular and/or any third-party library, even the one that we've written. Thanks to the static nature of the ES2015 modules, we're able to get rid of the code which is not referenced in our apps.
117 |
118 | **Example**
119 |
120 | ```javascript
121 | // foo.js
122 | export foo = () => 'foo';
123 | export bar = () => 'bar';
124 |
125 | // app.js
126 | import { foo } from './foo';
127 | console.log(foo());
128 | ```
129 | Once we tree-shake and bundle `app.js` we'll get:
130 |
131 | ```javascript
132 | let foo = () => 'foo';
133 | console.log(foo());
134 | ```
135 |
136 | This means that the unused export `bar` will not be included into the final bundle.
137 |
138 | **Tooling**
139 |
140 | - [Webpack](https://webpack.js.org) - provides efficient bundling by performing [tree-shaking](#tree-shaking). Once the application has been bundled, it does not export the unused code so it can be safely considered as dead code and removed by Uglify.
141 | - [Rollup](https://github.com/rollup/rollup) - provides bundling by performing an efficient tree-shaking, taking advantage of the static nature of the ES2015 modules.
142 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - performs plenty of optimizations and provides bundling support. Originally written in Java, since recently it has also a [JavaScript version](https://www.npmjs.com/package/google-closure-compiler) that can be [found here](https://www.npmjs.com/package/google-closure-compiler).
143 |
144 | *Note:* GCC does not support `export *` yet, which is essential for building Angular applications because of the heavy usage of the "barrel" pattern.
145 |
146 | **Resources**
147 |
148 | - ["Building an Angular Application for Production"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
149 | - ["2.5X Smaller Angular Applications with Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
150 | - ["Using pipeable operators in RxJS"](https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md)
151 |
152 | ### Tree-Shakeable Providers
153 |
154 | Since the release of Angular version 6, The angular team provided a new feature to allow services to be tree-shakeable, meaning that your services will not be included in the final bundle unless they're being used by other services or components. This can help reduce the bundle size by removing unused code from the bundle.
155 |
156 | You can make your services tree-shakeable by using the `providedIn` attribute to define where the service should be initialized when using the `@Injectable()` decorator. Then you should remove it from the `providers` attribute of your `NgModule` declaration as well as its import statement as follows.
157 |
158 | Before:
159 |
160 | ```ts
161 | // app.module.ts
162 | import { NgModule } from '@angular/core'
163 | import { AppRoutingModule } from './app-routing.module'
164 | import { AppComponent } from './app.component'
165 | import { environment } from '../environments/environment'
166 | import { MyService } from './app.service'
167 |
168 | @NgModule({
169 | declarations: [
170 | AppComponent
171 | ],
172 | imports: [
173 | ...
174 | ],
175 | providers: [MyService],
176 | bootstrap: [AppComponent]
177 | })
178 | export class AppModule { }
179 | ```
180 |
181 | ```ts
182 | // my-service.service.ts
183 | import { Injectable } from '@angular/core'
184 |
185 | @Injectable()
186 | export class MyService { }
187 | ```
188 |
189 | After:
190 |
191 | ```ts
192 | // app.module.ts
193 | import { NgModule } from '@angular/core'
194 | import { AppRoutingModule } from './app-routing.module'
195 | import { AppComponent } from './app.component'
196 | import { environment } from '../environments/environment'
197 |
198 | @NgModule({
199 | declarations: [
200 | AppComponent
201 | ],
202 | imports: [
203 | ...
204 | ],
205 | providers: [],
206 | bootstrap: [AppComponent]
207 | })
208 | export class AppModule { }
209 | ```
210 |
211 | ```ts
212 | // my-service.service.ts
213 | import { Injectable } from '@angular/core'
214 |
215 | @Injectable({
216 | providedIn: 'root'
217 | })
218 | export class MyService { }
219 | ```
220 |
221 | If `MyService` is not injected in any component/service, then it will not be included in the bundle.
222 |
223 | **Resources**
224 |
225 | - [Angular Providers](https://angular.io/guide/providers)
226 |
227 | ### Ahead-of-Time (AoT) Compilation
228 |
229 | A challenge for the available in the wild tools (such as GCC, Rollup, etc.) are the HTML-like templates of the Angular components, which cannot be analyzed with their capabilities. This makes their tree-shaking support less efficient because they're not sure which directives are referenced within the templates. The AoT compiler transpiles the Angular HTML-like templates to JavaScript or TypeScript with ES2015 module imports. This way we are able to efficiently tree-shake during bundling and remove all the unused directives defined by Angular, third-party libraries or by ourselves.
230 |
231 | **Resources**
232 |
233 | - ["Ahead-of-Time Compilation in Angular"](http://blog.mgechev.com/2016/08/14/ahead-of-time-compilation-angular-offline-precompilation/)
234 |
235 | ### Compression
236 |
237 | Compression of the responses' payload standard practice for bandwidth usage reduction. By specifying the value of the header `Accept-Encoding`, the browser hints the server which compression algorithms are available on the client's machine. On the other hand, the server sets the value for the `Content-Encoding` header of the response in order to tell the browser which algorithm has been chosen for compressing the response.
238 |
239 | **Tooling**
240 |
241 | The tooling here is not Angular-specific and entirely depends on the web/application server that we're using. Typical compression algorithms are:
242 |
243 | - deflate - a data compression algorithm and associated file format that uses a combination of the LZ77 algorithm and Huffman coding.
244 | - [brotli](https://github.com/google/brotli) - a generic-purpose lossless compression algorithm that compresses data using a combination of a modern variant of the LZ77 algorithm, Huffman coding, and 2nd order context modeling, with a compression ratio comparable to the best currently available general-purpose compression methods. It is similar in speed with deflate but offers more dense compression.
245 |
246 | **Resources**
247 |
248 | - ["Better than Gzip Compression with Brotli"](https://hacks.mozilla.org/2015/11/better-than-gzip-compression-with-brotli/)
249 | - ["2.5X Smaller Angular Applications with Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
250 |
251 | ### Pre-fetching Resources
252 |
253 | Resource pre-fetching is a great way to improve user experience. We can either pre-fetch assets (images, styles, modules intended to be [loaded lazily](#lazy-loading-of-resources), etc.) or data. There are different pre-fetching strategies but most of them depend on specifics of the application.
254 |
255 | ### Lazy-Loading of Resources
256 |
257 | In case the target application has a huge code base with hundreds of dependencies, the practices listed above may not help us reduce the bundle to a reasonable size (reasonable might be 100K or 2M, it again, completely depends on the business goals).
258 |
259 | In such cases, a good solution might be to load some of the application's modules lazily. For instance, let's suppose we're building an e-commerce system. In this case, we might want to load the admin panel independently from the user-facing UI. Once the administrator has to add a new product we'd want to provide the UI required for that. This could be either only the "Add product page" or the entire admin panel, depending on our use case/business requirements.
260 |
261 | **Tooling**
262 |
263 | - [Webpack](https://github.com/webpack/webpack) - allows asynchronous module loading.
264 | - [ngx-quicklink](https://github.com/mgechev/ngx-quicklink) - router preloading strategy which automatically downloads the lazy-loaded modules associated with all the visible links on the screen
265 |
266 | ### Don't Lazy-Load the Default Route
267 |
268 | Let's suppose we have the following routing configuration:
269 |
270 | ```ts
271 | // Bad practice
272 | const routes: Routes = [
273 | { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
274 | { path: 'dashboard', loadChildren: () => import('./dashboard.module').then(mod => mod.DashboardModule) },
275 | { path: 'heroes', loadChildren: () => import('./heroes.module').then(mod => mod.HeroesModule) }
276 | ];
277 | ```
278 |
279 | The first time the user opens the application using the url: https://example.com/ they will be redirected to `/dashboard` which will trigger the lazy-route with path `dashboard`. In order Angular to render the bootstrap component of the module, it will has to download the file `dashboard.module` and all of its dependencies. Later, the file needs to be parsed by the JavaScript VM and evaluated.
280 |
281 | Triggering extra HTTP requests and performing unnecessary computations during the initial page load is a bad practice since it slows down the initial page rendering. Consider declaring the default page route as non-lazy.
282 |
283 | ### Caching
284 |
285 | Caching is another common practice intending to speed-up our application by taking advantage of the heuristic that if one resource was recently been requested, it might be requested again in the near future.
286 |
287 | For caching data we usually use a custom caching mechanism. For caching static assets, we can either use the standard browser caching or Service Workers with the [CacheStorage API](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
288 |
289 | ### Use Application Shell
290 |
291 | To make the perceived performance of your application faster, use an [Application Shell](https://developers.google.com/web/updates/2015/11/app-shell).
292 |
293 | The application shell is the minimum user interface that we show to the users in order to indicate them that the application will be delivered soon. For generating an application shell dynamically you can use Angular Universal with custom directives which conditionally show elements depending on the used rendering platform (i.e. hide everything except the App Shell when using `platform-server`).
294 |
295 | **Tooling**
296 |
297 | - [Angular Service Worker](https://angular.io/guide/service-worker-intro) - aims to automate the process of managing Service Workers. It also contains Service Worker for caching static assets and one for [generating application shell](https://developers.google.com/web/updates/2015/11/app-shell?hl=en).
298 | - [Angular Universal](https://github.com/angular/angular/tree/master/packages/platform-server) - Universal (isomorphic) JavaScript support for Angular.
299 |
300 | **Resources**
301 |
302 | - ["Instant Loading Web Apps with an Application Shell Architecture"](https://developers.google.com/web/updates/2015/11/app-shell)
303 |
304 | ### Use Service Workers
305 |
306 | We can think of the Service Worker as an HTTP proxy which is located in the browser. All requests sent from the client are first intercepted by the Service Worker which can either handle them or pass them through the network.
307 |
308 | You can add a Service Worker to your Angular project by running
309 | ``` ng add @angular/pwa ```
310 |
311 | **Tooling**
312 |
313 | - [Angular Service Worker](https://angular.io/guide/service-worker-intro) - aims to automate the process of managing Service Workers. It also contains Service Worker for caching static assets and one for [generating application shell](https://developers.google.com/web/updates/2015/11/app-shell?hl=en).
314 | - [Offline Plugin for Webpack](https://github.com/NekR/offline-plugin) - Webpack plugin that adds support for Service Worker with a fall-back to AppCache.
315 |
316 | **Resources**
317 |
318 | - ["The offline cookbook"](https://jakearchibald.com/2014/offline-cookbook/)
319 | - ["Getting started with service workers"](https://angular.io/guide/service-worker-getting-started)
320 |
321 | ## Runtime Optimizations
322 |
323 | This section includes practices that can be applied in order to provide smoother user experience with 60 frames per second (fps).
324 |
325 | ### Use `enableProdMode`
326 |
327 | In development mode, Angular performs some extra checks in order to verify that performing change detection does not result in any additional changes to any of the bindings. This way the frameworks assures that the unidirectional data flow has been followed.
328 |
329 | In order to disable these changes for production do not forget to invoke `enableProdMode`:
330 |
331 | ```typescript
332 | import { enableProdMode } from '@angular/core';
333 |
334 | if (ENV === 'production') {
335 | enableProdMode();
336 | }
337 | ```
338 |
339 | ### Ahead-of-Time Compilation
340 |
341 | AoT can be helpful not only for achieving more efficient bundling by performing tree-shaking, but also for improving the runtime performance of our applications. The alternative of AoT is Just-in-Time compilation (JiT) which is performed runtime, therefore we can reduce the amount of computations required for the rendering of our application by performing the compilation as part of our build process.
342 |
343 | **Tooling**
344 |
345 | - [angular2-seed](https://github.com/mgechev/angular2-seed) - a starter project which includes support for AoT compilation.
346 | - [angular-cli](https://cli.angular.io) Using the `ng serve --prod`
347 |
348 | **Resources**
349 |
350 | - ["Ahead-of-Time Compilation in Angular"](http://blog.mgechev.com/2016/08/14/ahead-of-time-compilation-angular-offline-precompilation/)
351 |
352 | ### Web Workers
353 |
354 | The usual problem in the typical single-page application (SPA) is that our code is usually run in a single thread. This means that if we want to achieve smooth user experience with 60fps we have **at most 16ms** for execution between the individual frames are being rendered, otherwise, they'll drop by half.
355 |
356 | In complex applications with a huge component tree, where the change detection needs to perform millions of checks each second it will not be hard to start dropping frames. Thanks to the Angular's agnosticism and being decoupled from DOM architecture, it's possible to run our entire application (including change detection) in a Web Worker and leave the main UI thread responsible only for rendering.
357 |
358 | **Tooling**
359 |
360 | - The module which allows us to run our application in a Web Worker is supported by the core team. Examples of how it can be used can be [found here](https://github.com/angular/angular/tree/master/modules/playground/src/web_workers).
361 | - [Webpack Web Worker Loader](https://github.com/webpack/worker-loader) - A Web Worker Loader for webpack.
362 |
363 | **Resources**
364 |
365 | - ["Using Web Workers for more responsive apps"](https://www.youtube.com/watch?v=Kz_zKXiNGSE)
366 |
367 | ### Server-Side Rendering
368 |
369 | A big issue of the traditional SPA is that they cannot be rendered until the entire JavaScript required for their initial rendering is available. This leads to two big problems:
370 |
371 | - Not all search engines are running the JavaScript associated with the page so they are not able to index the content of dynamic apps properly.
372 | - Poor user experience, since the user will see nothing more than a blank/loading screen until the JavaScript associated with the page is downloaded, parsed and executed.
373 |
374 | Server-side rendering solves this issue by pre-rendering the requested page on the server and providing the markup of the rendered page during the initial page load.
375 |
376 | **Tooling**
377 |
378 | - [Angular Universal](https://github.com/angular/angular/tree/master/packages/platform-server) - Universal (isomorphic) JavaScript support for Angular.
379 | - [Preboot](https://github.com/angular/preboot) - Library to help manage the transition of state (i.e. events, focus, data) from a server-generated web view to a client-generated web view.
380 | - [Scully](https://github.com/scullyio/scully) - Static site generator for Angular projects looking to embrace the JAMStack.
381 |
382 | **Resources**
383 |
384 | - ["Angular Server Rendering"](https://www.youtube.com/watch?v=0wvZ7gakqV4)
385 | - ["Angular Universal Patterns"](https://www.youtube.com/watch?v=TCj_oC3m6_U)
386 | - ["Create a Static Site Using Angular & Scully"](https://www.youtube.com/watch?v=ugTx-14jRrI)
387 |
388 | ### Change Detection
389 |
390 | On each asynchronous event, Angular performs change detection over the entire component tree. Although the code which detects for changes is optimized for [inline-caching](http://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html), this still can be a heavy computation in complex applications. A way to improve the performance of the change detection is to not perform it for subtrees which are not supposed to be changed based on the recent actions.
391 |
392 | #### `ChangeDetectionStrategy.OnPush`
393 |
394 | The `OnPush` change detection strategy allows us to disable the change detection mechanism for subtrees of the component tree. By setting the change detection strategy to any component to the value `ChangeDetectionStrategy.OnPush` will make the change detection perform **only** when the component has received different inputs. Angular will consider inputs as different when it compares them with the previous inputs by reference, and the result of the reference check is `false`. In combination with [immutable data structures](https://facebook.github.io/immutable-js/), `OnPush` can bring great performance implications for such "pure" components.
395 |
396 | **Resources**
397 |
398 | - ["Change Detection in Angular"](https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c)
399 | - ["Everything you need to know about change detection in Angular"](https://indepth.dev/posts/1053/everything-you-need-to-know-about-change-detection-in-angular)
400 |
401 | #### Detaching the Change Detector
402 |
403 | Another way of implementing a custom change detection mechanism is by `detach`ing and `reattach`ing the change detector (CD) for given a component. Once we `detach` the CD Angular will not perform check for the entire component subtree.
404 |
405 | This practice is typically used when user actions or interactions with external services trigger the change detection more often than required. In such cases we may want to consider detaching the change detector and reattaching it only when performing change detection is required.
406 |
407 | #### Run outside Angular
408 |
409 | The Angular's change detection mechanism is being triggered thanks to [zone.js](https://github.com/angular/zone.js). Zone.js monkey patches all asynchronous APIs in the browser and triggers the change detection at the end of the execution of any async callback. In **rare cases**, we may want the given code to be executed outside the context of the Angular Zone and thus, without running change detection mechanism. In such cases, we can use the method `runOutsideAngular` of the `NgZone` instance.
410 |
411 | **Example**
412 |
413 | In the snippet below, you can see an example for a component that uses this practice. When the `_incrementPoints` method is called the component will start incrementing the `_points` property every 10ms (by default). The incrementation will make the illusion of an animation. Since in this case, we don't want to trigger the change detection mechanism for the entire component tree, every 10ms, we can run `_incrementPoints` outside the context of the Angular's zone and update the DOM manually (see the `points` setter).
414 |
415 | ```ts
416 | @Component({
417 | template: ''
418 | })
419 | class PointAnimationComponent {
420 |
421 | @Input() duration = 1000;
422 | @Input() stepDuration = 10;
423 | @ViewChild('label') label: ElementRef;
424 |
425 | @Input() set points(val: number) {
426 | this._points = val;
427 | if (this.label) {
428 | this.label.nativeElement.innerText = this._pipe.transform(this.points, '1.0-0');
429 | }
430 | }
431 | get points() {
432 | return this._points;
433 | }
434 |
435 | private _incrementInterval: any;
436 | private _points: number = 0;
437 |
438 | constructor(private _ngZone: NgZone, private _pipe: DecimalPipe) {}
439 |
440 | ngOnChanges(changes: any) {
441 | const change = changes.points;
442 | if (!change) {
443 | return;
444 | }
445 | if (typeof change.previousValue !== 'number') {
446 | this.points = change.currentValue;
447 | } else {
448 | this.points = change.previousValue;
449 | this._ngZone.runOutsideAngular(() => {
450 | this._incrementPoints(change.currentValue);
451 | });
452 | }
453 | }
454 |
455 | private _incrementPoints(newVal: number) {
456 | const diff = newVal - this.points;
457 | const step = this.stepDuration * (diff / this.duration);
458 | const initialPoints = this.points;
459 | this._incrementInterval = setInterval(() => {
460 | let nextPoints = Math.ceil(initialPoints + diff);
461 | if (this.points >= nextPoints) {
462 | this.points = initialPoints + diff;
463 | clearInterval(this._incrementInterval);
464 | } else {
465 | this.points += step;
466 | }
467 | }, this.stepDuration);
468 | }
469 | }
470 | ```
471 |
472 | **Warning**: Use this practice **very carefully only when you're sure what you are doing** because if not used properly it can lead to an inconsistent state of the DOM. Also, note that the code above is not going to run in WebWorkers. In order to make it WebWorker-compatible, you need to set the label's value by using the Angular's renderer.
473 |
474 | #### Coalescing event change detections
475 |
476 | Angular uses zone.js to intercept events that occurred in the application and runs a change detection automatically. By default this happens when the [microtask queue](https://www.youtube.com/watch?v=cCOL7MC4Pl0) of the browser is empty, which in some cases may call redundant cycles.
477 | From v9, Angular provides a way to coalesce event change detections by turning `ngZoneEventCoalescing` on, i.e
478 | ```typescript
479 | platformBrowser()
480 | .bootstrapModule(AppModule, { ngZoneEventCoalescing: true });
481 | ```
482 | The above configuration will schedule change detection with `requestAnimationFrame`, instead of plugging into the microtask queue, which will run checks less frequently and consume fewer computational cycles.
483 |
484 | > Warning: **ngZoneEventCoalescing: true** may break existing apps that relay on consistently running change detection.
485 |
486 |
487 | **Resources**
488 | - [ngZoneEventCoalescing BootstrapOption](https://github.com/angular/angular/blob/master/packages/core/src/application_ref.ts#L268) - source code for BootstrapOptions interface
489 | - [Reduce Change Detection Cycles with Event Coalescing in Angular](https://netbasal.com/reduce-change-detection-cycles-with-event-coalescing-in-angular-c4037199859f)
490 | - [Simple Angular context help component or how global event listener can affect your perfomance](https://medium.com/@a.yurich.zuev/simple-angular-context-help-component-or-how-global-event-listener-can-affect-your-perfomance-75b67dba197f)
491 |
492 | ### Use pure pipes
493 |
494 | As argument the `@Pipe` decorator accepts an object literal with the following format:
495 |
496 | ```typescript
497 | interface PipeMetadata {
498 | name: string;
499 | pure: boolean;
500 | }
501 | ```
502 |
503 | The pure flag indicates that the pipe is not dependent on any global state and does not produce side-effects. This means that the pipe will return the same output when invoked with the same input. This way Angular can cache the outputs for all the input parameters the pipe has been invoked with, and reuse them in order to not have to recompute them on each evaluation.
504 |
505 | The default value of the `pure` property is `true`.
506 |
507 | ### `*ngFor` directive
508 |
509 | The `*ngFor` directive is used for rendering a collection.
510 |
511 | #### Use `trackBy` option
512 |
513 | By default `*ngFor` identifies object uniqueness by reference.
514 |
515 | Which means when a developer breaks reference to object during updating item's content Angular treats it as removal of the old object and addition of the new object. This effects in destroying old DOM node in the list and adding new DOM node on its place.
516 |
517 | The developer can provide a hint for angular how to identify object uniqueness: custom tracking function as the `trackBy` option for the `*ngFor` directive. The tracking function takes two arguments: `index` and `item`. Angular uses the value returned from the tracking function to track items identity. It is very common to use the ID of the particular record as the unique key.
518 |
519 | **Example**
520 |
521 | ```typescript
522 | @Component({
523 | selector: 'yt-feed',
524 | template: `
525 | Your video feed
526 |
527 | `
528 | })
529 | export class YtFeedComponent {
530 | feed = [
531 | {
532 | id: 3849, // note "id" field, we refer to it in "trackById" function
533 | title: "Angular in 60 minutes",
534 | url: "http://youtube.com/ng2-in-60-min",
535 | likes: "29345"
536 | },
537 | // ...
538 | ];
539 |
540 | trackById(index, item) {
541 | return item.id;
542 | }
543 | }
544 | ```
545 |
546 | #### Minimize DOM elements
547 |
548 | Rendering the DOM elements is usually the most expensive operation when adding elements to the UI. The main work is usually caused by inserting the element into the DOM and applying the styles. If `*ngFor` renders a lot of elements, browsers (especially older ones) may slow down and need more time to finish rendering of all elements. This is not specific to Angular.
549 |
550 | To reduce rendering time, try the following:
551 | - Apply virtual scrolling via [CDK](https://material.angular.io/cdk/scrolling/overview) or [ngx-virtual-scroller](https://github.com/rintoj/ngx-virtual-scroller)
552 | - Reducing the amount of DOM elements rendered in `*ngFor` section of your template. Usually, unneeded/unused DOM elements arise from extending the template again and again. Rethinking its structure probably makes things much easier.
553 | - Use [`ng-container`](https://angular.io/guide/structural-directives#ngcontainer) where possible
554 |
555 | **Resources**
556 |
557 | - ["NgFor directive"](https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html) - official documentation for `*ngFor`
558 | - ["Angular — Improve performance with trackBy"](https://netbasal.com/angular-2-improve-performance-with-trackby-cc147b5104e5) - shows gif demonstration of the approach
559 | - [Component Dev Kit (CDK) Virtual Scrolling](https://material.angular.io/cdk/scrolling/overview) - API description
560 | - [ngx-virtual-scroller](https://github.com/rintoj/ngx-virtual-scroller) - displays a virtual, "infinite" list
561 |
562 | ### Optimize template expressions
563 |
564 | Angular executes template expressions after every change detection cycle. Change detection cycles are triggered by many asynchronous activities such as promise resolutions, http results, timer events, keypresses, and mouse moves.
565 |
566 | Expressions should finish quickly or the user experience may drag, especially on slower devices. Consider caching values when their computation is expensive.
567 |
568 | **Resources**
569 | - [quick-execution](https://angular.io/guide/template-syntax#quick-execution) - official documentation for template expressions
570 | - [Increasing Performance - more than a pipe dream](https://youtu.be/I6ZvpdRM1eQ) - ng-conf video on youtube. Using pipe instead of function in interpolation expression
571 |
572 | # Conclusion
573 |
574 | The list of practices will dynamically evolve over time with new/updated practices. In case you notice something missing or you think that any of the practices can be improved do not hesitate to fire an issue and/or a PR. For more information please take a look at the "[Contributing](#contributing)" section below.
575 |
576 | # Contributing
577 |
578 | In case you notice something missing, incomplete or incorrect, a pull request will be greatly appreciated. For discussion of practices that are not included in the document please [open an issue](https://github.com/mgechev/angular2-performance-checklist/issues).
579 |
580 | # License
581 |
582 | MIT
583 |
--------------------------------------------------------------------------------
/README.pt-BR.md:
--------------------------------------------------------------------------------
1 | # Checklist de Performance do Angular
2 |
3 |
4 |
5 | [中文版](./README.zh-CN.md) [Русский](./README.ru-RU.md) [Português](./README.pt-BR.md) [Español](./readme-es-ES.md)
6 |
7 | ## Introdução
8 |
9 | Este documento contém uma lista de boas práticas que vão nos ajudar a melhorar a performance das nossas aplicações. O "Checklist de Performance do Angular" cobre diferentes tópicos - de pré renderização no servidor e criação do pacote da nossa aplicação até performance em tempo de execução e otimização do sistema de detecção de mudança feita pelo framework.
10 |
11 |
12 | Este documento é dividido em duas seções principais:
13 |
14 | - Performance de rede - Lista de práticas que vão melhorar o tempo de carregamento da nossa aplicação. Ela inclui métodos para redução de latência e consumo de rede.
15 |
16 | - Performance de Execução - Práticas que melhoram a performance de execução da nossa aplicação. Ela inclui principalmente otimizações de detecção de mudança e renderização
17 |
18 | Algumas práticas impactam nas duas categorias então pode haver alguma interceção, mas de qualquer forma, as diferenças nos casos de uso e implicações serão explicitamente mencionadas.
19 |
20 | A maioria das subseções com lista de ferramentas, relacionadas a prática específicas, podem nos fazer mais eficientes ao automatizar o nosso fluxo de desenvolvimento
21 |
22 | Note que a maioria das práticas são válidas tanto para HTTP/1.1 quanto para HTTP2. Práticas que servem apenas para um protocolo específico será mencionado a versão que ele se aplica.
23 |
24 |
25 | ## Sumário
26 |
27 | - [Checklist de Performance do Angular](#checklist-de-performance-do-angular)
28 | - [Introdução](#introdução)
29 | - [Sumário](#sumário)
30 | - [Performance de Rede](#performance-de-rede)
31 | - [Bundling](#bundling-empacotamento)
32 | - [Minificação e eliminação de código morto](#minificação-e-eliminação-de-código-morto)
33 | - [Remover espaços em branco do template](#remover-espaços-em-branco-do-template)
34 | - [Tree-shaking](#tree-shaking)
35 | - [Tree-shakeable providers](#tree-shakeable-providers)
36 | - [Compilação a Ahead-of-Time (AoT)](#compilação-a-ahead-of-time-aot)
37 | - [Compressão](#compressão)
38 | - [Pré Carregamento (Pre-fetching) de Recursos](#pré-carregamento-de-recursos)
39 | - [Carregamento Tardio de Recursos (Lazy Load)](#carregamento-tardio-de-recursos-lazy-load)
40 | - [Não use lazy-load com a rota padrão](#não-use-lazy-load-na-rota-padrão)
41 | - [Cache](#cache)
42 | - [Use uma casca da Aplicação](#use-uma-casca-da-aplicação)
43 | - [Use Service Workers](#use-service-workers)
44 | - [Otimizações de Execução](#otimizações-em-tempode-execução)
45 | - [Use o `enableProdMode`](#use-enableprodmode)
46 | - [Compilação Ahead-of-Time](#compilação-ahead-of-time-aot)
47 | - [Web Workers](#web-workers)
48 | - [Renderização no servidor (Server Side Rendering - SSR)](#renderização-no-servidor-server-side-rendering---ssr)
49 | - [Detecção de Mudança](#detecção-de-mudança)
50 | - [`ChangeDetectionStrategy.OnPush`](#changedetectionstrategyonpush)
51 | - [Removendo o Change Detector](##desacoplando-o-detector-de-mudança)
52 | - [Execute código fora do angular](#executar-fora-do-angular)
53 | - [Use pipes puros](#use-pipes-puros)
54 | - [Diretiva `*ngFor`](#diretiva-ngfor)
55 | - [Use a opção `trackBy`](#use-a-opção-trackby)
56 | - [Reduza a quantidade de elementos no DOM](#reduza-a-quantidade-de-elementos-no-dom)
57 | - [Otimize os template expressions ({{expression}})](#otimize-as-expressões-de-template-template-expressions)
58 | - [Conclusão](#conclusão)
59 | - [Contribuindo](#contribuindo)
60 |
61 | ## Performance de Rede
62 |
63 | algumas dessas ferramentas desta seção ainda estão em desenvolvimento e estão sujeitas a mudança. A equipe do core do Angular está trabalhando em automatizar o máximo possível o processo de build para que muitas coisas fiquem mais transparentes.
64 |
65 | ### Bundling (Empacotamento)
66 |
67 | Bundling ou empacotamento é uma prática padrão que visa reduzir o número de requisições que o browser precisa fazer para entregar a aplicação pedida pelo usuário. Na essência, o bundler recebe uma lista de entry points (Ex. Arquivos JS e CSS), junta esses arquivos e produz um ou mais bundles. Desta forma o browser pode baixar uma aplicação inteira fazendo apenas algumas requisições ao invés de baixar cada arquivo separado.
68 |
69 | Conforme a sua aplicação cresce, juntar tudo em um único bundle gigante pode ser contra-produtivo. Explore técnicas de Code Splitting utilizando o webpack.
70 |
71 |
72 | **Requisições http adicionais não são um problema no HTTP/2 por causa do recurso de [server push](https://http2.github.io/faq/#whats-the-benefit-of-server-push).**
73 |
74 | **Ferramentas**
75 |
76 | Ferramentas que nos permitem criar bundles para as nossas aplicações de forma eficiente:
77 |
78 | - [Webpack](https://webpack.js.org) - provê um bundle eficiente por fazer uso do [tree-shaking](#tree-shaking).
79 | - [Webpack Code Splitting](https://webpack.js.org/guides/code-splitting/) - Técnicas para dividir o seu código.
80 | - [Webpack & http2](https://medium.com/webpack/webpack-http-2-7083ec3f3ce6#.46idrz8kb) - Necessário para dividir o código usando http2.
81 | - [Rollup](https://github.com/rollup/rollup) - provê bundles fazendo uso eficiente do tree-shaking, tendo como vantagem a natuzera estática dos módulos ES2015.
82 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - faz várias otimizações e provê suporte a bundles. Originalmente escrito em Java, recentemente ganhou uma [Versão em javascript](https://www.npmjs.com/package/google-closure-compiler-js) que pode ser [encontrada aqui](https://www.npmjs.com/package/google-closure-compiler-js).
83 | - [SystemJS Builder](https://github.com/systemjs/builder) - provê um bundle de um único arquivo para módulos mixos de injeção de dependencia para o SystemJS
84 | - [Browserify](http://browserify.org/).
85 | - [ngx-build-modern](https://github.com/manfredsteyer/ngx-build-plus/tree/master/ngx-build-modern) - Plugin para o Angular-CLI que cria bundles de duas formas:
86 | 1. Para browsers modernos com módulos do ES2015 e polyfills específicos resultando em um bundle menor.
87 | 2. Código legado adicional usando diferentes polyfills (Padrão)
88 |
89 | **Recursos**
90 |
91 | - ["Construindo uma aplicação em Angular para Produção (Em Inglês)"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
92 | - ["Aplicações em Angular 2.5x menor com Google Closure Compiler (Em Inglês)"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
93 |
94 | ### Minificação e Eliminação de Código Morto
95 |
96 | Essas práticas nos permitem minimizar o consumo de rede reduzindo o tamanho (Payload) da nossa aplicação.
97 |
98 | **Ferramentas**
99 |
100 | - [Uglify](https://github.com/mishoo/UglifyJS) - Faz a minificação como reduzir o tamanho do nome das variáveis, remover espaços e comentários, eliminação de código morto, etc. Completamente escrito em JavaScript, tem vários plugins para todos os task runners populares.
101 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - Faz a minificação similar ao uglify. No modo avançado transforma agressivamente a árvore sintática abstrata da nossa aplicação para que seja feita otimizações mais sofisticadas. Também possui uma [versão em JavaScript](https://www.npmjs.com/package/google-closure-compiler-js) que pode ser [encontrada aqui](https://www.npmjs.com/package/google-closure-compiler-js). GCC também suporte *a maioria dos módulos do ES2015* então também pode [realizar o tree-shaking](#tree-shaking).
102 |
103 | **Recursos**
104 |
105 | - ["Construindo uma aplicação em Angular para Produção (Em Inglês)"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
106 | - ["Aplicações em Angular 2.5x menor com Google Closure Compiler (Em Inglês)"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
107 |
108 | ### Remover espaços em branco do template
109 |
110 | Mesmo que a gente não veja os espaços em branco ( um caracter que bata com a regex `\s`) eles ainda são representados em bytes que são transferidos pela rede. Se reduzirmos os espaços em branco dos nossos templates também reduziremos o tamanho do código da nossa aplicação.
111 |
112 | Ao menos não precisamos fazer isso manualmente. A interface `ComponentMetadata` provê a propriedade `preseveWhitespaces` que por padrão possui o valor `false`, porque os espaços em branco sempre podem influencia o layout do DOM. Caso mudamos esse valor para `true` o Angular vai remover os espaços em brancos desnecessários, levando a uma diminuição do tamanho final do bundle.
113 | - [preserveWhitespaces na documentação do Angular (Em inglês)](https://angular.io/api/core/Component#preserveWhitespaces)
114 |
115 |
116 | ### Tree-shaking
117 |
118 | Para a versão final das nossas aplicações, normalmente nós não usamos todo o código fornecido pelo Angular e/ou por bibliotecas de terceiros, até mesmo daquelas que nós escrevemos. Graças a natureza estática dos módulos ES2015, nós conseguimos nos livrar do código que não é utilizado nos nossos apps.
119 |
120 | **Exemplo**
121 |
122 | ```javascript
123 | // foo.js
124 | export foo = () => 'foo';
125 | export bar = () => 'bar';
126 |
127 | // app.js
128 | import { foo } from './foo';
129 | console.log(foo());
130 | ```
131 | Quando fazemos o tree-shake e compilamos o `app.js` nós temos
132 |
133 | ```javascript
134 | let foo = () => 'foo';
135 | console.log(foo());
136 | ```
137 |
138 | Isso significa que não vamos incluir no nosso bundle final o export `bar` não utiizado
139 |
140 | **Ferramentas**
141 |
142 | - [Webpack](https://webpack.js.org) - provê um bundle eficiente fazendo uso do [tree-shaking](#tree-shaking). Uma vez que a aplicação foi empacotada (bundled), ele não exporta o código não utilizado então pode seguramente se considerado código morto e removido pelo Uglify.
143 | - [Rollup](https://github.com/rollup/rollup) - provê um bundle eficiente fazendo uso do tree-shaking, tendo como vantagem a natureza estática dos módulos ES2015.
144 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - faz otimizações e tem suporte ao empacotamento (Bundling) Originalmente escrito em Java, recentemente ganhou uma [Versão em javascript](https://www.npmjs.com/package/google-closure-compiler-js) que pode ser [encontrada aqui](https://www.npmjs.com/package/google-closure-compiler-js).
145 |
146 | *nota:* GCC ainda não suporte `export *`, que é essencial na construções de aplicações em Angular por causa do uso do padrão "barrel".
147 |
148 | **Recursos**
149 |
150 | - ["Construindo uma aplicação em Angular para Produção (Em Inglês)"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
151 | - ["Aplicações em Angular 2.5x menor com Google Closure Compiler (Em Inglês)"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
152 | - ["Usando operadores encadeados (Pipeable) no RxJS (Em Ingles)"](https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md)
153 |
154 | ### Tree-Shakeable Providers
155 |
156 | Desde a versão 6 do Angular, o time do angular forneceu novas feature para permitir que as services possam fazer uso do tree-shake. Isso significa que as suas services não serão incluídas no seu bundle final a não ser que elas estejam sendo utilizadas por outras services ou componentes. Isso ajuda a reduzir o tamanho do bundle reduzindo a quantidade de código não usado.
157 |
158 | Você pode fazer as suas services usarem o tree-shake definindo o atributo `provideIn` com o lugar em que a service deve ser inicializada quando usando o decorator `@Injectable()`. Dessa forma, você pode removê-los do atributo `providers` do seu `NgModule` da seguinte forma:
159 |
160 |
161 | Antes:
162 |
163 | ```ts
164 | // app.module.ts
165 | import { NgModule } from '@angular/core'
166 | import { AppRoutingModule } from './app-routing.module'
167 | import { AppComponent } from './app.component'
168 | import { environment } from '../environments/environment'
169 | import { MinhaService } from './app.service'
170 |
171 | @NgModule({
172 | declarations: [
173 | AppComponent
174 | ],
175 | imports: [
176 | ...
177 | ],
178 | providers: [MinhaService],
179 | bootstrap: [AppComponent]
180 | })
181 | export class AppModule { }
182 | ```
183 |
184 | ```ts
185 | // minha-service.service.ts
186 | import { Injectable } from '@angular/core'
187 |
188 | @Injectable()
189 | export class MinhaService { }
190 | ```
191 |
192 | Depois:
193 |
194 | ```ts
195 | // app.module.ts
196 | import { NgModule } from '@angular/core'
197 | import { AppRoutingModule } from './app-routing.module'
198 | import { AppComponent } from './app.component'
199 | import { environment } from '../environments/environment'
200 |
201 | @NgModule({
202 | declarations: [
203 | AppComponent
204 | ],
205 | imports: [
206 | ...
207 | ],
208 | providers: [],
209 | bootstrap: [AppComponent]
210 | })
211 | export class AppModule { }
212 | ```
213 |
214 | ```ts
215 | // minha-service.service.ts
216 | import { Injectable } from '@angular/core'
217 |
218 | @Injectable({
219 | providedIn: 'root'
220 | })
221 | export class MinhaService { }
222 | ```
223 |
224 | Se `MinhaService` não for injetada em nenhum componente/service, ela não vai ser incluída no bundle final
225 |
226 | **Recursos**
227 |
228 | - [Angular Providers (Em Inglês)](https://angular.io/guide/providers)
229 |
230 | ### Compilação a Ahead-of-Time (AoT)
231 |
232 | Um desafio para as ferramentas existentes por aí(Como o GCC, Rollup, etc) são os templates em tipo-HTML dos componentes, que não podem ser analizados com as suas capacidades. Isso faz o suporte ao tree-shaking menos eficientes porque eles não sabem quais diretivas estão sendo utilizadas dentro dos templates. O Compilar AoT transpila os templates do Angular para JavaScript ou TypeScript com os imports do ES2015. Dessa forma, nós conseguimos fazer um tree-shake eficiente durante o processo de bundling e removemos todas as diretivas não utilizadas que foram definidas pelo Angular, bibliotecas de terceiros ou por nós mesmos.
233 |
234 |
235 | **Recursos**
236 |
237 | - ["Compilação Ahead-of-Time no Angular (Em Inglês)"](http://blog.mgechev.com/2016/08/14/ahead-of-time-compilation-angular-offline-precompilation/)
238 |
239 | ### Compressão
240 |
241 | Comprimir as respostas do servidor é uma prática para reduzir o consumo de banda. Ao especificar o valor do cabeçalho `Accept-Encoding`, o browser diz para o servidor quais são os algoritmos disponíveis na máquina do cliente. Do outro lado, o servidor seta o valor do cabeçalho `Content-Encoding` da resposta com a finalidade de dizer ao browser quais algoritmos foram escolhidos para comprimir as respostas.
242 | Compression of the responses' payload is a standard practice for bandwidth usage reduction. By specifying the value of the header `Accept-Encoding`, the browser hints the server which compression algorithms are available on the client's machine. On the other hand, the server sets value for the `Content-Encoding` header of the response in order to tell the browser which algorithm has been chosen for compressing the response.
243 |
244 | **Ferramentas**
245 |
246 | The Ferramentas here is not Angular-specific and entirely depends on the web/application server that we're using. Typical compression algorithms are:
247 |
248 | - deflate - a data compression algorithm and associated file format that uses a combination of the LZ77 algorithm and Huffman coding.
249 | - [brotli](https://github.com/google/brotli) - a generic-purpose lossless compression algorithm that compresses data using a combination of a modern variant of the LZ77 algorithm, Huffman coding and 2nd order context modeling, with a compression ratio comparable to the best currently available general-purpose compression methods. It is similar in speed with deflate but offers more dense compression.
250 |
251 | **Recursos**
252 |
253 | - ["Better than Gzip Compression with Brotli"](https://hacks.mozilla.org/2015/11/better-than-gzip-compression-with-brotli/)
254 | - ["2.5X Smaller Angular Applications with Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
255 |
256 | ### Pré-Carregamento de Recursos
257 |
258 | O pré-carregamento de recursos é uma ótima forma de melhorar a experiência do usuário. Nós podemos inclusive pré-carregar (pre-fetch) assets (imagens, css, módulos que serão carregados com [lazy load](#lazy-loading-of-Recursos), etc) ou dados. Existem diferentes estratégias de pré-carregamento mas a maioria depende da especificidade da aplicação
259 |
260 |
261 | ### Carregamento Tardio de Recursos (Lazy Load)
262 |
263 | Caso a aplicação tenha uma abse de código muito grande, com centenas de dependencias, a prática listada acima pode não nos ajudar a reduzir o tamanho do pacote da nossa aplicação para um tamanho aceitável (Aceitável pode ser 100K ou 2M. Isso, de novo, depende completamente do objetivo da aplicação)
264 |
265 | Nesses casos, uma boa solução pode ser carregar alguns comentes da aplicação tardiamente. Por exemplo, vamos supor que estamos construindo um sistema de ecommerce. Nesse caso, nós gostariamos de carregar o painel de administração independentemente da interface que os usuários irão ver. Uma vez que o administrador cadastro um novo produto, a gente quer fornecer a interface para aquele produto. Pode ser apenas a página de adicionar um produto ou o painel inteiro, dependendo dos requisitos do negócio.
266 |
267 | **Ferramentas**
268 |
269 | - [Webpack](https://github.com/webpack/webpack) - allows asynchronous module loading.
270 | - [ngx-quicklink](https://github.com/mgechev/ngx-quicklink) - router preloading strategy which automatically downloads the lazy-loaded modules associated with all the visible links on the screen
271 |
272 | ### Não use Lazy-Load na rota padrão
273 |
274 | Vamos supor que nós temos a seguinte configuração de rota
275 | Lets suppose we have the following routing configuration:
276 |
277 | ```ts
278 | // Má Prática
279 | const routes: Routes = [
280 | { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
281 | { path: 'dashboard', loadChildren: './dashboard.module#DashboardModule' },
282 | { path: 'heroes', loadChildren: './heroes.module#HeroesModule' }
283 | ];
284 | ```
285 |
286 | A primeira vez que o usuário abrir a aplicação usando a url: https://exemplo.com ele vão ser redirecionado para o `/dashboard` que vai disparar a rota `dashboard` que está configurada para carregar com lazy lado. Para o Angular conseguir renderizar o componente do módulo, ele vai ter que baixar o arquivo `dashboard.module` e todas as suas dependências. Depois, os arquivos serão lidos pelo JavaScript VM e então processados.
287 |
288 | Disparar uma requisição HTTP extra e fazer processamento desnecessário durante o carregamento da página inicial é uma má prática já que aumenta o tempo para renderizar a página inicial. Considere declarar a rota padrão sem usar o Lazy-Load
289 |
290 | ### Cache
291 |
292 |
293 | Cache é outra forma bem comum para acelerar a nossa aplicação tendo como heurística que se um recurso foi recentemente requisitado, ele pode ser requisitado em um futuro próximo.
294 | Para o cache, nós normalmente utilizamos macanismos customizados de Cache. Para arquivos estáticos nós podemos usar o cache padrão do navegador ou usar Service Workers com o [CacheStorage API](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
295 |
296 | ### Use uma Casca da Aplicação
297 |
298 | Para fazer a performance percebida da sua aplicação, use uma [Casca da Aplicação](https://developers.google.com/web/updates/2015/11/app-shell).
299 |
300 | A casca da aplicação é uma interface mínima, que mostra aos usuários como a aplicação será entregue a eles. Para gerar uma casca da aplicação dinamicamente, você pode usar o Angular Universao com diretivas customizadas que condicionalmente exibe os elementos dependendo da plataforma onde está sendo renderizados (Exemplo: Esconda tudo exceto a Casca da Aplicação quando estiver usando `platform-server`).
301 |
302 | **Ferramentas**
303 |
304 | - [Angular Service Worker](https://angular.io/guide/service-worker-intro) - aims to automate the process of managing Service Workers. It also contains Service Worker for caching static assets, and one for [generating application shell](https://developers.google.com/web/updates/2015/11/app-shell?hl=en).
305 | - [Angular Universal](https://github.com/angular/angular/tree/master/packages/platform-server) - Universal (isomorphic) JavaScript support for Angular.
306 |
307 | **Recursos**
308 |
309 | - [aInstant Loading Web Apps with an Application Shell Architecture"](https://developers.google.com/web/updates/2015/11/app-shell)
310 |
311 | ### Use Service Workers
312 |
313 | Nós podemos pensar no Service Worker com um proxy HTTP que fica no Browser. Todas as requisiçõs feitas do cliente são interceptadas pelo Service Worker que pode processá-las ou passar adiante para a rede.
314 |
315 | Você pode adicionar um Service Worker ao seu projeto ocm o seguinte comando
316 | ``` ng add @angular/pwa ```
317 |
318 |
319 | **Ferramentas**
320 |
321 | - [Angular Service Worker](https://angular.io/guide/service-worker-intro) - aims to automate the process of managing Service Workers. It also contains Service Worker for caching static assets, and one for [generating application shell](https://developers.google.com/web/updates/2015/11/app-shell?hl=en).
322 | - [Offline Plugin for Webpack](https://github.com/NekR/offline-plugin) - Webpack plugin that adds support for Service Worker with a fall-back to AppCache.
323 |
324 | **Recursos**
325 |
326 | - ["The offline cookbook"](https://jakearchibald.com/2014/offline-cookbook/)
327 | - ["Iniciando com Service Workers (Em Inglês)"](https://angular.io/guide/service-worker-getting-started)
328 |
329 | ## Otimizações em tempode execução
330 |
331 | Essa seção inclui práticas que podem ser aplicadas para fornecer uma experiencia fluída para os nossos usuários com 60 frames por segundo (fps)
332 |
333 | ### Use `enableProdMode`
334 |
335 | No modo de desenvolvimento, o Angular faz algumas checagens extras para verificar se o sistema de detecção de mudança não resultou em nenhuma diferença para nenhum dos bindings. Dessa forma, a framework garante que o fluxo unidirecional dos dados está sendo seguido.
336 |
337 | Para desabilitar essas checagens adicionais em produção, não se esqueça de executar `enableProdMode`:
338 |
339 | ```typescript
340 | import { enableProdMode } from '@angular/core';
341 |
342 | if (ENV === 'production') {
343 | enableProdMode();
344 | }
345 | ```
346 |
347 | ### Compilação Ahead-of-Time (AoT)
348 |
349 | AoT pode ser útil não apenas por fazer bundles mais eficientes usando o tree-shake, mas também por fornecer melhor performance em tempo de execução na nossa aplicação. A Alternativa ao AoT é a Compilação em Tempo de Execução (Just-in-Time [JiT]) que é feita durante a execução do código, portante nós podemos reduzir a quantidade de processamento necessária para a nossa aplicação fazendo com que a compilação seja parte do processo de build
350 |
351 | **Ferramentas**
352 |
353 | - [angular2-seed](https://github.com/mgechev/angular2-seed) - Projeto inicial com suporte a compilação AoT
354 | - [angular-cli](https://cli.angular.io) Usando o `ng serve --prod`
355 |
356 | **Recursos**
357 |
358 | - ["Ahead-of-Time Compilation in Angular"](http://blog.mgechev.com/2016/08/14/ahead-of-time-compilation-angular-offline-precompilation/)
359 |
360 | ### Web Workers
361 |
362 | Um problema típico das aplicações Single-Page (Single-Page Apllications [SPA]) é que nosso código por padrão roda em uma única thread. Isso significa que se nós quisermos fornecer uma experiência mais fluída aos nossos usuários com 60fps nós temos **no máximo 16ms** para executar cada frame que será renderizado, caso contrário essa valor cai pela metade.
363 |
364 | Em aplicações complexas com um DOM muito grande, onde o sistema de detecção de mudança precisa realizar milhares de checagens a cada segundo não vai ser muito difícil começar a perder frames. Graças a plataforma agnóstica do Angular e ele ser desacoplado da arquitetura do DOM é possível rodar a nossa aplicação inteira, (inclusive a detecção de mudanças) em um Web Worker e deixar a thread principal responsável apenas pela renderização da interface.
365 |
366 |
367 | **Ferramentas**
368 |
369 | - O módulo que nossa aplicação rode em um Web Worker é suportado pela equipe do Angular. Exemplo sobre como usar, podem [ser encontrados aqui (Em Inglês)](https://github.com/angular/angular/tree/master/modules/playground/src/web_workers).
370 | - [Webpack Web Worker Loader](https://github.com/webpack/worker-loader) -Um Loader Web Worker para o Webpack.
371 |
372 | **Recursos**
373 |
374 | - [“Usando Web Workers para um app mais responsivo" (Em Inglês)](https://www.youtube.com/watch?v=Kz_zKXiNGSE)
375 |
376 | ### Renderização no Servidor (Server-Side Rendering - SSR)
377 |
378 | Um grande problema das SPA é que elas não podem ser renderizadas até que todo o Javascript necessário para a renderização inicial esteja disponível. Isso nos leva a 2 grandes problemas:
379 |
380 | - Nem todos os motores de busca estão rodando o Javascript associado a página ent então elas não conseguem indexar adequadamente o conteúdo dinâmico da nossa aplicação
381 | - Má experiência para o usuário, já que ele não vai ver nada do que uma tela em branco ou carregando até que todo o JavaScript associado à página seja baixado, processado e executado.
382 |
383 | A renderização no servidor resolve esses problemas pré-renderizando estas páginas no servidor e fornecendo o conteúdo da página durante a etapa inicial de carregamento.
384 |
385 | **Ferramentas**
386 |
387 | - [Angular Universal](https://github.com/angular/angular/tree/master/packages/platform-server) - Suporte par aJavascript Universal (isomórfico) do Angular
388 | - [Preboot](https://github.com/angular/preboot) - Biblioteca que ajuda na transição dos estados (i.e. eventos, focos, dados)de uma view gerada no servidor para uma view gerada no cliente.
389 |
390 | **Recursos**
391 |
392 | - [“Rendererização no servidor com o Angular”](https://www.youtube.com/watch?v=0wvZ7gakqV4)
393 | - [“Padrões para o Angular Universal“](https://www.youtube.com/watch?v=TCj_oC3m6_U)
394 |
395 | ### Detecção de Mudança
396 |
397 | A cada evento assíncrono o Angular executa o sistema de detecção de mudança em toda a árvore de componentes. Mesmo que o código que detecte mudanças seja otimizado para [cache-inline](http://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html), isso ainda pode ser um bastante pesado em aplicações maiores. Uma forma de melhorar a performance da detecção de mudanças é não executá-lo nas subárvore que supostamente não foram alteradas baseadas nas ações recents
398 |
399 | #### `ChangeDetectionStrategy.OnPush`
400 |
401 | A estratégia `OnPush` nos permite desabilitar o mecanismo de detecção de mudança para as subárvores de uma árvore de componentes. Configurando essa estratágia para um componente, ela só vai disparar a detecção de mudança **apenas** quant o componente receber algum valor diferente. O Angular vai considerar uma entrada diferente quando comparar com o valor anterior por referencia, e se o resultado for `false`. Em conjunto com [estrutura de dados imutáveis] `OnPush` pode trazer grandes implicações de performance por usar componentes “puros” (Pure Components)
402 |
403 |
404 | **Recursos**
405 |
406 | - [“Deteção de mudança no angular (Em Inglês)"](https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c)
407 | - [“Tudo o que você precisa saber sobre a detecção de mudança no angular (Em Inglês)"](https://blog.angularindepth.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f)
408 |
409 | #### Desacoplando o detector de mudança
410 |
411 | Uma outra forma de implementar um mecanismo customizado de detecção de mudança é desacoplar ( detach) e reacoplar (reattach) o detector de mudança de um componente. Uma vez que descolamos o detector de mudança o Angular não vai mais verificar a árvore daquele componente.
412 | Essa prática é utilizada normalmente quando o as ações do usuário ou interações com serviços externos disparam a detecção de mudanças mais vezes do que o necessário. Nesse caso, nós podemos considerar remover o detector de mudanças e adicioná-lo, apenas quando for necessário verificar alguma alteração na aplicação.
413 |
414 |
415 | #### Executar fora do Angular
416 |
417 | O Mecanismo de detecção de mudança do Angular é disparado graças ao [zone.js](https://github.com/angular/zone.js). Zone.js faz o monkey patch de todas as APIs assíncronas no browser e executa a detecção de mudança no final de qualquer callback assíncrono. Em **casos raros** nós podemos querer pegar um código e executar fora do contexto do Angular Zone, sem executar o mecanismo de detecção de mudança. Nesses casos nós podemos usar o método `runOutsideAngular` na instância do `NgZone`.
418 |
419 | **Exemplo**
420 |
421 | No snippet abaixo, você pode pode ver um exemplo de um componente que usa essa prática. Quando o método `_incrementarPontos` é chamado, o componente vai começar a incrementar a propriedade `_points` a cada 10ms ( por padrão). Ao incrementar teremos uma ilusão de uma animação. Como não queremos que o angular dispare o mecanismo de deteção de mudanças para toda a árvore do componente a cada 10ms, nós podemos rodar a função `_incrementarPontos` fora do contexto da Zona do Angular e atualizar o DOM manualmente (Veja o setter de `pontos`)
422 |
423 |
424 | ```ts
425 | @Component({
426 | template: ''
427 | })
428 | class PointAnimationComponent {
429 |
430 | @Input() duracao = 1000;
431 | @Input() duracaoDoPasso = 10;
432 | @ViewChild('label') label: ElementRef;
433 |
434 | @Input() set points(val: number) {
435 | this._pontos = val;
436 | if (this.label) {
437 | this.label.nativeElement.innerText = this._pipe.transform(pontos, '1.0-0');
438 | }
439 | }
440 | get pontos() {
441 | return this.pontos;
442 | }
443 |
444 | private _intervaloDeIncremento: any;
445 | private _pontos: number = 0;
446 |
447 | constructor(private _zone: NgZone, private _pipe: DecimalPipe) {}
448 |
449 | ngOnChanges(changes: any) {
450 | const change = changes.pontos;
451 | if (!change) {
452 | return;
453 | }
454 | if (typeof change.previousValue !== 'number') {
455 | this.pontos = change.currentValue;
456 | } else {
457 | this.pontos = change.previousValue;
458 | this._ngZone.runOutsideAngular(() => {
459 | this._incrementarPontos(change.currentValue);
460 | });
461 | }
462 | }
463 |
464 | private _incrementarPontos(novoValor: number) {
465 | const dif = novoValor - this.pontos;
466 | const passo = this.duracaoDoPasso * (dif / this.duracao);
467 | const pontosIniciais= this.pontos;
468 | this._intervalorDeIncremento = setInterval(() => {
469 | let proximosPontos = Math.ceil(pontosIniciais + dif);
470 | if (this.points >= nextPoints) {
471 | this.points = initialPoints + diff;
472 | clearInterval(this._intervalorDeIncremento);
473 | } else {
474 | this.pontos += passo;
475 | }
476 | }, this.duracaoDoPasso);
477 | }
478 | }
479 | ```
480 |
481 | **Aviso**: Use essa prática **com muito cuidado e somente quando tiver certeza do que está fazendo** porque se não usado da forma correta pode levar a um estado inconsistent do DOM. Note também que o código acima não vai rodar em um WebWorker. Para fazer ele compatível com um WebWorker, você precisa alterar o valor da label usando o Renderer do Angular.
482 |
483 | ### Use pipes puros
484 |
485 |
486 | O decorator `@Pipe`aceita um objeto literal como argumento usando o seguinte formato:
487 |
488 | ```typescript
489 | interface PipeMetadata {
490 | name: string;
491 | pure: boolean;
492 | }
493 | ```
494 |
495 | A flag `pure` indica que o pipe não depende de nenhum estado global e não produz efeitos colaterais (Side-effects). Isso significa que o pipe vai retornar sempre o mesmo resultado quando chamado passando os mesmos valores. Dessa forma, o Angular pode fazer cache das saídas de todos os parâmetros de entradas passados para um pipe que já foi chamado, e reusar esse valor no lugar de recalcula-los em cada execução
496 |
497 | O valor padrão da propriedade `pure` é `true`.
498 |
499 | ### Diretiva `*ngFor`
500 |
501 | A diretiva `*ngFor` é usada para renderizar uma coleção
502 |
503 | #### Use a opção `trackBy`
504 |
505 | Por padrão o `*ngFor` identifica a unicidade de um objeto por referência.
506 | O que significa que quando a desenvolvedora quebra a referencia de um objeto ao atualizar o seu conteúdo, o Angular trata ele como a remoção de um antigo objeto e adição de um novo. O efeito disso é remover o nó do DOM da lista e adicionar um novo nó ao DOM em seu lugar.
507 |
508 | A desenvolvedor pode fornecer uma dica para o angular identificar a unicidade de um objeto: Fornecer uma função de rastreamento customizada na opção `trackBy` para a diretiva `*ngFor`. Essa função recebe dois argumentos: `index` e `item`. O Angular usa o valor retornado da função de rastreamento para mapear a identidade dos itens. É muito comum utilizar a propriedade Id de um registro como a chave de unicidade.
509 |
510 | **Exemplo**
511 |
512 | ```typescript
513 | @Component({
514 | selector: 'yt-feed',
515 | template: `
516 | Seus vídeos
517 |
518 | `
519 | })
520 | export class YtFeedComponent {
521 | feed = [
522 | {
523 | id: 3849, // Note o campo “id”. Nós vamos referenciá-la na função “trackById”
524 | title: "Angular em 60 minutos”,
525 | url: "http://youtube.com/ng2-in-60-min",
526 | likes: "29345"
527 | },
528 | // ...
529 | ];
530 |
531 | trackById(index, item) {
532 | return item.id;
533 | }
534 | }
535 | ```
536 |
537 | #### Reduza a quantidade de elementos no DOM
538 |
539 | Normalmente renderizar os elementos do DOM é a operação mais pesadas ao adicionar elementos à interface. A maior parte do trabalho é causada por inserir elementos no dom aplicando novos estilos. Se um `*ngFor` renderiza vários elementos, os navegadores (especialmente os antigos) podem ficar lentos e precisar de mais tempo para renderizar todos os elementos. Isso não é específico do Angular.
540 | Rendering the DOM elements is usually the most expensive operation when adding elements to the UI.
541 |
542 | Para reduzir o tempo de renderização, tente o seguinte
543 |
544 |
545 | - Aplicar Virtual Scrolling usando o [CDK](https://material.angular.io/cdk/scrolling/overview) ou [ngx-virtual-scroller](https://github.com/rintoj/ngx-virtual-scroller)
546 | - Reduzir a quantidade de elementos do DOM renderizados no `*ngFor` do seu template. Normalmente elementos não utilizados ou desnecessários aparecem ao extender um template várias vezes. Repensar a estrutura provavelmente vai fazer as coisas ficarem mais fáceis
547 | - Use [`ng-container`](https://angular.io/guide/structural-directives#ngcontainer) onde possível
548 |
549 | **Recursos**
550 |
551 | - [“Diretiva NgFor”](https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html) - documentação oficial do `*ngFor`
552 | - [“Angular - Melhorando a performance com o trackBy"](https://netbasal.com/angular-2-improve-performance-with-trackby-cc147b5104e5) - Exibe algumas gifs com esta prática
553 | - [Component Dev Kit (CDK) Virtual Scrolling](https://material.angular.io/cdk/scrolling/overview) - Descrição da API
554 | - [ngx-virtual-scroller](https://github.com/rintoj/ngx-virtual-scroller) - Exibe uma lista infinita virtualmente
555 |
556 | ### Otimize as expressões de template (Template Expressions)
557 |
558 | Angular executa as expressões de template a cada ciclo de detecção de mudança. Os ciclos de detecção de mudança são disparados por várias atividades assíncronas, como promises que foram resolvidas, resultado de uma requisição http, eventos de timer, teclas que foram apertadas e movimento do mouse.
559 |
560 | As expressões devem terminar rapidamente ou o usuário pode ter uma experiência ruim, especialmente em dispositivos mais lentos. Consider fazer cache de valores quando for necessário um processamento mais pesado.
561 |
562 | **Recursos**
563 | - [quick-execution](https://angular.io/guide/template-syntax#quick-execution) - Documentação oficial das expressões de template
564 | - [Melhorando a Performance - Mais do que um sonho(Em inglês)](https://youtu.be/I6ZvpdRM1eQ) - Vídeo do ng-conf no youtube. Usando um pipe ao invés de uma função interpolada.
565 |
566 | # Conclusão
567 |
568 | Esta lista de práticas vai crescer dinamicamente ao longo do tempo com novas práticas ou atualizações. Caso você note alguma coisa faltando ou ache que estas práticas podem ser melhoradas não elite em criar uma issue ou pull request. Para mais informações veja a seção “[Contribuindo](#contribuindo)” abaixo.
569 |
570 | # Contribuindo
571 |
572 | Caso você note algo faltando, incompleto ou incorreto um pull request vai ser imensamente apreciado. Para discutir as práticas que não estão inclusas neste documento por favor [abra uma issue](https://github.com/mgechev/angular2-performance-checklist/issues).
573 |
574 | # Licença
575 |
576 | MIT
577 |
578 |
--------------------------------------------------------------------------------
/README.ru-RU.md:
--------------------------------------------------------------------------------
1 | # Angular Performance Checklist
2 |
3 |
4 |
5 | ## Вступление
6 |
7 | В этой статье описаны полезные практики, которые помогут вам улучшить производительность ваших приложений на Angular. "Angular Performance Checklist" покрывает множество вопросов — от server-side pre-rendering и сборки приложений, до производительности в runtime и оптимизации change detection, который выполняется Angular.
8 |
9 | Эта статья разделена на два основных блока:
10 |
11 | - Network performance содержит в себе список практик, следуя которым, вы ускорите загрузку ваших приложений. Он также включает в себя способы оптимизации задержек и повышает эффективность в условиях медленого интернета.
12 | - Runtime performance - содержит в себе практики, которые улучшат производительность ваших приложений в runtime. Они включают в себя оптимизации change detection и rendering.
13 |
14 | Некоторые методы оптимизаций могут находиться сразу в двух котегориях, поэтому может быть небольшое пересечение. Однако, в этом случае будут перечислены различия в вариантах использования, а также их последствия.
15 |
16 | Большинство инструментов связаны со специфичными проблемами. Эти инструменты помогут вам улучшить качество разработки за счет автоматизации процесса.
17 |
18 | Обратите внимание, что большинство практик применимы к HTTP/1.1 и HTTP/2. В практиках, где делаются исключения, будут пометки о том, для какой версии протокола они предназначены.
19 |
20 | ## Содержание
21 |
22 | - [Angular Performance Checklist](#angular-performance-checklist)
23 | - [Вступление](#%D0%B2%D1%81%D1%82%D1%83%D0%BF%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5)
24 | - [Содержание](#%D1%81%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D0%BD%D0%B8%D0%B5)
25 | - [Network performance](#network-performance)
26 | - [Bundling](#bundling)
27 | - [Minification and dead code elimination](#minification-and-dead-code-elimination)
28 | - [Remove template whitespace](#remove-template-whitespace)
29 | - [Tree-shaking](#tree-shaking)
30 | - [Tree-shakeable providers](#tree-shakeable-providers)
31 | - [Ahead-of-Time (AoT) Compilation](#ahead-of-time-aot-compilation)
32 | - [Compression](#compression)
33 | - [Pre-fetching Resources](#pre-fetching-resources)
34 | - [Lazy-Loading of Resources](#lazy-loading-of-resources)
35 | - [Don't Lazy-Load the Default Route](#dont-lazy-load-the-default-route)
36 | - [Caching](#caching)
37 | - [Use Application Shell](#use-application-shell)
38 | - [Use Service Workers](#use-service-workers)
39 | - [Runtime Optimizations](#runtime-optimizations)
40 | - [Use `enableProdMode`](#use-enableprodmode)
41 | - [Ahead-of-Time Compilation](#ahead-of-time-compilation)
42 | - [Web Workers](#web-workers)
43 | - [Server-Side Rendering](#server-side-rendering)
44 | - [Change Detection](#change-detection)
45 | - [`ChangeDetectionStrategy.OnPush`](#changedetectionstrategyonpush)
46 | - [Detaching the Change Detector](#detaching-the-change-detector)
47 | - [Run outside Angular](#run-outside-angular)
48 | - [Use pure pipes](#use-pure-pipes)
49 | - [`*ngFor` directive](#ngfor-directive)
50 | - [Use `trackBy` option](#use-trackby-option)
51 | - [Minimize DOM elements](#minimize-dom-elements)
52 | - [Optimize template expressions](#optimize-template-expressions)
53 | - [Итоги](#%D0%B8%D1%82%D0%BE%D0%B3%D0%B8)
54 | - [Contributing](#contributing)
55 | - [License](#license)
56 |
57 | ## Network performance
58 |
59 | Некоторые из инструментов в этом разделе все еще находятся в разработке и в будущем могут быть изменены. Команда разработчиков Angular занимается тем, чтобы максимально автоматизировать процесс сборки для наших приложений и сделать большинство вещей проще в использовании.
60 |
61 | ### Bundling
62 |
63 | Bundling - это стандартная практика, направленная на уменьшение количества запросов браузером, которые он должен выполнить для загрузки приложения. По сути, в качестве входных параметров, bundler получает список модулей. Таким образом, браузер может загрузить все приложение, выполнив всего несколько запросов, вместо того, чтобы по отдельности запрашивать каждый модуль.
64 |
65 | Скорее всего, по мере разработки вашего приложения, объединение всех модулей в один станет не эффективным. Поэтому рассмотрите Code Splitting, который можно сделать с помощью Webpack.
66 |
67 | **Дополнительные http запросы не будут происходить в HTTP/2 из-за его функции [server push](https://http2.github.io/faq/#whats-the-benefit-of-server-push).**
68 |
69 | **Инструменты**
70 |
71 | Инструменты, которые позволяют эффективно упаковывать в модуль ваше приложение:
72 |
73 | - [Webpack](https://webpack.js.org) - обеспечивает эффективное объединение кода выполняя [tree-shaking](#tree-shaking).
74 | - [Webpack Code Splitting](https://webpack.js.org/guides/code-splitting/) - технология для разделения вашего кода.
75 | - [Webpack & http2](https://medium.com/webpack/webpack-http-2-7083ec3f3ce6#.46idrz8kb) - требуется для разделения кода в HTTP/2.
76 | - [Rollup](https://github.com/rollup/rollup) - позволяет объединять код, применяя tree-shaking, и используя преимущество статичных импортов модулей ES2015.
77 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - выполняет множество оптимизаций и обеспечивает объединение кода. Изначально был написан на Java, но также имеет реализацию на [JavaScript](https://www.npmjs.com/package/google-closure-compiler-js), которую можете [найти здесь](https://www.npmjs.com/package/google-closure-compiler-js).
78 | - [SystemJS Builder](https://github.com/systemjs/builder) - обеспечивает сборку приложения в один файл с помощью SystemJS и имеет поддержку зависимостей с различными версиями.
79 | - [Browserify](http://browserify.org/).
80 | - [ngx-build-modern](https://github.com/manfredsteyer/ngx-build-plus/tree/master/ngx-build-modern) - плагин для Angular CLI, который может собирать приложение в двух версиях:
81 | 1. Для современных браузеров с модулями ES2015 и основные полифиламы, что делает bundle меньше;
82 | 2. Дополнительная легаси версия, использующая остальные полифилы и другой compiler target (по-умолчанию).
83 |
84 | **Полезные материалы**
85 |
86 | - ["Сборка Angular приложения для Production"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
87 | - ["Сборка Angular приложения в 2.5X меньше вместе с Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
88 |
89 | ### Minification and dead code elimination
90 |
91 | В случае медленного интернет соединения эти методы позволяют нам оптимизировать загрузку приложения за счет уменьшения его веса.
92 |
93 | **Инструменты**
94 |
95 | - [Uglify](https://github.com/mishoo/UglifyJS) - делает минификацию кода, a именно уменьшает размер переменных, удаляет комментарии и пробелы, а также мертвый код и т.д. Он написан полностью на JavaScript, и имеет плагины для всех популярных task runners.
96 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - работает аналогично uglify. В продвинутом режиме он принудительно преобразует AST вашего приложения, чтобы проводить еще более сложные оптимизации. Он так же имеет [JavaScript версию](https://www.npmjs.com/package/google-closure-compiler-js), которую можно [найти здесь](https://www.npmjs.com/package/google-closure-compiler-js). GCC имеет *почти полную поддержку модулей ES2015*, поэтому может [делать tree-shaking](#tree-shaking).
97 |
98 | **Полезные материалы**
99 |
100 | - ["Сборка Angular приложения для Production"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
101 | - ["Сборка Angular приложения в 2.5X меньше вместе с Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
102 |
103 | ### Remove template whitespace
104 |
105 | Хотя мы и не видим символ пробела (соотвествующий регулярному выражению `\s`), он все еще представлен байтами, которые передаются по сети. Однако, если мы максимально уменьшим количество пустых значений в шаблонах, то мы сможем уменьшить размер итогового AoT-кода.
106 |
107 | К счастью, нам не нужно делать это вручную. В интерфейсе `ComponentMetadata` есть свойство `preserveWhitespaces`. Так как удаление пробелов может повлиять на DOM, оно по умолчанию имеет значение `false`. В случае, если мы установим свойство в `true`, то Angular очистит код от ненужных пробелов, что приведет к дополнительному уменьшению размера модуля.
108 |
109 | - [Об preserveWhitespaces в документации Angular](https://angular.io/api/core/Component#preserveWhitespaces)
110 |
111 | ### Tree-shaking
112 |
113 | В собранной версии приложения обычно не нужен весь код, который есть в Angular, сторонних библиотеках, или даже тот, который мы сами написали. Поэтому благодаря тому, что при импорте модулей ES2015 явно указывается что именно импортируется, можно избавиться от кода, который не был использован в приложении.
114 |
115 | **Пример**
116 |
117 | ```javascript
118 | // foo.js
119 | export foo = () => 'foo';
120 | export bar = () => 'bar';
121 |
122 | // app.js
123 | import { foo } from './foo';
124 | console.log(foo());
125 | ```
126 | После tree-shaking и сборки `app.js` мы получим:
127 |
128 | ```javascript
129 | let foo = () => 'foo';
130 | console.log(foo());
131 | ```
132 |
133 | Это значит, что не использованный экспорт `bar` не будет включен в bundle.
134 |
135 | **Инструменты**
136 | - [Webpack](https://webpack.js.org) - предоставляет эффективную сборку с использованием [tree-shaking](#tree-shaking). После сборки приложения не экспортируется код, который не был использован. Таким образом код может быть помечен как dead code и удален с помощью Uglify.
137 | - [Rollup](https://github.com/rollup/rollup) - предоставляет сборку с использованием tree-shaking, за счет статических импортов модулей ES2015.
138 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - предлагает множество оптимизаций и предоставляет возможность сборки приложения. Изначально он был написан на Java, но с недавнего времени поддерживает и [версию для JavaScript](https://www.npmjs.com/package/google-closure-compiler-js).
139 |
140 | *Обратите внимание:* GCC еще не поддерживает `export *`. Однако функция важна для сборки Angular приложений из-за широкого использования "barrel" файлов.
141 |
142 | **Полезные материалы**
143 |
144 | - ["Сборка Angular приложения для Production"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
145 | - ["Сборка Angular приложения в 2.5X меньше вместе с Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
146 | - ["Использование pipeable операторов в RxJS"](https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md)
147 |
148 | ### Tree-shakeable providers
149 |
150 | Начиная с версии Angular 6, команда Angular представила новую фичу, которая позволяет делать tree-shakable сервисы. Это значит, что сервисы не будут включены в финальный бандл пока они не будут использованы другими сервисами или компонентами. Это помогает уменьшить размер итогового бандла за счет удаления неиспользуемого кода.
151 |
152 | Используя аттрибут `providedIn` в декораторе `@Injectable()` можно определить место, где сервис должен быть инициализирован и сделать его tree-shakeable. После этого нужно удалить его из аттрибута `providers` в инициализации `NgModule`, а также из импортов в файле `NgModule`.
153 |
154 | До:
155 |
156 | ```ts
157 | // app.module.ts
158 | import { NgModule } from '@angular/core'
159 | import { AppRoutingModule } from './app-routing.module'
160 | import { AppComponent } from './app.component'
161 | import { environment } from '../environments/environment'
162 | import { MyService } from './app.service'
163 |
164 | @NgModule({
165 | declarations: [
166 | AppComponent
167 | ],
168 | imports: [
169 | ...
170 | ],
171 | providers: [MyService],
172 | bootstrap: [AppComponent]
173 | })
174 | export class AppModule { }
175 | ```
176 |
177 | ```ts
178 | // my-service.service.ts
179 | import { Injectable } from '@angular/core'
180 |
181 | @Injectable()
182 | export class MyService { }
183 | ```
184 |
185 | После:
186 |
187 | ```ts
188 | // app.module.ts
189 | import { NgModule } from '@angular/core'
190 | import { AppRoutingModule } from './app-routing.module'
191 | import { AppComponent } from './app.component'
192 | import { environment } from '../environments/environment'
193 |
194 | @NgModule({
195 | declarations: [
196 | AppComponent
197 | ],
198 | imports: [
199 | ...
200 | ],
201 | providers: [],
202 | bootstrap: [AppComponent]
203 | })
204 | export class AppModule { }
205 | ```
206 |
207 | ```ts
208 | // my-service.service.ts
209 | import { Injectable } from '@angular/core'
210 |
211 | @Injectable({
212 | providedIn: 'root'
213 | })
214 | export class MyService { }
215 | ```
216 |
217 | Если `MyService` не используется ни в одном компоненте/сервисе/директиве, то он не будет включен в итоговый bundle.
218 |
219 | **Полезные материалы**
220 |
221 | - [Angular Providers](https://angular.io/guide/providers)
222 |
223 | ### Ahead-of-Time (AoT) Compilation
224 |
225 | Проблемой низкоуровневых инструментов (таких как GCC, Rollup и т.д.) является то, что они не анализируют HTML-подобные шаблоны Angular компонентов. Это делает менее эффективной поддержку tree-shaking, потому что они не знают, на какие директивы имеются ссылки в шаблонах. Компилятор AoT конвертирует HTML-подобные шаблоны в JavaScript или TypeScript с импортами ES2015 модулей. Таким образом, мы можем эффективно делать tree-shaking во время сборки и удалять все неиспользуемые директивы, которые могут быть определенны Angular'ом, сторонними библиотеками или нашим приложением.
226 |
227 | **Полезные материалы**
228 |
229 | - ["Ahead-of-Time Compilation в Angular"](http://blog.mgechev.com/2016/08/14/ahead-of-time-compilation-angular-offline-precompilation/)
230 |
231 | ### Compression
232 |
233 | Сжатие ответов является стандартной практикой уменьшения используемого трафика для загрузки приложения. Указав заголовок `Accept-Encoding`, браузер говорит серверу, какие алгоритмы сжатия он поддерживает на клиентском компьютере. В свою очередь сервер в заголовке ответа устанавливает значение для `Content-Encoding`, чтобы сообщить браузеру, какой алгоритм сжатия был применен.
234 |
235 | **Инструменты**
236 |
237 | Инструменты, приведенные здесь, не являются специфичными для Angular, и полностью зависит от используемого веб сервера. И вот основные инструменты для сжатия:
238 |
239 | - deflate - алгоритмы сжатия данных, связанных с конкретным форматом файла, который использует комбинацию алгоритма LZ77 и Код Хаффмана.
240 | - [brotli](https://github.com/google/brotli) - алгоритм сжатия общего назначения без потерь, который сжимает данные, используя комбинацию современного варианта алгоритма LZ77, Кода Хаффмана и моделирование контекста 2-го порядка, со степенью сжатия, сопостовимой с лучшими в настоящее время способами сжатия общего назначения. Это сравнимо по скорости с deflate, но имеет лучшее сжатие.
241 |
242 | **Полезные материалы**
243 |
244 | - ["Сжатие с использованием Brotli лучше, чем Gzip"](https://hacks.mozilla.org/2015/11/better-than-gzip-compression-with-brotli/)
245 | - ["Сборка Angular приложения в 2.5X меньше вместе с Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
246 |
247 | ### Pre-fetching Resources
248 |
249 | Предзагрузка ресурсов это отличный способ улучшить user experience. Мы можем загружать заранее как ассеты (изображения, стили, модули предназначенные для [lazy load](#lazy-loading-of-resources) и т.д.), так и данные. Существуют различные стратегии предзагрузки, но в большинстве случаев их использование зависит от специфики вашего приложения.
250 |
251 | ### Lazy-Loading of Resources
252 |
253 | Когда приложение обладает большой кодовой базой с сотней зависимостей, подходы, описанные выше, могут оказаться бесполезными с точки зрения снижения размеров бандла (до разумных показателей 100кб или 2мб, но это полностью зависит от бизнес целей).
254 |
255 | В таком случае разумно подгружать модули частично, лениво. Например, допустим, разрабатываемое приложение - это площадка для электронной торговли. В таком случае мы бы хотели, чтобы панель администратора загружалась независимо от интерфейса пользователя. Если, например, администратор должен добавить новый продукт, мы бы хотели обеспечить загрузку только необходимого для этого модуля. Это могла бы быть просто страница с добавлением продукта или вся панель администратора, в зависимости от бизнес логики приложения.
256 |
257 | **Инструменты**
258 |
259 | - [Webpack](https://github.com/webpack/webpack) - обеспечивает асинхронную загрузку модулей
260 | - [ngx-quicklink](https://github.com/mgechev/ngx-quicklink) - стратегия предварительной загрузки роутера, которая обеспечивает автоматическую ленивую загрузку модулей, связанных со всеми видимыми ссылками на экране
261 |
262 | ### Don't Lazy-Load the Default Route
263 |
264 | Предположим, имеется следующая конфигурация роутинга:
265 |
266 | ```ts
267 | // Плохой пример
268 | const routes: Routes = [
269 | { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
270 | { path: 'dashboard', loadChildren: './dashboard.module#DashboardModule' },
271 | { path: 'heroes', loadChildren: './heroes.module#HeroesModule' }
272 | ];
273 | ```
274 |
275 | В первый раз пользователь открывает приложения, используя адрес: https://example.com/. После этого он будет перенаправлен на `/dashboard`, после чего будет произведена ленивая загрузка `DashboardModule`.
276 |
277 | Для того, чтобы Angular отобразил начальный компонент модуля, необходимо загрузить файл `dashboard.module` и все его зависимости. После этого файл должен быть проанализирован виртуальной машиной JavaScript и оценен.
278 |
279 | Запуск дополнительных HTTP-запросов и выполнение ненужных вычислений во время начальной загрузки страницы является плохой практикой, поскольку она замедляет стартовый рендеринг страницы. Поэтому рассмотрите возможность объявления страницы по умолчанию в обход ленивой загрузки модулей.
280 |
281 | ### Caching
282 |
283 | Кэширование - это еще одна распространенная практика, направленная на ускорение работы нашего приложения за счет использования предположения о том, что если недавно был запрошен один ресурс, он может быть запрошен снова в ближайшем будущем.
284 |
285 | Для кэширования данных мы обычно используем кастомные методы. Для кэширования статических ресурсов мы можем использовать стандартные механизмы в браузере или Service Workers с [CacheStorage API](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
286 |
287 | ### Use Application Shell
288 | Для того, чтобы быстрее отобразить пользователю часть страницы используйте [Application Shell](https://developers.google.com/web/updates/2015/11/app-shell).
289 |
290 | Application Shell - это минимальный пользовательский интерфейс, который мы показываем пользователям, чтобы показать, что приложение будет доступно в ближайшее время. Для динамического создания оболочки приложения вы можете использовать Angular Universal с пользовательскими директивами, которые по условиям отображают элементы в зависимости от используемой платформы рендеринга (т.е. скрывают все, кроме оболочки приложения, при использовании `platform-server`).
291 |
292 | **Инструменты**
293 |
294 | - [Angular Service Worker](https://angular.io/guide/service-worker-intro) - стремится автоматизировать процесс настройки Service Workers. Он включает в себя Service Worker для кэширования статичных ресурсов и инструмент для [генерации application shell](https://developers.google.com/web/updates/2015/11/app-shell?hl=en).
295 | - [Angular Universal](https://github.com/angular/angular/tree/master/packages/platform-server) - Universal (изоморфный) JavaScript для Angular.
296 |
297 | **Полезные материалы**
298 |
299 | - ["Мгновенная загрузка приложения с Application Shell Architecture"](https://developers.google.com/web/updates/2015/11/app-shell)
300 |
301 | ### Use Service Workers
302 |
303 | Мы думаем о Service Worker, как о HTTP-прокси, который находится в браузере. Все запросы, которые отправляются клиентом, перехватываются Service Worker. Он может обработать их или передать дальше по сети.
304 |
305 | **Инструменты**
306 |
307 | - [Angular Service Worker](https://angular.io/guide/service-worker-intro) - направлен на автоматизацию процесса управления Service Worker. Он так же содержит Service Worker для кэширования статических ресурсов и [генерацию application shell](https://developers.google.com/web/updates/2015/11/app-shell?hl=en).
308 | - [Offline Plugin для Webpack](https://github.com/NekR/offline-plugin) - Webpack плагин добавляющий поддержку Service Worker с fall-back для AppCache.
309 |
310 | **Полезные материалы**
311 |
312 | - ["The offline cookbook"](https://jakearchibald.com/2014/offline-cookbook/)
313 |
314 | ## Runtime Optimizations
315 |
316 | В этом разделе приведены рекомендации, которые необходимы для обеспечания плавной работы UI со скоростью 60 кадров в секунду (fps).
317 |
318 | ### Use `enableProdMode`
319 |
320 | В development режиме Angular вызывает дополнительные проверки изменений, чтобы убедиться, что change detection не приводит к каким-либо дополнительным изменениям. Таким образом, Angular гарантирует, что соблюден однонаправленный поток данных.
321 |
322 | Чтобы отключить эти проверки для production, не забудьте вызвать `enableProdMode`:
323 |
324 | ```typescript
325 | import { enableProdMode } from '@angular/core';
326 |
327 | if (ENV === 'production') {
328 | enableProdMode();
329 | }
330 | ```
331 |
332 | ### Ahead-of-Time Compilation
333 |
334 | AoT может быть не только полезен для достижения более эффективной сборки приложения, путем применения tree-shaking, но также для повышения производительности наших приложений в runtime. Альтернативой AoT является компиляция Just-in-Time (JiT), который выполняется в runtime. Поэтому AoT позволяет уменьшить количество вычислений, необходимых для рендеринга нашего приложения, выполняя компиляцию во время сборки.
335 |
336 | **Инструменты**
337 |
338 | - [angular2-seed](https://github.com/mgechev/angular2-seed) - стартер с поддержкой AoT компиляции.
339 | - [angular-cli](https://cli.angular.io) - использование `ng serve --prod`
340 |
341 | **Полезные материалы**
342 |
343 | - ["Ahead-of-Time Compilation в Angular"](http://blog.mgechev.com/2016/08/14/ahead-of-time-compilation-angular-offline-precompilation/)
344 |
345 | ### Web Workers
346 |
347 | Проблема типичного одностраничного приложения (SPA) заключается в том, что код выполняется в одом потоке. Это означает, что если мы хотим добиться плавного UX с 60fps, то у нас есть **максимум 16мс** для выполнения вычислений между кадрами. В противном случае UI будет тормозить.
348 |
349 | В сложном приложении с серьезным деревом компонентов, где change detection должно выполнять миллионы проверок ежесекундно, нетрудно потерять целые кадры. Благодаря абстрагированности платформы Angular, а именно тому, что она отделена от архитектуры DOM, можно запустить наше приложение (включая change detection) в Web Worker, оставив основной поток ответственным только за рендеринг UI.
350 |
351 | **Инструменты**
352 |
353 | - Модуль, который позволяет запускать приложение в Web Worker, поддерживается командой Angular. Примеры использования, можно [найти здесь](https://github.com/angular/angular/tree/master/modules/playground/src/web_workers).
354 | - [Webpack Web Worker Loader](https://github.com/webpack/worker-loader) - загрузчик Web Worker для webpack.
355 |
356 | **Полезные материалы**
357 |
358 | - ["Использование Web Workers для большей отзывчивости приложений"](https://www.youtube.com/watch?v=Kz_zKXiNGSE)
359 |
360 | ### Server-Side Rendering
361 |
362 | Большая проблема традиционных SPA заключается в том, что их содержимое не может быть отрисовано пока не загрузится весь JavaScript, потому что весь рендеринг происходит после. Отсюда мы имеем две большие проблемы:
363 |
364 | - Не все поисковые сервисы запускают JavaScript, содержащийся в приложениях, поэтому они не могут получить содержимое динамических веб-страниц.
365 | - Не самый лучший UX, так как пользователь не увидит ничего, кроме пустой/загрузочной страницы, пока весь JavaScript, содержащийся на странице, не загрузится, не распарсится и не выполнится.
366 |
367 | Server-side rendering решает эту проблему пре-рендерингом запрашиваемой страницы на сервере и отправкой готового шаблона во время инициациализации приложения.
368 |
369 | **Инструменты**
370 |
371 | - [Angular Universal](https://github.com/angular/angular/tree/master/packages/platform-server) - Universal (изоморфная) JavaScript поддержка для Angular.
372 | - [Preboot](https://github.com/angular/preboot) - Библиотека для управления переноса состояния страницы (т.е. events, focus, data), которые были сгенерированы на сервере, на страницу, отображаемую в браузере
373 | - [Scully](https://github.com/scullyio/scully) - Генератор статических сайтов для проектов использующий концепцию JAMStack.
374 |
375 | **Полезные материалы**
376 |
377 | - ["Angular Server Rendering"](https://www.youtube.com/watch?v=0wvZ7gakqV4)
378 | - ["Angular Universal Patterns"](https://www.youtube.com/watch?v=TCj_oC3m6_U)
379 | - ["Create a Static Site Using Angular & Scully"](https://www.youtube.com/watch?v=ugTx-14jRrI)
380 |
381 | ### Change Detection
382 |
383 | При каждом асинхронном событии Angular вызывает change detection для всего дерева компонентов. Несмотря на то что код, который обнаруживает изменения, оптимизирован для [inline-caching](http://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html), он все равно может быть затратным для больших и сложных приложений. Способ, который поможет улучшить производительность change detection, заключается в том, что change detection не должен выполняться для поддеревьев компонента, в которых не было изменений.
384 |
385 | #### `ChangeDetectionStrategy.OnPush`
386 |
387 | `ChangeDetectionStrategy.OnPush` позволяет нам отключить механизм change detection для дерева компонентов. Указав для change detection strategy в компоненте значение `ChangeDetectionStrategy.OnPush`, изменения будут срабатывать **только** тогда, когда компонент получил inputs, отличающиеся от предыдущих, или испустило значение Observable, отслеживаемое внутри компонента. Angular сравнивает предыдущие и текущие inputs по ссылке, и когда результат проверки равен `false`, то inputs помечаются как изменившиеся. В сочетании с [иммутабельными структурами данных](https://facebook.github.io/immutable-js/), `OnPush` улучшает производительность для "чистых" компонентов.
388 |
389 | **Полезные материалы**
390 |
391 | - ["Change Detection в Angular"](https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c)
392 | - ["Все что вам нужно знать о change detection в Angular"](https://blog.angularindepth.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f)
393 |
394 | #### Detaching the Change Detector
395 |
396 | Другой реализацией кастомного механизма отслеживания изменений является открепление и прикрепления отслеживания изменений (CD) для конкретного компонента. Как только мы открепляем CD, Angular не будет делать проверки для компонента и всей его низлежащей структуры.
397 |
398 | Данная практика обычно используется, когда действия или взаимодействия пользователя со внешними сервисами запускают цикл отслеживания изменений чаще, чем это действительно необходимо. В таких ситуациях мы можем откреплять отслеживания измненений и прикреплять его обратно, когда нужно совершить проверку изменений.
399 |
400 | #### Run outside Angular
401 |
402 | В основе механизма отслеживания изменений в Angular лежит [zone.js](https://github.com/angular/zone.js). Zone.js патчит все асинхронные API в браузере и запускает отслеживание изменений в конце исполнения любой асинхронной функции. В **редких** случаях может быть необходимо исполнить код вне контекста Angular Zone и тогда механизм отслежвания изменений не будет вызван. В таких случаях мы можем использовать метод `runOutsideAngular` из `NgZone`.
403 |
404 | **Пример**
405 |
406 | В отрывке кода далее, вы можете увидеть пример компонента с использованием данной практики. Когда метод `_incrementPoints` вызван, компонент начнет инкрементировать свойство `_points` каждые 10 мс (по умолчанию). Инкрементация создаст иллюзию анимации. Т.к. в данной ситуации мы не хотим вызывать проверку изменений для всего древа компонентов каждые 10 секунд, мы можем вызвать `_incrementPoints` вне контекста Angular Zone и обновить DOM вручную (`points` сеттер метод).
407 |
408 | ```ts
409 | @Component({
410 | template: ''
411 | })
412 | class PointAnimationComponent {
413 |
414 | @Input() duration = 1000;
415 | @Input() stepDuration = 10;
416 | @ViewChild('label') label: ElementRef;
417 |
418 | @Input() set points(val: number) {
419 | this._points = val;
420 | if (this.label) {
421 | this.label.nativeElement.innerText = this._pipe.transform(this.points, '1.0-0');
422 | }
423 | }
424 | get points() {
425 | return this._points;
426 | }
427 |
428 | private _incrementInterval: any;
429 | private _points: number = 0;
430 |
431 | constructor(private _zone: NgZone, private _pipe: DecimalPipe) {}
432 |
433 | ngOnChanges(changes: any) {
434 | const change = changes.points;
435 | if (!change) {
436 | return;
437 | }
438 | if (typeof change.previousValue !== 'number') {
439 | this.points = change.currentValue;
440 | } else {
441 | this.points = change.previousValue;
442 | this._ngZone.runOutsideAngular(() => {
443 | this._incrementPoints(change.currentValue);
444 | });
445 | }
446 | }
447 |
448 | private _incrementPoints(newVal: number) {
449 | const diff = newVal - this.points;
450 | const step = this.stepDuration * (diff / this.duration);
451 | const initialPoints = this.points;
452 | this._incrementInterval = setInterval(() => {
453 | let nextPoints = Math.ceil(initialPoints + diff);
454 | if (this.points >= nextPoints) {
455 | this.points = initialPoints + diff;
456 | clearInterval(this._incrementInterval);
457 | } else {
458 | this.points += step;
459 | }
460 | }, this.stepDuration);
461 | }
462 | }
463 | ```
464 |
465 | **Обратите внимание**: Используйте эту практику **очень осторожно и только тогда, когда вы знаете, что делаете**, потому что при некорректном использовании это может привести к неустойчивому состоянию DOM. Также обратите внимание, что код выше не расчитан для запуска в WebWorkers. Если это необходимо, вы можете сделать его WebWorker совместимым, для этого нужно установить label's value используя Angular Renderer.
466 |
467 | ### Use pure pipes
468 |
469 | Аргумент декоратора `@Pipe` принимает объекты в следующем формате:
470 |
471 | ```typescript
472 | interface PipeMetadata {
473 | name: string;
474 | pure: boolean;
475 | }
476 | ```
477 |
478 | Свойство pure означает, что pipe не зависит от какого-либо глобального состояния и не производит сторонних эффектов. Т.е. возвращаемое значение всегда будет одинаковым для конкретного входного аргумента. Таким образом Angular может кэшировать выходы для всех входных аргументов, передаваемых в этот pipe, и переиспользовать их в дальнейшем для избежания повторных вычислений.
479 |
480 | Значение по умолчанию свойства `pure` является `true`.
481 |
482 |
483 | ### `*ngFor` directive
484 |
485 | Директива `*ngFor` используется для отрисовки коллекции.
486 |
487 | #### Use `trackBy` option
488 |
489 | По умолчанию `*ngFor` сравнивает объекты по ссылке.
490 |
491 | Это значит, что когда разработчик меняет ссылку на объект во время изменения содержимого элемента, Angular распознает это как удаление старого объекта и создание нового. Это способствует уничтожению старого DOM элемента из списка и добавлению нового на его место.
492 |
493 | Разработчик может указать, как Angular будет идентифицировать уникальность объекта: кастомная индексирующая функция в виде параметра `trackBy` для директивы `*ngFor`. Данная функция принимает два параметра: `index` и `item`. Angular использует значение, возвращаемое функцией, для идентификации элементов. Очень часто используют ID определенного элемента в качестве уникального ключа.
494 |
495 | **Пример**
496 |
497 | ```typescript
498 | @Component({
499 | selector: 'yt-feed',
500 | template: `
501 | Your video feed
502 |
503 | `
504 | })
505 | export class YtFeedComponent {
506 | feed = [
507 | {
508 | id: 3849, // обратите внимание на поле "id", мы ссылаемся на него в "trackById" функции
509 | title: "Angular in 60 minutes",
510 | url: "http://youtube.com/ng2-in-60-min",
511 | likes: "29345"
512 | },
513 | // ...
514 | ];
515 |
516 | trackById(index, item) {
517 | return item.id;
518 | }
519 | }
520 | ```
521 |
522 | #### Minimize DOM elements
523 |
524 | Рендеринг DOM элементов обычно является самой дорогой операцией, например, при добавлении элементов в UI. Основные затраты вызваны вставкой элемента в DOM и применением стилей. Если `*ngFor` рендерит множество элементов, браузер (особенно старый) может тормозить, поэтому ему может потребоваться больше времени, чтобы отрендерить все элементы. Но это не относится к оптимизациям в Angular.
525 |
526 | Чтобы снизить количество времени на рендеринг, попробуйте следующее:
527 | - Виртуальная прокрутка посредством [CDK](https://material.angular.io/cdk/scrolling/overview) или [ngx-virtual-scroller](https://github.com/rintoj/ngx-virtual-scroller)
528 | - Уменьшение количества DOM элементов, отображаемых с помощью `*ngFor` в шаблоне. Обычно ненужные/неиспользуемые DOM элементы возникают в результате расширения шаблона. Переосмысление структуры, скорее всего, сделает шаблон более простым.
529 | - Используйте [`ng-container`](https://angular.io/guide/structural-directives#ngcontainer), где это возможно
530 |
531 | **Полезные материалы**
532 |
533 | - ["NgFor directive"](https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html) - официальная документация для `*ngFor`
534 | - ["Angular — улучшение производительности с помощью trackBy"](https://netbasal.com/angular-2-improve-performance-with-trackby-cc147b5104e5) - gif-демонстрация подходов
535 | - [Component Dev Kit (CDK) Virtual Scrolling](https://material.angular.io/cdk/scrolling/overview) - описание API
536 | - [ngx-virtual-scroller](https://github.com/rintoj/ngx-virtual-scroller) - отображает виртуальный, "бесконечный" список
537 |
538 | ### Optimize template expressions
539 |
540 | Angular извлекает выражения в шаблонах после каждого срабатывания цикла change detection. Change detection срабатывает вследствие асинхронных вызовов, например, выполнение промисов, получение ответа http, нажатие клавиш и движение курсором мыши.
541 |
542 | Такие выражения должны завершаться быстро, иначе пользователь может замечать "дергания", особенно на слабых девайсах. Поэтому, если сталкиваетесь с затратными вычислениями, стоит подумать о кэшировании.
543 |
544 | **Полезные материалы**
545 | - [quick-execution](https://angular.io/guide/template-syntax#quick-execution) - официальная документация по выражениям в шаблонах
546 | - [Increasing Performance - more than a pipe dream](https://youtu.be/I6ZvpdRM1eQ) - ng-conf видеозапись на youtube. Использование pipe вместо функции для интерполяции строки
547 |
548 | # Итоги
549 |
550 | Представленный список со временем будет постепенно развиваться добавлением и обновлением текущих практик. Если вы заметили, что чего-то не хватает, или считаете, что какие-то практики можно улучшить, то не стесняйтесь создавать issue и/или PR. Для более подробной информации об этом, пожалуйста, посмотрите раздел [Contributing](#contributing)", который находится ниже.
551 |
552 | # Contributing
553 |
554 | В случае если вы заметите недостающую, незавершенную или некорректную информацию, вы можете сделать pull request, это будет очень ценно для нас. Для обсуждения лучших практик, которые не включены в документацию, пожалуйста, [создайте issue на github](https://github.com/mgechev/angular2-performance-checklist/issues).
555 |
556 | # License
557 |
558 | MIT
559 |
--------------------------------------------------------------------------------
/README.zh-CN.md:
--------------------------------------------------------------------------------
1 |
2 | # Angular 性能检查清单
3 |
4 |
5 |
6 | ## 简介
7 | 这份清单涵盖了许多提高 Angular 应用性能的实践。本清单 涵盖了不同的主题,其中包括服务端渲染,预渲染和构建应用,还包括运行时的性能和变更检测。
8 |
9 |
10 | 这份清单分为两个大的部分:
11 |
12 | - 网络性能 - 列举一些能够改善你的应用程序加载时间的最佳实践,包括网络延迟和低带宽的情况。
13 | - 运行时性能 - 提高我们应用运行时性能的最佳实践,包括变更检测机制和渲染相关的优化。
14 |
15 | 其中,部分实践会影响涵盖这两个类别,因此可能会有些许的交集,但是,下文将明确指出其中的二者交集的含义和差异。
16 |
17 | 不仅如此,我们还例举出最佳实践相关的工具链,这些工具协助我们更好完成的自动化的开发,提高我们的生产效率。
18 |
19 | 提示:大多数实践对HTTP/1.1和HTTP/2都有效。通过指定可以应用到哪一个协议版本,每个实践都会指出碰到异常该如何处理。
20 |
21 |
22 | ## 目录
23 |
24 | - [Angular 性能检查清单](#angular-2-performance-checklist)
25 | - [简介](#introduction)
26 | - [目录](#table-of-content)
27 | - [网络性能](#network-performance)
28 | - [打包](#bundling)
29 | - [代码去重和优化](#minification-and-dead-code-elimination)
30 | - [删除空模板](#remove-template-whitespace)
31 | - [摇树优化](#tree-shaking)
32 | - [摇树优化的供应商](#tree-shakeable-providers)
33 | - [Ahead-of-Time (AoT) 编译](#ahead-of-time-aot-compilation)
34 | - [压缩](#compression)
35 | - [预加载资源](#pre-fetching-resources)
36 | - [懒加载资源](#lazy-loading-of-resources)
37 | - [禁止懒加载默认的 module](#dont-lazy-load-the-default-route)
38 | - [缓存](#caching)
39 | - [使用命令](#use-application-shell)
40 | - [使用 Service Workers](#use-service-workers)
41 | - [运行时优化](#runtime-optimizations)
42 | - [使用 `enableProdMode`](#use-enableprodmode)
43 | - [Ahead-of-Time 编译](#ahead-of-time-compilation)
44 | - [Web Workers](#web-workers)
45 | - [服务端渲染](#server-side-rendering)
46 | - [变更检测](#change-detection)
47 | - [`ChangeDetectionStrategy.OnPush`(译者注: `变更检测的 onPush 策略`)](#changedetectionstrategyonpush)
48 | - [Detaching the Change Detector(译者注: `ChangeDetectorRef.detach()`)](#detaching-the-change-detector)
49 | - [Run outside Angular(译者注: `ngZone.runOutsizeAngular()`)](#run-outside-angular)
50 | - [使用纯管道(pure pipe)](#use-pure-pipes)
51 | - [`*ngFor` 指令](#ngfor-directive)
52 | - [使用 `trackBy` 选项](#use-trackby-option)
53 | - [最小化 DOM 节点](#minimize-dom-elements)
54 | - [优化模板语法](#optimize-template-expressions)
55 | - [结语](#conclusion)
56 | - [贡献](#contributing)
57 |
58 | ## 网络性能
59 | 本节中的一些工具目前还在开发阶段,可能会发生更改。Angular核心团队正在尽可能多地自动化我们的应用程序的构建过程,因此许多优化和新特性都将接踵而至。
60 |
61 | ### 构建
62 | 打包是一种标准实践,旨在减少浏览器对于资源的多次请求,降低 http 请求数。本质上,bundler 接收一个入口点列表作为输入,并生成一个或多个bundler。这样,浏览器只需执行几个请求,而不是单独请求每个资源,就可以获得整个应用程序。
63 |
64 | 当你的应用程序增长到一定的量级,把所有资源打包到一个 bundle 可能会适得其反,这时就需要使用 Webpack 对项目进行依赖拆分。
65 |
66 |
67 | **由于具有服务器推送功能,HTTP/2不会涉及HTTP请求。参照 [server push](https://http2.github.io/faq/#whats-the-benefit-of-server-push) feature.**
68 |
69 | **工具**
70 |
71 | 帮助我们更高效的构建应用程序:
72 |
73 | - [Webpack](https://webpack.js.org) - 提供高效的构建,打包及[摇树优化](#tree-shaking).
74 | - [Webpack Code Splitting](https://webpack.js.org/guides/code-splitting/) - 将代码分割.
75 | - [Webpack & http2](https://medium.com/webpack/webpack-http-2-7083ec3f3ce6#.46idrz8kb) - 与 http2 分离.
76 | - [Rollup](https://github.com/rollup/rollup) -利用ES2015模块的静态特性,通过执行有效的摇树来进行构建
77 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - 提供大量优化和构建支持. 最初使用 JAVA 进行编写的, 最近也有一个 [JavaScript 版本](https://www.npmjs.com/package/google-closure-compiler-js) 参见 [这里](https://www.npmjs.com/package/google-closure-compiler-js).
78 | - [SystemJS Builder](https://github.com/systemjs/builder) - 为最小依赖系统模块树提供单文件构建
79 | - [Browserify](http://browserify.org/) (译者注:brwoser 通过捆绑依赖,让你在浏览器具备 `require('modules') ` 的能力)
80 | - [ngx-build-modern](https://github.com/manfredsteyer/ngx-build-plus/tree/master/ngx-build-modern) - Angular-CLI 的插件, 把应用构建成两个版本:
81 | 1. 支持 ES2015 的现代浏览器和通过 pollyfill 实现新特性的浏览器,构建出较小的包
82 | 2. 使用不同polyfill和编译器目标的旧版本(默认)
83 |
84 | **资源**
85 |
86 | - ["制作一个 Angular 应用程序"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
87 | - ["使用 Google Closure Compiler 让 Angular 程序缩小 2.5 倍"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
88 |
89 | ### 优化并删除无用代码
90 | 这些实践都会让我们的应用程序更轻巧并减少带宽的负担。
91 |
92 | **工具**
93 |
94 | - [Uglify](https://github.com/mishoo/UglifyJS) 代码压缩,如管理变量、删除注释和空白、消除死码等,完全用 javascript编写,所有流行的代码运行程序(IDE)都有插件。
95 | - [Google Closure Compiler](https://github.com/google/closure-compiler) -执行类似于uglify类型的代码压缩。在高级模式下,它会积极地转换程序的 AST ,以便能够执行更复杂的优化. 同样具有 [JavaScript 版本](https://www.npmjs.com/package/google-closure-compiler-js) 参见 [这里](https://www.npmjs.com/package/google-closure-compiler-js). GCC还支持大多数 ES2015 模块语法,因此它可以[摇树优化](#摇树优化)。
96 |
97 | **资源**
98 |
99 | - ["Building an Angular Application for Production"](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
100 | - ["2.5X Smaller Angular Applications with Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
101 |
102 | ### 移除无用模板
103 |
104 | 虽然我们看不到空白字符(与`\s` RegExp 匹配的字符),但它仍然由通过网络传输的字节表示。如果我们将模板中的空白减少到最小,我们将能够分别进一步减少 AOT 代码的包大小。
105 |
106 | 好在,“componentmetadata” 接口提供属性 “preserveWhitespaces”,默认值为“false”,因为删除空白可能会影响 DOM 布局。如果我们将属性设置为“true”,那么 Angular 将修剪不必要的空白,从而进一步减小构建体积的大小。
107 |
108 | - [preserveWhitespaces in the Angular docs](https://angular.io/api/core/Component#preserveWhitespaces)
109 |
110 | ### 摇树优化
111 |
112 |
113 | 对于我们的应用程序的最终版本,我们通常不使用 Angular或任何第三方库提供的整个 Library,甚至是我们自己写的所有代码。由于ES2015模块的静态特性,我们能够剔除应用程序中未引用的代码。
114 |
115 | **示例**
116 |
117 | ```javascript
118 | // foo.js
119 | export foo = () => 'foo';
120 | export bar = () => 'bar';
121 |
122 | // app.js
123 | import { foo } from './foo';
124 | console.log(foo());
125 | ```
126 | 一旦我们对app.js进行了摇树优化,那么最终打包的结果为:
127 |
128 | ```javascript
129 | let foo = () => 'foo';
130 | console.log(foo());
131 | ```
132 | 无用的方法 `bar` 最终没有被打包
133 |
134 | **工具**
135 |
136 | - [Webpack](https://webpack.js.org) - 通过执行 [摇树优化](#摇树优化), 一旦应用构建完成, bundle 中不会包含无用代码,因此通过 uglify 可以安全地删除无用代码
137 | - [Rollup](https://github.com/rollup/rollup) - 利用ES2015模块的静态特性,通过执行有效的摇树来进行构建
138 | - [Google Closure Compiler](https://github.com/google/closure-compiler) - 提供大量优化和构建支持. 最初使用 JAVA 进行编写的, 最近也有一个 JavaScript 版本 参见 这里.
139 |
140 | *Note:* GCC还支持大多数 ES2015 模块语法,因此它可以[摇树优化](#摇树优化)。
141 |
142 | **资源**
143 |
144 | - [“制作一个 Angular 应用程序”](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/)
145 | - [“使用 Google Closure Compiler 让 Angular 程序缩小 2.5 倍”](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
146 | - ["使用 Rx.js 操作符"](https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md)
147 |
148 | ### 摇树优化服务
149 | 自从 Agnular 发布 V6 版本以来,Angular 团队提供了一个可以将 services 摇树优化的新特性,这意味着,除非其他服务或组件正在使用该服务,否则该的服务将不会包含在最终捆绑包中,这可以通过从包中删除未使用的代码来帮助减少包的大小。
150 |
151 | 你可以在 Angular 应用中的 Services 里使用 `@injectable()` 的 `providedIn` 方法去定义服务应该在哪里初始化,从而更好的摇树优化。
152 | 然后,您应该将其从 “ngModule” 声明的 “providers” 属性以及其 import 语句中删除,如下所示:
153 |
154 | Before:
155 |
156 | ```ts
157 | // app.module.ts
158 | import { NgModule } from '@angular/core'
159 | import { AppRoutingModule } from './app-routing.module'
160 | import { AppComponent } from './app.component'
161 | import { environment } from '../environments/environment'
162 | import { MyService } from './app.service'
163 |
164 | @NgModule({
165 | declarations: [
166 | AppComponent
167 | ],
168 | imports: [
169 | ...
170 | ],
171 | providers: [MyService],
172 | bootstrap: [AppComponent]
173 | })
174 | export class AppModule { }
175 | ```
176 |
177 | ```ts
178 | // my-service.service.ts
179 | import { Injectable } from '@angular/core'
180 |
181 | @Injectable()
182 | export class MyService { }
183 | ```
184 |
185 | After:
186 |
187 | ```ts
188 | // app.module.ts
189 | import { NgModule } from '@angular/core'
190 | import { AppRoutingModule } from './app-routing.module'
191 | import { AppComponent } from './app.component'
192 | import { environment } from '../environments/environment'
193 |
194 | @NgModule({
195 | declarations: [
196 | AppComponent
197 | ],
198 | imports: [
199 | ...
200 | ],
201 | providers: [],
202 | bootstrap: [AppComponent]
203 | })
204 | export class AppModule { }
205 | ```
206 |
207 | ```ts
208 | // my-service.service.ts
209 | import { Injectable } from '@angular/core'
210 |
211 | @Injectable({
212 | providedIn: 'root'
213 | })
214 | export class MyService { }
215 | ```
216 |
217 | 如果没有在任何组件/服务中注入 `myService`,那么它将不会出现在最终的打包文件中。
218 |
219 | **资源**
220 |
221 | - [Angular 服务供应商](https://angular.io/guide/providers)
222 |
223 | ### Ahead-of-Time (AoT) 编译
224 | 对于可用的构建工具(如gcc、rollup等)来说,用内置的功能去解析 Angular 组件的 HTML-like 模板是一项艰巨的挑战。这使得它们的摇树效率降低,因为它们不确定模板中引用了哪些指令。AOT编译器通过 `ES2015` 模块导入将 Angular HTML 类模板传输到 JavaScript 或 TypeScript。这样,我们就能够在绑定期间有效地进行摇树,并删除由 Angular、或第三方库或是我们自己定义的所有未使用的指令。
225 |
226 | **资源**
227 |
228 | - ["Angular Ahead-of-Time 编译"](http://blog.mgechev.com/2016/08/14/ahead-of-time-compilation-angular-offline-precompilation/)
229 |
230 | ### 压缩
231 | 压缩响应是减少网络负载的标准实践。通过指定头“Accept-Encoding”的值,浏览器提示服务器哪些压缩算法在客户端上可用。另一方面,服务器在响应时,会在 “Content-Encoding” 中告诉浏览器选择了哪个算法来压缩。
232 |
233 | **工具**
234 | 这些工具不是 Angular 特有的,完全依赖于我们的应用/服务。典型的压缩算法有:
235 |
236 |
237 | - deflate - 一种数据压缩算法和相关文件格式,使用LZ77算法和哈夫曼编码的组合
238 | - [brotli](https://github.com/google/brotli) -一种通用无损压缩算法,它使用LZ77算法的现代变种、哈夫曼编码和二阶上下文建模相结合来压缩数据,压缩比与当前可用的最佳通用压缩方法相当。它在速度上与 deflate 相似,但提供更密集的压缩。
239 |
240 |
241 |
242 | **资源**
243 |
244 | - ["Better than Gzip Compression with Brotli"](https://hacks.mozilla.org/2015/11/better-than-gzip-compression-with-brotli/)
245 | - ["2.5X Smaller Angular Applications with Google Closure Compiler"](http://blog.mgechev.com/2016/07/21/even-smaller-angular2-applications-closure-tree-shaking/)
246 |
247 | ### 预加载资源
248 |
249 | 资源预加载是提高用户体验的好方法。我们可以预先获取资源(图像、样式、模块等)或数据。有不同的预加载策略,但大多数都取决于应用程序的具体情况。
250 |
251 | ### 懒加载资源
252 | 假设目标应用程序有一个庞大的代码库,并且有数百个依赖,上面列出的实践可能无法帮助我们将 bundle 包体积减少到一个合理的大小(合理的可能是 100k 或 2M,同样,完全取决于业务指标)。
253 |
254 |
255 | 在这种情况下,一个好的解决方案可能是延迟加载应用程序的一些模块。举个例子,假设我们正在构建一个电子商务系统。在这种情况下,我们可能希望独立于面向用户的 UI 加载 admin 面板。一旦管理员必须添加一个新产品,我们将希望提供所需的用户界面。这可能只是“添加产品页面”或整个 admin 面板,具体取决于我们的业务需求。
256 |
257 |
258 | **工具**
259 |
260 | - [Webpack](https://github.com/webpack/webpack) - 异步加载模块
261 | - [ngx-quicklink](https://github.com/mgechev/ngx-quicklink) - 自动延迟加载屏幕上所有链接所引用的 modules
262 |
263 |
264 | ### 最好不要懒加载默认模块
265 |
266 | 我们假设有如下的路由配置:
267 |
268 | ```ts
269 | // Bad practice
270 | const routes: Routes = [
271 | { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
272 | { path: 'dashboard', loadChildren: './dashboard.module#DashboardModule' },
273 | { path: 'heroes', loadChildren: './heroes.module#HeroesModule' }
274 | ];
275 | ```
276 | 第一次用户打开的 url 为: https://example.com/ ,将自动重定向到 `/dashboard` ,与此同时将触发 `dashedboard.module `的懒加载。实际上 Angular 在渲染启动组件所在模块时,将下载 `dasheboard.module` 的所有文件和依赖项,接着,这些文件需要 JavaScript 引擎去解析。
277 | 在初始化页面中触发外部 HTTP
278 |
279 | 请求并执行大量的计算是一种不好的实践方式,这将大大降低初始路由页的加载速度。考虑将默认模块声明为非惰性加载。
280 |
281 |
282 | ### 缓存
283 | 缓存是另一种加快程序运行速度的实践方式:请求了一个资源,很有可能不久之后又去请求一次。
284 |
285 | 我们通常使用自定义的缓存机制,对于静态资源我们直接使用浏览器缓存或者 Service Worker :[CacheStorage API](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
286 |
287 |
288 | ### 使用应用程序指令
289 | 要使应用程序的性能更快,请使用[Application Shell](https://developers.google.com/web/updates/2015/11/app-shell).
290 |
291 | 使用应用程序指令是我们向用户展示的最小用户界面,以指示他们应用程序将很快交付。为了动态生成应用程序指令,我们可以将 Angular Universal 与自定义指令一起使用,这些指令根据所使用的渲染平台有条件地显示元素(例子:除了`platform-server` 之外都隐藏掉)。
292 |
293 |
294 | **工具**
295 |
296 | - [Angular Service Worker](https://angular.io/guide/service-worker-intro) - 自动化管理 Service Workers ,以及静态资源的缓存 [generating application shell](https://developers.google.com/web/updates/2015/11/app-shell?hl=en).
297 | - [Angular Universal](https://github.com/angular/angular/tree/master/packages/platform-server) - 为 Angular 提供同构支持
298 |
299 | **资源**
300 |
301 | - ["Instant Loading Web Apps with an Application Shell Architecture"](https://developers.google.com/web/updates/2015/11/app-shell)
302 |
303 | ### 使用 Service Workers
304 | 我们可以认为 Service Worker 是 客户端本地 对 HTTP 的代理。所有请求从客户端发出第一时间都被 Service Worker 拦截,也可以直接通过 HTTP 去处理这些请求。
305 |
306 |
307 | **工具**
308 |
309 | - [Angular Service Worker](https://angular.io/guide/service-worker-intro) - 自动化管理 Service Workers ,以及静态资源的缓存 [generating application shell](https://developers.google.com/web/updates/2015/11/app-shell?hl=en).
310 | - [Webpack 离线插件](https://github.com/NekR/offline-plugin) - Webpack Service 提供的 Worker插件
311 |
312 | **资源**
313 |
314 | - ["离线指南"](https://jakearchibald.com/2014/offline-cookbook/)
315 |
316 | ## 运行时优化
317 | 这个部分涵盖了一些浏览器帧率优化的最佳实践(最佳用户体验 60FPS / S)
318 |
319 |
320 | ### 使用 `enableProdMode`
321 | 在生产模式下,为了验证执行变更检测不会导致对任何绑定进行任何其他更改,Angular 会执行一些额外检查。这样,框架就可以确保遵循单向数据流。
322 |
323 | 要在正式模式下禁用这些变化,记得调用 `enableProdMode`:
324 |
325 |
326 | ```typescript
327 | import { enableProdMode } from '@angular/core';
328 |
329 | if (ENV === 'production') {
330 | enableProdMode();
331 | }
332 | ```
333 |
334 | ### Ahead-of-Time(AoT) 编译
335 |
336 | AoT 不仅有助于在摇树阶段更有效率的打包,而且会提升我们应用的运行时性能。AoT 的另一种选择是运行时执行的实时编译(JIT),因此,我们可以通过将编译作为构建过程的一部分执行来减少呈现应用程序所需的计算量。
337 |
338 | **工具**
339 |
340 | - [angular2-seed](https://github.com/mgechev/angular2-seed) - 一个包含 Aot 编译的 starter 项目 [angular-cli](https://cli.angular.io) 使用 `ng serve --prod`
341 |
342 | **资源**
343 |
344 | - ["Angular 的 Aot"](http://blog.mgechev.com/2016/08/14/ahead-of-time-compilation-angular-offline-precompilation/)
345 |
346 | ### Web Workers
347 | 在典型的单页应用中,我们的代码通常跑在一个线程里,也就是说如果我们想要用户体验到 60FPS,我们最多只有 16ms 的时间在各个帧之间去执行我们的代码。
348 |
349 | 在具备巨大组件树的应用中,变更检测通常每秒钟要执行上百万次,因此很容易让页面的性能下降,帧率变低。还好 Angular 将 Dom 结构进行了剥离,我们可以在 Web Woker中运行整个应用程序(包括更改检测),只让主 UI 线程只负责渲染。
350 |
351 |
352 | **工具**
353 |
354 | Webpack 允许我们在 Web Worker 中运行应用程序的模块。示例如何使用[found here](https://github.com/angular/angular/tree/master/modules/playground/src/web_workers).
355 | - [Webpack Web Worker Loader](https://github.com/webpack/worker-loader) - A Web Worker Loader for webpack.
356 |
357 | **资源**
358 |
359 | - ["为更多 APP 使用 Web Workers"](https://www.youtube.com/watch?v=Kz_zKXiNGSE)
360 |
361 | ### 服务端渲染
362 | SPA 有一个大问题:在初始渲染页面所需的整个JavaScript 加载之前,无法渲染。这导致了两个大问题:
363 |
364 | -并非所有的搜索引擎都在运行与页面相关联的 JavaScript,因此它们无法正确爬到动态应用程序的内容。
365 |
366 | -糟糕的用户体验,因为在下载、分析和执行与页面相关的 JavaScript 之前,用户只会看到页面处于空白/加载状态。
367 |
368 | **工具**
369 |
370 | - [Angular Universal](https://github.com/angular/angular/tree/master/packages/platform-server) - 为 Angular 同构提供支持.
371 | - [Preboot](https://github.com/angular/preboot) -用于帮助管理状态(即事件、焦点、数据)从服务器生成的 Web 视图到客户端生成的 Web 视图的转换的库。
372 |
373 | **资源**
374 |
375 | - ["Angular 服务端渲染"](https://www.youtube.com/watch?v=0wvZ7gakqV4)
376 | - ["Angular 通用模式"](https://www.youtube.com/watch?v=TCj_oC3m6_U)
377 |
378 | ### 变更检测
379 |
380 | 在每个异步事件上,Angular 对整个组件树执行更改检测。虽然检测更改的代码针对[内联缓存](http://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html)进行了优化,但在复杂的应用程序中,这仍然是一个大量复杂的计算。提高变更检测性能的一种方法是不对子树执行变更检测,子树不应根据最近的操作进行变更。
381 |
382 |
383 |
384 | #### `ChangeDetectionStrategy.OnPush`
385 |
386 | `OnPush` 禁用组件树子树的更改检测机制. 将单个组件的变更策略设定为 `ChangeDetectionStrategy.OnPush`, 将使更改检测仅在组件收到 **不同输入** 时执行。当 Angular 将输入与以前的参考输入进行比较时,它会将输入视为不同的输入,并且参考检查的结果是 `false`。结合[不可变数据结构](https://facebook.github.io/immutable-js/)`onpush`可以为此类 **纯** 组件带来巨大的性能提升。
387 |
388 | **资源**
389 |
390 | - ["Angular 变更检测机制"](https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c)
391 | - ["Angular 中你应该知道的所有关于变更检测的原理"](https://blog.angularindepth.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f)
392 |
393 | #### 关闭变更检测
394 | 另外的一种方法,通过 `detach`ing 和 `reattach`ing 实现自定义变更检测机制,从而来控制组件的变更检测开关。一旦执行 `detach` ,Angular 变更检测时将不会对整个组件树进行检查。
395 |
396 | 当用户操作或与外部服务的交互比需要的更频繁地触发变更检测时,通常使用此实践。在这种情况下,我们可能需要考虑分离变更检测器,并仅在需要执行变更检测时通过重新 `reattach` 来打开变更检测的**开关**。
397 |
398 |
399 |
400 |
401 | #### 运行于 Angular 之外
402 | Angular 的变更检测机制是通过[zone.js](https://github.com/angular/zone.js) 实现的。
403 | Zone.js 通过猴子补丁的方法代理了所有浏览器的异步 APIs,并在他们执行的时候以异步回调的形式触发变更检测。
404 | 在**极少情况**下,我们希望代码在 Angular 之外的上下文运行,所以不需要变更检测机制进行检查。这种情况下我们可以在组件中注入 `zone: NgZone` 服务,并且调用这个实例的 `runOutsizeAngular` 方法即可。
405 |
406 | **例子**
407 | 在下面的代码片段中,您可以看到使用此实践的组件的示例。当调用 `_incrementPoints` 方法时,组件将开始每 10 ms 递增一次 `_points` 属性(默认情况下)。递增会造成动画的假象。因为在这种情况下,我们不希望触发整个组件树的更改检测机制,所以每 10 ms,我们可以在 Angular 区域的上下文之外运行 `incrementpoints`,并手动更新 DOM(请参见 `points` setter 访问器)。
408 |
409 |
410 | ```ts
411 | @Component({
412 | template: ''
413 | })
414 | class PointAnimationComponent {
415 |
416 | @Input() duration = 1000;
417 | @Input() stepDuration = 10;
418 | @ViewChild('label') label: ElementRef;
419 |
420 | @Input() set points(val: number) {
421 | this._points = val;
422 | if (this.label) {
423 | this.label.nativeElement.innerText = this._pipe.transform(this.points, '1.0-0');
424 | }
425 | }
426 | get points() {
427 | return this._points;
428 | }
429 |
430 | private _incrementInterval: any;
431 | private _points: number = 0;
432 |
433 | constructor(private _zone: NgZone, private _pipe: DecimalPipe) {}
434 |
435 | ngOnChanges(changes: any) {
436 | const change = changes.points;
437 | if (!change) {
438 | return;
439 | }
440 | if (typeof change.previousValue !== 'number') {
441 | this.points = change.currentValue;
442 | } else {
443 | this.points = change.previousValue;
444 | this._ngZone.runOutsideAngular(() => {
445 | this._incrementPoints(change.currentValue);
446 | });
447 | }
448 | }
449 |
450 | private _incrementPoints(newVal: number) {
451 | const diff = newVal - this.points;
452 | const step = this.stepDuration * (diff / this.duration);
453 | const initialPoints = this.points;
454 | this._incrementInterval = setInterval(() => {
455 | let nextPoints = Math.ceil(initialPoints + diff);
456 | if (this.points >= nextPoints) {
457 | this.points = initialPoints + diff;
458 | clearInterval(this._incrementInterval);
459 | } else {
460 | this.points += step;
461 | }
462 | }, this.stepDuration);
463 | }
464 | }
465 | ```
466 |
467 | **警告**: **只有当您确定要做什么时,才能非常小心地使用这个实践**,因为如果使用不当,它可能导致 DOM 的状态不一致而引发错误。还要注意,上面的代码不会在 Web Worker 中运行。为了使它与 Web Worker 兼容,需要使用 Angular 的 `renderer` 设置标签的值。
468 |
469 |
470 | ### 使用纯管道
471 | 作为参数,`@pipe` decorator接受具有以下格式参数:
472 |
473 | ```typescript
474 | interface PipeMetadata {
475 | name: string;
476 | pure: boolean;
477 | }
478 | ```
479 | `pure` 标志表示管道不依赖于任何全局状态,不会产生副作用。这意味着当使用相同的输入调用时,管道将返回相同的输出。通过这种方式,Angular 可以缓存管道调用时使用的所有输入参数的输出,并重用它们,以便不必在每次计算时重新计算它们。
480 |
481 | 默认值 `pure` 为 `true`
482 |
483 | ### `*ngFor` 指令
484 |
485 | `*ngFor` 用于渲染集合(译者注:拥有迭代器的对象)
486 |
487 | #### 使用 `trackBy` 选项
488 | 默认情况下,`*ngFor` 通过参考对象引用的唯一值来确认对象(译者注:对象的引用)。
489 | 也就是说,当开发人员在更新项的内容时中断对对象的引用时,Angular 将其视为删除旧对象和添加新对象。这会破坏列表中的旧 DOM 节点并在其位置添加新的 DOM 节点。
490 |
491 | 开发者可以提供一个关于 Angular 如何识别对象唯一性的提示:自定义跟踪函数作为 `*ngfor` 指令的 `trackby` 选项。跟踪函数接受两个参数:`index` 和 `item`。Angular 使用跟踪函数返回的值来跟踪每个迭代的对象。使用函数生成的值作为唯一键(key)。
492 |
493 |
494 | **示例**
495 |
496 | ```typescript
497 | @Component({
498 | selector: 'yt-feed',
499 | template: `
500 | Your video feed
501 |
502 | `
503 | })
504 | export class YtFeedComponent {
505 | feed = [
506 | {
507 | id: 3849, // note "id" field, we refer to it in "trackById" function
508 | title: "Angular in 60 minutes",
509 | url: "http://youtube.com/ng2-in-60-min",
510 | likes: "29345"
511 | },
512 | // ...
513 | ];
514 |
515 | trackById(index, item) {
516 | return item.id;
517 | }
518 | }
519 | ```
520 |
521 | #### 精简 DOM 元素
522 | 在向 UI 添加元素时,操作 DOM 元素是很昂贵的操作。主要工作通常是将元素插入 DOM 并应用样式。如果 `*ngfor` 呈现很多元素,浏览器(尤其是旧的浏览器)可能会运行变得缓慢,因为需要更多的时间来完成所有元素的呈现。不仅仅是使用 Angular 会有这样的现象。
523 |
524 | 减少渲染时间,可以参考:
525 |
526 | - 虚拟滚动 [CDK](https://material.angular.io/cdk/scrolling/overview) 或 [ngx-virtual-scroller](https://github.com/rintoj/ngx-virtual-scroller)
527 | - 减少在模板中 `*ngfor` 遍历的 DOM 元素的数量。通常不需要/未使用的 DOM 元素是由一次又一次地扩展模板引起的。重新考虑它的结构可能会有帮助。
528 | - 尽可能的使用 [`ng-container`](https://angular.io/guide/structural-directives#ngcontainer)
529 |
530 | **资源**
531 |
532 | - ["NgFor 指令"](https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html) - `*ngFor` 官方文档
533 | - ["Angular — Improve performance with trackBy"](https://netbasal.com/angular-2-improve-performance-with-trackby-cc147b5104e5) - 显示方法的GIF演示
534 | - [Component Dev Kit (CDK) Virtual Scrolling](https://material.angular.io/cdk/scrolling/overview) - API 描述
535 | - [ngx-virtual-scroller](https://github.com/rintoj/ngx-virtual-scroller) -显示虚拟“无限”列表
536 |
537 | ### 优化模板表达式
538 | Angular 在每次变更检测周期之后才去计算模板表达式。变更检测通常由一些异步动作去触发,例如 `promise`, `http返回结果`,`定时器 / 计时器事件` ,`键盘和鼠标事件`等。
539 |
540 | 表达式应该很快完成,否则用户体验可能会被拖走,尤其是在速度较慢的设备上。当计算代价高昂时,应该考虑缓存值。
541 |
542 | **资源**
543 | - [快速计算](https://angular.io/guide/template-syntax#quick-execution) - 模板计算-官方文档
544 | - [提高性能-不仅仅是一个白日梦](https://youtu.be/I6ZvpdRM1eQ) - ng-conf 相关视频. 插值表达式中用管道代替函数
545 |
546 | # 结语
547 |
548 | 实践列表将随着新的/更新的实践而动态演变。如果您发现遗漏或认为可以改进任何实践,请 PR/或 ISSUE。有关更多信息,请查看下面的 "[Contributing](#contributing)部分。
549 |
550 | # 加入构建
551 | 如果您发现缺少,不完整或不正确的内容,我们将十分乐于看到您的 PR。对于文件中未包含的实践的讨论,请
552 | [open an issue](https://github.com/mgechev/angular2-performance-checklist/issues).
553 |
554 | # 协议
555 |
556 | MIT
557 |
--------------------------------------------------------------------------------
/assets/flash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgechev/angular-performance-checklist/c5e6926bd76f27923d9268020cd8a3efb4158be1/assets/flash.jpg
--------------------------------------------------------------------------------
/assets/flash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgechev/angular-performance-checklist/c5e6926bd76f27923d9268020cd8a3efb4158be1/assets/flash.png
--------------------------------------------------------------------------------