Minifikuj i kompresuj ładunki sieciowe za pomocą narzędzia gzip

Z tego ćwiczenia w programie dowiesz się, jak minifikacja i kompresja pakietu JavaScriptu w poniższej aplikacji poprawia wydajność strony dzięki zmniejszeniu rozmiaru żądania w aplikacji.

Zrzut ekranu aplikacji

Zmierz odległość

Przed zagłębieniem się w optymalizacje warto najpierw przeanalizować bieżący stan aplikacji.

  • Aby wyświetlić podgląd strony, kliknij Wyświetl aplikację, a potem Pełny ekran pełny ekran.

Ta aplikacja, która została również opisana w ramach ćwiczenia z programowania „Usuwanie nieużywanego kodu”, pozwala zagłosować na swojego ulubionego kotka. 🐈

Teraz zobacz, jak duża jest ta aplikacja:

  1. Naciśnij „Control + Shift + J” (lub „Command + Option + J” na Macu), aby otworzyć Narzędzia deweloperskie.
  2. Kliknij kartę Sieć.
  3. Zaznacz pole wyboru Wyłącz pamięć podręczną.
  4. Załaduj ponownie aplikację.

Oryginalny rozmiar grupy w panelu Sieć

Chociaż w pracy z programowaniem „Remove unused code” (Usuwanie niewykorzystanego kodu) udało się już znacznie zmniejszyć rozmiar pakietu, 225 KB nadal jest dość duży.

Minifikacja

Przeanalizujmy ten blok kodu.

function soNice() {
  let counter = 0;

  while (counter < 100) {
    console.log('nice');
    counter++;
  }
}

Jeśli ta funkcja jest zapisana we własnym pliku, rozmiar pliku wynosi około 112 B (bajty).

Jeśli wszystkie spacje zostaną usunięte, kod będzie wyglądał tak:

function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}

Rozmiar pliku wyniesie teraz około 83 B. Jeśli zostanie bardziej zniekształcony przez skrócenie nazwy zmiennej i zmodyfikowanie niektórych wyrażeń, końcowy kod może wyglądać tak:

function soNice(){for(let i=0;i<100;)console.log("nice"),i++}

Rozmiar pliku osiąga teraz 62 B.

Z każdym krokiem coraz trudniej jest odczytać kod. Mechanizm JavaScript przeglądarki interpretuje jednak każdą z tych funkcji dokładnie tak samo. Korzyści z zaciemniania kodu w ten sposób pomagają uzyskać mniejsze rozmiary plików. 112 mld to niewiele, ale i tak udało się zmniejszyć rozmiar o 50%!

Jako pakiet modułów w tej aplikacji używany jest pakiet webpack w wersji 4. Konkretną wersję można zobaczyć tutaj: package.json.

"devDependencies": {
  //...
  "webpack": "^4.16.4",
  //...
}

Wersja 4 domyślnie zmniejsza pakiet w trybie produkcyjnym. Używa wtyczki TerserWebpackPlugin do środowiska Terser. Terser to popularne narzędzie służące do kompresji kodu JavaScript.

Aby zobaczyć, jak wygląda zminifikowany kod, kliknij main.bundle.js, nie wychodząc z panelu narzędzi dla programistów Network (Sieć). Kliknij teraz kartę Odpowiedź.

Ograniczona odpowiedź

Końcowy kod, zminifikowany i zniekształcony, jest wyświetlany w treści odpowiedzi. Aby dowiedzieć się, jak duży byłby pakiet, gdyby nie został zminifikowany, otwórz webpack.config.js i zaktualizuj konfigurację mode.

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

Załaduj ponownie aplikację i jeszcze raz sprawdź rozmiar pakietu w panelu Sieć Narzędzi deweloperskich.

Rozmiar pakietu 767 KB

To duża różnica! 😅

Zanim przejdziesz dalej, cofnij zmiany tutaj.

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

Proces minifikacji kodu w aplikacji zależy od używanych przez Ciebie narzędzi:

  • Jeśli korzystasz z pakietu webpack w wersji 4 lub nowszej, nie musisz nic robić, ponieważ kod jest domyślnie zminimalizowany w trybie produkcyjnym. 👍
  • Jeśli używana jest starsza wersja pakietu internetowego, zainstaluj pakiet TerserWebpackPlugin i dołącz go do procesu kompilacji. Szczegółowo to opisaliśmy w dokumentacji.
  • Istnieją też inne wtyczki do minifikacji, z których można korzystać, np. BabelMinifyWebpackPlugin i ClosureCompilerPlugin.
  • Jeśli moduł tworzenia pakietów modułów w ogóle nie jest używany, użyj interfejsu wiersza poleceń Terser lub dołącz go bezpośrednio jako zależność.

