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

W tym laboratorium programistycznym dowiesz się, jak zoptymalizować wydajność strony przez zminimalizowanie i skompresowanie pakietu JavaScript w danej aplikacji, co pozwoli zmniejszyć rozmiar żądań wysyłanych przez aplikację.

Zrzut ekranu aplikacji

Zmierz odległość

Zanim zaczniesz wprowadzać optymalizacje, dobrze jest najpierw przeanalizować obecny stan aplikacji.

  • Aby wyświetlić podgląd strony, kliknij Wyświetl aplikację. Następnie kliknij Pełny ekranpełny ekran.

Ta aplikacja, która była też omawiana w ćwiczeniach z programowania „Usuwanie nieużywanego kodu”, umożliwia głosowanie na ulubione kocięta. 🐈

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 Disable cache (Wyłącz pamięć podręczną).
  4. Ponownie załaduj aplikację.

Pierwotny rozmiar pakietu w panelu Sieć

Chociaż w ramach ćwiczenia „Usuwanie nieużywanego kodu” w Codelab udało się zmniejszyć rozmiar pakietu, nadal wynosi on 225 KB, co jest dość dużo.

Minifikacja

Rozważ poniższy blok kodu.

function soNice() {
  let counter = 0;

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

Jeśli ta funkcja jest zapisana w osobnym pliku, jego rozmiar wynosi około 112 B (bajtów).

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

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

Rozmiar pliku będzie wynosił około 83 B. Jeśli zostanie on jeszcze bardziej zniekształcony przez skrócenie nazwy zmiennej i zmianę niektórych wyrażeń, ostateczny kod może wyglądać tak:

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

Rozmiar pliku wynosi teraz 62 B.

Z każdym krokiem kod staje się coraz trudniejszy do odczytania. Jednak mechanizm JavaScript przeglądarki interpretuje je dokładnie tak samo. Zaletą takiego zaciemniania kodu może być mniejsza liczba plików. 112 B nie było dużo na początku, ale rozmiar zmniejszył się o 50%.

W tej aplikacji jako usługa tworzenia pakietów modułów jest używana wersja 4 usługi webpack. Szczegółową wersję znajdziesz w package.json.

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

W wersji 4 pakiet jest już domyślnie minifikowany w trybie produkcyjnym. Wykorzystuje wtyczkę TerserWebpackPlugin do obsługi Terser. Terser to popularne narzędzie służące do kompresji kodu JavaScript.

Aby zobaczyć, jak wygląda zminiaturyzowany kod, kliknij main.bundle.js w panelu Sieć w DevTools. Kliknij kartę Odpowiedź.

Zminiaturyzowana odpowiedź

Kod w ostatecznej formie, zminiaturyzowany i zmodyfikowany, jest widoczny w ciele odpowiedzi. Aby sprawdzić, jak duży mógłby być pakiet, gdyby nie został zoptymalizowany, otwórz webpack.config.js i zaktualizuj konfigurację mode.

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

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

Rozmiar pakietu: 767 KB

To dość duża różnica. 😅

Zanim przejdziesz dalej, cofnij te zmiany.

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

Włączenie w aplikacji procesu kompresji kodu zależy od używanych narzędzi:

  • Jeśli używasz webpacka w wersji 4 lub nowszej, nie musisz nic robić, ponieważ kod jest domyślnie zoptymalizowany w trybie produkcyjnym. 👍
  • Jeśli używasz starszej wersji webpack, zainstaluj i uwzględnij TerserWebpackPlugin w procesie kompilacji webpack. Szczegółowe informacje na ten temat znajdziesz w dokumentacji.
  • Istnieją też inne wtyczki do minifikacji, których można używać zamiast nich, np. BabelMinifyWebpackPlugin i ClosureCompilerPlugin.
  • Jeśli w ogóle nie korzystasz z modułu łączenia modułów, użyj narzędzia Terser jako narzędzia interfejsu wiersza poleceń lub dodaj go bezpośrednio jako zależność.

Kompresja

Mimo że termin „kompresja” jest czasem używany w ogólnym znaczeniu, aby wyjaśnić, jak kod jest skracany podczas procesu minimalizacji, to w rzeczywistości nie jest on kompresowany w dosłownym tego słowa znaczeniu.

Kompresja oznacza zazwyczaj kod zmodyfikowany za pomocą algorytmu kompresji danych. W przeciwieństwie do minifikacji, która powoduje dostarczanie idealnie prawidłowego kodu, skompresowany kod należy zdekompresować przed użyciem.

Przy każdym żądaniu i odpowiedzi HTTP przeglądarki i serwery WWW mogą dodawać nagłówki zawierające dodatkowe informacje o pobieranym lub odbieranym zasobie. Można to sprawdzić na karcie Headers w panelu Sieć w Narzędziach deweloperskich, gdzie wyświetlane są 3 typy:

  • Ogólne reprezentuje ogólne nagłówki istotne dla całej interakcji żądanie-odpowiedź.
  • Nagłówki odpowiedzi zawiera listę nagłówków związanych z odpowiednią odpowiedzią z serwera.
  • Nagłówki żądania zawiera listę nagłówków dołączonych do żądania przez klienta.

Sprawdź nagłówek accept-encoding w pliku Request Headers.

Nagłówek kodowania Accept

accept-encoding służy do określenia, które formaty kodowania treści lub algorytmy kompresji są obsługiwane przez przeglądarkę. Istnieje wiele algorytmów kompresji tekstu, ale tylko 3 z nich są obsługiwane w przypadku kompresji (i dekompresji) żądań sieciowych HTTP:

  • Gzip (gzip): najczęściej używany format kompresji do interakcji z serwerem i klientem. Jest on oparty na algorytmie Deflate i obsługiwany we wszystkich aktualnych przeglądarkach.
  • Deflate (deflate): nie jest często używany.
  • Brotli (br): nowszy algorytm kompresji, którego celem jest dalsze zwiększanie współczynników kompresji, co może skutkować jeszcze szybszym wczytywaniem stron. Jest on obsługiwany w najnowszych wersjach większości przeglądarek.

Przykładowa aplikacja w tym samouczku jest identyczna z aplikacją, która została ukończona w ćwiczeniach z programowania „Usuwanie nieużywanego kodu”, z tym wyjątkiem, że teraz jako framework serwera jest używany Express. W kolejnych sekcjach omówimy zarówno kompresję stałą, jak i dynamiczną.

Kompresja dynamiczna

Kompresja dynamiczna polega na kompresowaniu zasobów na bieżąco, gdy są one żądane przez przeglądarkę.

Zalety

  • Nie musisz tworzyć ani aktualizować zapisanych, skompresowanych wersji zasobów.
  • Kompresowanie na bieżąco sprawdza się zwłaszcza w przypadku stron internetowych generowanych dynamicznie.

Wady

  • Kompresowanie plików na wyższych poziomach w celu uzyskania lepszych współczynników kompresji trwa dłużej. Może to spowodować spadek wydajności, ponieważ użytkownik musi czekać na skompresowanie zasobów, zanim zostaną wysłane przez serwer.

Kompresja dynamiczna za pomocą Node/Express

Plik server.js odpowiada za konfigurowanie serwera Node, na którym działa 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 ta metoda tylko importuje express i korzysta z poziomu pośredniczącego express.static, aby wczytać wszystkie statyczne pliki HTML, JS i CSS w katalogu public/ (pliki te są tworzone przez webpacka przy każdym tworzeniu).

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

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

Następnie zaimportuj go do pliku serwera server.js:

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

Dodaj go jako oprogramowanie pośredniczące, zanim zostanie podłączony element 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 kompresją dynamiczną

Z 225 KB do 61,6 KB! W pliku Response Headers nagłówek content-encoding wskazuje, że serwer wysyła ten plik zakodowany za pomocą gzip.

Nagłówek kodowania treści

Kompresja statyczna

Koncepcja skompresowania statycznego polega na wcześniejszym skompresowaniu i zapisaniu zasobów.

Zalety

  • Czas oczekiwania związany z wysokim poziomem kompresji nie jest już problemem. Nie musisz nic robić na bieżąco, aby skompresować pliki, ponieważ można je teraz pobrać bezpośrednio.

Wady

  • Zasoby muszą być kompresowane w ramach każdej kompilacji. Czas kompilacji może się znacznie wydłużyć, jeśli używasz wysokiego poziomu kompresji.

Statyczna kompresja za pomocą Node/Express i webpack

Kompresja statyczna wymaga wcześniejszej kompresji plików, dlatego ustawienia pakietu internetowego można zmienić w taki sposób, aby na etapie kompilacji kompresowało ono zasoby. Do tego celu możesz użyć karty CompressionPlugin.

Zacznij od dodania go jako devDependency w sekcji package.json:

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

Podobnie jak w przypadku innych wtyczek webpacka, zaimportuj go do pliku konfiguracji:webpack.config.js:

const path = require("path");

//...

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

Uwzględnij go w tablicy plugins:

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

Domyślnie wtyczka kompresuje pliki kompilacji za pomocą gzip. Zapoznaj się z dokumentacją, aby dowiedzieć się, jak dodać opcje użycia innego algorytmu lub uwzględnić bądź wykluczyć określone pliki.

Gdy aplikacja zostanie ponownie załadowana i ponownie utworzona, zostanie utworzona skompresowana wersja głównego pakietu. Otwórz konsolę Glitch, aby sprawdzić, co znajduje się w katalogu public/, który jest obsługiwany przez serwer Node.

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

Końcowe pliki wyjściowe w katalogu publicznym

Zapisujemy tutaj również skompresowaną wersję pakietu (main.bundle.js.gz). CompressionPlugin domyślnie kompresuje też kompresję index.html.

Kolejną rzeczą, którą należy zrobić, jest przekazanie serwerowi, aby wysyłał te skompresowane pliki za każdym razem, gdy otrzyma żądanie oryginalnej wersji kodu JS. Możesz to zrobić, definiując nową ścieżkę w pliku server.js, zanim pliki zostaną przesłane za pomocą pliku 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 służy do informowania serwera, jak ma reagować na żądanie GET dla określonego punktu końcowego. Następnie za pomocą funkcji wywołania zwrotnego można określić sposób obsługi tego żądania. Trasa działa w ten sposób:

  • Podanie wartości '*.js' jako pierwszego argumentu oznacza, że ta funkcja działa w przypadku każdego punktu końcowego, który jest wywoływany w celu pobrania pliku JS.
  • W wywołaniu zwrotnym parametr .gz jest dołączany do adresu URL żądania, a nagłówek odpowiedzi Content-Encoding jest ustawiony na gzip.
  • Na koniec next() zapewnia, że sekwencja będzie kontynuowana w przypadku każdego wywołania zwrotnego, które może nastąpić.

Gdy aplikacja zostanie ponownie załadowana, jeszcze raz otwórz panel Network.

Zmniejszenie rozmiaru pakietu dzięki kompresji statycznej

Tak jak poprzednio, znaczny spadek rozmiaru pakietów!

Podsumowanie

W tym laboratorium kodowania omówiliśmy proces minifikacji i kompresji kodu źródłowego. Obie te techniki stają się domyślnymi w wielu dostępnych obecnie narzędziach, dlatego ważne jest, aby dowiedzieć się, czy Twoje narzędzia już je obsługują, czy też musisz samodzielnie stosować oba procesy.