Trening na redukcję
jak spalić kilobajty
Specyfikacja
- Angular 4.0.3
- 7 komponentów (z czego jeden dość spory)
- 4 ścieżki główne i 2 dzieci
- Rxjs
- Lodash
Statystyki
- 47% użytkowników oczekuje, że kot przejdzie poniżej dwóch sekund
- 40% sobie pójdzie jeśli to trwa powyżej 3 sekund
- 70% użytkowników mobilnych widziało kota, który był wolny
- 51% użytkowników mobilnych widziało kota, który się zablokował
- 75% do kota, który przechodził powyżej 4 sekund
źródło: Akamai 10For10, KissMetrics
Każda sekunda to
- 11% mniej wyświetleń
- 16% obniżona satysfakcja użytkowników
- 7% niższy współczynnik konwersji
- Strata pieniędzy $$
źródło: Amazon, Walmart, KissMetrics
Czas pobierania 100KB
* Najczęściej stosowany lejek
Angular
4MB => 10min @ 3G
Co wpływa na szybkość ładowania strony
- Waga
- Kondycja połączenia
- Czas przetwarzania
Minifikacja, obfuskacja i optymalizacja
- Skrócenie nazw
- Usunięcie komentarzy
- Usunięcie martwego kodu
- Optymalizacja pętli
- Optymalizacja wartości boolowskich (tautologia)
Minifikacja - Webpack + UglifyJsPlugin
new UglifyJsPlugin({
beautify: false,
output: { comments: false },
compress: {
unused: true,
dead_code: true
}
})
Angular + UglifyJs
1.5MB => 3:51min @ 3G
Analiza struktury aplikacji
Analiza struktury aplikacji
- http://webpack.github.io/analyse
- webpack --profile --json > stats.json
- Graf zależności
- Szczegółowe statystyki
- source-map-explorer
- npm install source-map-explorer
- source-map-explorer main.bundle.js main.bundle.js.map
Wszystko jest pakowane
// maths.js
export function square(x) { return x * x; }
export function cube(x) { return x * x * x; }
// main.js
import { cube } from './maths.js';
console.log(cube(5)); // 125
function(module, exports) { // CommonJS
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.square = square;
exports.cube = cube;
function square(x) {
return x * x;
}
function cube(x) {
return x * x * x;
}
}
Tree Shaking
Tree Shaking
- Bundler usuwa referencje nieużywanego kodu
- Minifikator usuwa kod
- Webpack 2.x + ES6 (import/export)
- Babel
Tree Shaking - Webpack
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[ 'es2015', { modules: false } ] // default CommonJS
]
}
// ES6
function(module, exports, __webpack_require__) {
/* harmony export */ exports["cube"] = cube;
/* unused harmony export square */;
function square(x) {
return x * x;
}
function cube(x) {
return x * x * x;
}
}
Angular + UglifyJs + Tree Shaking
1.3MB => 3:25min @ 3G
Prekompilacja (AoT)
- Mniejszy rozmiar aplikacji - brak @angular/compiler
- Szybsze renderowanie komponentu - szablony są już skompilowane
- Błędy w szablonach wykryte podczas kompilacji
- Bardziej bezpieczny - szablony nie są interpolowane dynamicznie
Prekompilacja (AoT) - @ngtools/webpack
let AotPlugin = require('@ngtools/webpack').AotPlugin;
rules: [{ test: /\.ts/, use: '@ngtools/webpack' }]
plugins: [
new AotPlugin({
tsConfigPath: path.resolve('tsconfig.json'),
entryModule: path.resolve('app.module#AppModule')
})
]
Prekompilacja (AoT) - czego nie robić
-
template: require('./app.component.ts')
templateUrl: './app.component.ts'
-
form.controls.name
form.get('name')
-
form.errors?.required
form.hasError('required')
- @Input, @Output, @HostBinding muszą być publiczne
Angular + UglifyJs + Tree Shaking + AoT
0.9MB => 2:20min @ 3G
Kompresja GZip
- Odpowiedź zmniejszona do 80% (większość danych to powtarzalny tekst)
.selector { /* ... */ }
.selector .child-selector { /* ... */ }
bootstrap.min.css |
118KB |
bootstrap.min.css.gz |
19KB |
Różnica |
83,9% |
- Bardzo wysoki współczynnik zysk/koszt
compression-webpack-plugin
new CompressionPlugin({
regExp: /\.css$|\.html$|\.js$$/,
threshold: 2 * 1024
})
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/html
Angular + UglifyJs + Tree Shaking + AoT + GZip
158KB => 0:24min @ 3G
Closure*
- Agresywna optymalizacja
- Trzeba pisać kod z myślą o Closure
- Łatwo o biedę
* Do codziennego użytku wystarczy UglifyJS
function square(x) {
return x * x;
}
function cube(x) {
return x * x * x;
}
console.log(cube(5));
console.log(125);
Spróbuj sam :)
Podsumowanie
Optymalizacja |
Waga |
Czas pobierania @ 3G |
Brak |
4186KB (100%) |
10:47min |
Minifikacja |
1449KB (34,62%) |
3:43min |
Tree shaking |
1315KB (31,41%) |
3:23min |
Prekompilacja |
907KB (21,67%) |
2:20min |
Gzip |
158KB (3,77%) |
0:24min |
Co wpływa na szybkość ładowania strony
- Waga
- Kondycja połączenia
- Czas przetwarzania
GET /index.html HTTP/1.1
- Przeglądarka: Hej! Czy masz plik index.html?
- Serwer: (sprawdza...)
- Serwer: Pewnie stary, jest tutaj.
- Przeglądarka: Spoko, pobieram i zaraz pokaże go użytkownikowi.
HTTP/1.1: co najwyżej 2 równoczesne połączenia per host
Keep-Alive
Keep-Alive: timeout=15, max=100
- timeout to czas (w sekundach) po jakim połączenie zostanie zamknięte jeśli nic nie zostanie wysłane
- max to maksymalna ilość żądań jakie klient może wykonać
- Oszczędzamy czas tylko na połączeniu, zasoby nie są pobierane równolegle
Nginx potrafi utrzymać 10 000 połączeń przy pomocy zaledwie 2,5 MB RAM
Cache
GET /logo.png HTTP/1.1
Expires: Tue, 17 May 2018 18:00:00 GMT
- Przeglądarka: Hmm... Czy jest przed 17 maja 2018 roku?
- Przeglądarka: Pokaże zatem użytkownikowi plik z pamięci podręcznej.
Google radzi: conajmniej miesiąc, zalecany rok.
Cache-control - polityka buforowania
public |
Przeglądarka klienta i serwery pośrednie |
private |
Przeglądarka klienta |
no-store |
Odpowiedzi nie wolno buforować |
https://developers.google.com/
Cache - .htaccess
<filesMatch ".(jpg|jpeg|png|gif|ico)$">
Header set Cache-Control "max-age=31536000, public"
</filesMatch>
<filesMatch ".(css|js)$">
Header set Cache-Control "max-age=2628000, public"
</filesMatch>
Content Delivery Network
- Szybsze ładowanie zasobów
- "Load balancer" plików statycznych
- Proste w implementacji
- Płatne
Podsumowanie
HSDPA, DL: 3.5Mb/s, UP: 2.1Mb/s, Waga: 525KB
Optymalizacja |
Czas dociagania |
Brak |
~0.8s - 0.9s |
Keep-alive (timeout=10, max=499) |
~0.5s - 0.65s |
Cache |
~0.08s-0.1s (165B) |
Co wpływa na szybkość ładowania strony
- Waga
- Kondycja połączenia
- Czas parsowania
Jak przeglądarka renderuje stronę
- HTML -> DOM
- CSS -> CSSOM
- Wykonywane są skrypty
- DOM + CSSOM => drzewa renderowania
- Pozycjonowanie elementów
- Malowanie elementów
Optmalizacja HTML
- Style na górę, skrypty na dół
- Ładowanie tylko tego co jest potrzebne
- window.onload = function () { /* ... * / }
Optymalizacja CSS
- Atrybuty media
- Telefony nie są wydajne
-
<link rel="stylesheet" media="(max-width: 800px)" href="example.css" />
- Unikanie @import
- Zmniejszenie złożoności selektorów
- Uproszczanie styli (np. UnCSS)
Optymalizacja obrazków
- Właściwe rozmiary
- Odpowiednie formatów plików
- Ładowanie obrazów przez odpowiednie media query
- Scalenie kilka obrazów w jeden
Optymalizacja JavaScript
-
Ładowanie skryptów niezależnych od DOM przez async
- Nie blokuje głównego wątku
- Nie można przewidzieć kiedy zostaną uruchomione
- Google Analytics, Bugsnag
- Ładowanie skryptów zależnych od DOM przez defer
- Nie blokuje głównego wątku
- Zostaną uruchomione po załadowaniu DOM
Lazy loading
- Dociąganie modułów na żądanie
- ...lub pobranie wszystkich po załadowaniu DOM
- Szybka dostępność strony
Lazy loading
const routes: Routes = [
{ path: '', component: Component },
{
path: 'lazy',
loadChildren: 'lazy/lazy.module#LazyModule'
}
];
Podsumowanie
Optymalizacja |
Czas skryptowania |
Brak |
~5s |
Prekompilacja |
3sek (907KB) |
Lazy load |
~600ms (525KB) |
Ogólne podsumowanie
Optmalizacja |
Czas do wyrenderowania strony |
Brak |
~9s |
+ optymalizacja połączenia |
~6s - 8s |
+ optymalizacja wagi |
~5s - 6s |
+ optymalizacja skryptowania |
~1s |