Kompresja

Chociaż termin „kompresja” jest czasami luźno używany do wyjaśnienia sposobu redukcji kodu podczas minifikacji, nie jest on tak naprawdę kompresowany w sensie dosłownym.

Kompresja zwykle oznacza kod, który został zmodyfikowany za pomocą algorytmu kompresji danych. W przeciwieństwie do minifikacji, która zapewnia całkowicie prawidłowy kod, skompresowany kod musi zostać zdekompresowany przed użyciem.

W każdej odpowiedzi i żądaniu HTTP przeglądarki i serwery WWW mogą dodawać headers zawierające dodatkowe informacje o pobieranym lub odbieranym zasobie. Możesz ją zobaczyć na karcie Headers w panelu Narzędzi deweloperskich, gdzie wyświetlane są 3 rodzaje danych:

  • Ogólne to ogólne nagłówki istotne dla całej interakcji żądanie-odpowiedź.
  • Nagłówki odpowiedzi to lista nagłówków specyficznych dla rzeczywistej odpowiedzi z serwera.
  • Nagłówki żądania to lista nagłówków dołączonych do żądania przez klienta.

Spójrz na nagłówek accept-encoding w Request Headers.

Akceptuj nagłówek kodowania

Parametr accept-encoding służy przeglądarce do określenia, które formaty kodowania treści lub algorytmy kompresji obsługuje. Istnieje wiele algorytmów do kompresji tekstu, ale tylko trzy z nich obsługują kompresję (i dekompresję) żądań sieciowych HTTP:

  • Gzip (gzip): najpopularniejszy format kompresji do interakcji z serwerem i klientem. Działa na bazie algorytmu Deflate i obsługuje wszystkie obecne przeglądarki.
  • Deflate (deflate): rzadko używane.
  • Brotli (br): nowszy algorytm kompresji, który ma na celu jeszcze lepsze współczynniki kompresji, co może prowadzić do jeszcze szybszego wczytywania stron. Jest obsługiwane w najnowszych wersjach większości przeglądarek.

Przykładowa aplikacja w tym samouczku jest identyczna z aplikacją uzyskaną w ramach ćwiczenia „Usuwanie nieużywanego kodu” z wyjątkiem tego, że platforma Express jest teraz używana jako platforma serwera. W kilku kolejnych sekcjach zajmiemy się zarówno kompresją statyczną, jak i dynamiczną.

Kompresja dynamiczna

Kompresja dynamiczna obejmuje kompresowanie zasobów w czasie rzeczywistym, gdy przeglądarka wysyła ich żądania.

Zalety

  • Nie musisz tworzyć i aktualizować zapisanych skompresowanych wersji zasobów.
  • Kompresja „w czasie rzeczywistym” sprawdza się szczególnie dobrze w przypadku stron internetowych generowanych dynamicznie.

Wady

  • Kompresowanie plików na wyższych poziomach, aby uzyskać lepszy stopień kompresji, trwa dłużej. Może to spowodować spadek wydajności, ponieważ użytkownik czeka na skompresowanie zasobów, zanim zostaną one przesłane przez serwer.

Dynamiczna kompresja z użyciem węzła/Express

Plik server.js odpowiada za skonfigurowanie serwera węzłów, na którym jest hostowana aplikacja.

const express = require('express');

const app = express();

app.use(express.static('public'));

const listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});

Obecnie wystarczy zaimportować express i użyć oprogramowania pośredniczącego express.static, aby wczytać wszystkie statyczne pliki HTML, JS i CSS do katalogu public/ (pliki te są tworzone przez pakiet internetowy przy każdej kompilacji).

Aby mieć pewność, że wszystkie zasoby są kompresowane za każdym razem, gdy są potrzebne, możesz użyć biblioteki kompresji oprogramowania pośredniczącego. Zacznij od dodania go jako devDependency w package.json:

"devDependencies": {
  //...
  "compression": "^1.7.3"
},

I zaimportuj go do pliku serwera server.js:

const express = require('express');
const compression = require('compression');

I dodaj go jako oprogramowanie pośredniczące, zanim zostanie podłączony express.static:

//...

const app = express();

app.use(compression());

app.use(express.static('public'));

//...

Ponownie załaduj aplikację i sprawdź rozmiar pakietu w panelu Sieć.

Rozmiar pakietu z dynamiczną kompresją

Od 225 KB do 61,6 KB! Nagłówek content-encoding wskazuje teraz, że w elemencie Response Headers serwer wysyła plik zakodowany za pomocą funkcji gzip.

Nagłówek kodowania treści

Kompresja statyczna

Kompresja statyczna polega na skompresowaniu zasobów i zaoszczędzeniu czasu.

Zalety

  • Opóźnienie spowodowane wysokim poziomem kompresji nie jest już problemem. Kompresowanie plików nie musi odbywać się na bieżąco, ponieważ można je teraz pobierać bezpośrednio.

Wady

  • Przy każdej kompilacji zasoby muszą być skompresowane. Czas kompilacji może się znacznie wydłużyć, jeśli stosowane są wysokie poziomy kompresji.

Kompresja statyczna z użyciem węzła/Express i pakietu webpack

Ponieważ kompresja statyczna obejmuje wcześniejsze kompresowanie plików, ustawienia pakietu internetowego można zmodyfikować tak, aby kompresować zasoby w ramach etapu kompilacji. Do tego celu można użyć pola CompressionPlugin.

Zacznij od dodania go jako devDependency w package.json:

"devDependencies": {
  //...
  "compression-webpack-plugin": "^1.1.11"
},

Jak każdą inną wtyczkę pakietu internetowego, zaimportuj ją w pliku konfiguracji webpack.config.js:

const path = require("path");

//...

const CompressionPlugin = require("compression-webpack-plugin");

I uwzględnij go w tablicy plugins:

module.exports = {
  //...
  plugins: [
    //...
    new CompressionPlugin()
  ]
}

Domyślnie wtyczka kompresuje pliki kompilacji za pomocą polecenia gzip. Zapoznaj się z dokumentacją, aby dowiedzieć się, jak dodawać opcje, aby korzystać z innego algorytmu lub uwzględniać bądź wykluczać wybrane pliki.

Gdy aplikacja załaduje się ponownie i ponownie skompiluje, tworzona jest skompresowana wersja pakietu głównego. Otwórz konsolę Glitch, aby zobaczyć, co znajduje się w końcowym katalogu public/ obsługiwanym przez serwer węzłów.

  • Kliknij przycisk Narzędzia.
  • Kliknij przycisk Konsola.
  • W konsoli uruchom te polecenia, aby przejść do katalogu public i wyświetlić wszystkie znajdujące się w nim pliki:
cd public
ls

Ostateczne pliki wyjściowe w katalogu publicznym

Tutaj też jest zapisywana wersja pakietu main.bundle.js.gz skompresowana gzip. CompressionPlugin również domyślnie kompresuje również plik index.html.

Kolejnym krokiem jest polecenie serwera, aby za każdym razem, gdy zażądano ich oryginalnych wersji JS, wysyłał te pliki gzip. Aby to zrobić, zdefiniuj nową trasę w komponencie server.js, zanim pliki zostaną udostępnione w usłudze express.static.

const express = require('express');
const app = express();

app.get('*.js', (req, res, next) => {
  req.url = req.url + '.gz';
  res.set('Content-Encoding', 'gzip');
  next();
});

app.use(express.static('public'));

//...

app.get informuje serwer, jak ma odpowiedzieć na żądanie GET dla określonego punktu końcowego. Za pomocą funkcji wywołania zwrotnego można następnie określić sposób obsługi tego żądania. Trasa działa w następujący sposób:

  • Jeśli jako pierwszy argument określisz '*.js', będzie to działać w przypadku każdego punktu końcowego, który uruchamia się w celu pobrania pliku JS.
  • W wywołaniu zwrotnym adres .gz jest dołączony do adresu URL żądania, a nagłówek odpowiedzi Content-Encoding jest ustawiony na gzip.
  • next() dba też o to, aby sekwencja obejmowała kolejne wywołania zwrotne.

Po ponownym załadowaniu aplikacji przyjrzyj się jeszcze raz panelowi Network.

Zmniejszanie rozmiaru pakietu za pomocą kompresji statycznej

Podobnie jak poprzednio, znaczne zmniejszenie rozmiaru pakietu!

Podsumowanie

W ramach tego ćwiczenia w programie omówiliśmy proces minifikacji i kompresji kodu źródłowego. Obie te techniki stają się domyślnymi elementami wielu dostępnych obecnie narzędzi, warto więc sprawdzić, czy Twój łańcuch narzędzi już je obsługuje, czy też zacząć samodzielnie stosować oba procesy.