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

Dzięki temu ćwiczeniu w Codelabs dowiesz się, jak zmniejszanie i kompresowanie pakietu JavaScript w poniższej aplikacji zwiększa wydajność strony przez zmniejszenie rozmiaru żądania aplikacji.

Zrzut ekranu aplikacji

Zmierz odległość

Zanim zagłębimy się w temat optymalizacji, warto najpierw przeanalizować bieżący stan aplikacji.

  • Aby wyświetlić podgląd witryny, kliknij Wyświetl aplikację, a następnie naciśnij Pełny ekran pełny ekran.

Dzięki tej aplikacji możesz zagłosować na 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 pakietu w panelu Sieć

Choć w ćwiczeniu z programowania „Usuwanie nieużywanego kodu” zrobiliśmy już wiele w celu skrócenia pakietu, 225 KB to nadal dość duży rozmiar.

Minifikacja

Rozważ poniższy blok kodu.

function soNice() {
  let counter = 0;

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

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

Po usunięciu całej spacji wynikowy kod będzie wyglądał tak:

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

Rozmiar pliku wynosi teraz około 83 B. Jeśli później zmniejszysz długość nazwy zmiennej i zmodyfikujesz niektóre wyrażenia, końcowy kod może wyglądać tak:

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

Rozmiar pliku osiągnie teraz 62 B.

Z każdym etapem kod staje się coraz trudniejszy do odczytania. Mechanizm JavaScript w przeglądarce interpretuje jednak każdy z nich dokładnie w ten sam sposób. Zaletą takiego zaciemniania kodu może być mniejsza liczba plików. Na początku rozmiar 112 B był niewielki, ale i tak udało się zmniejszyć jego rozmiar o 50%.

W tej aplikacji do tworzenia pakietów modułów używany jest pakiet webpack w wersji 4. Konkretną wersję znajdziesz tutaj: package.json.

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

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

Aby zorientować się, jak wygląda zminifikowany kod, kliknij main.bundle.js, nie opuszczając panelu Network (Sieć) w Narzędziach deweloperskich. Kliknij kartę Odpowiedź.

Zmniejszona odpowiedź

Ostateczny kod, zminifikowany i zniekształcony, jest wyświetlany w treści odpowiedzi. Aby sprawdzić, jak duży był pakiet, gdyby nie został zmniejszony, 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ć w Narzędziach deweloperskich.

Rozmiar pakietu: 767 KB

To spora różnica. 😅

Zanim przejdziesz dalej, cofnij te zmiany.

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

Uwzględnienie procesu minimalizowania kodu w aplikacji zależy od używanych narzędzi:

  • Jeśli używany jest pakiet webpack w wersji 4 lub nowszej, nie musisz wykonywać żadnych dodatkowych czynności, ponieważ kod jest domyślnie zmniejszony w trybie produkcyjnym. 👍
  • Jeśli używana jest starsza wersja pakietu internetowego, zainstaluj pakiet TerserWebpackPlugin i uwzględnij go w procesie kompilacji pakietu internetowego. Szczegółowo opisujemy to 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 czasami luźno używany do wyjaśnienia sposobu zmniejszania kodu podczas minifikacji, nie jest on tak naprawdę kompresowany w dosłownym sensie.

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ć headers zawierające dodatkowe informacje o pobieranym lub odbieranym zasobie. Znajdziesz je na karcie Headers w panelu Network (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ź.
  • W sekcji Nagłówki odpowiedzi znajduje się lista nagłówków charakterystycznych dla rzeczywistej odpowiedzi z serwera.
  • W sekcji Nagłówki żądania wyświetlana jest lista nagłówków dołączonych do żądania przez klienta.

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

Zaakceptuj nagłówek kodowania

accept-encoding pozwala przeglądarce określić, jakie formaty kodowania treści lub algorytmy kompresji obsługuje. 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. Opiera się na algorytmie Deflate i jest obsługiwany we wszystkich aktualnych przeglądarkach.
  • Zdefiniowane (deflate): rzadko używane.
  • Brotli (br): nowszy algorytm kompresji, którego celem jest dalsza poprawa współczynników kompresji, co może spowodować jeszcze szybsze wczytywanie stron. Jest on obsługiwany w najnowszych wersjach większości przeglądarek.

Przykładowa aplikacja w tym samouczku jest taka sama jak aplikacja ukończona w ramach ćwiczenia „Usuwanie nieużywanego kodu” z wyjątkiem tego, że jako platforma serwera działa teraz Express. W kolejnych sekcjach omówimy kompresję statyczną i dynamiczną.

Kompresja dynamiczna

Kompresja dynamiczna polega na kompresowaniu zasobów na bieżąco w odpowiedzi na żądanie przeglądarki.

Zalety

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

Wady

  • Kompresja plików na wyższych poziomach w celu uzyskania lepszych współczynników kompresji trwa dłużej. Może to spowodować trafienie na wydajność, gdy użytkownik czeka na skompresowanie zasobów przed ich wysłaniem przez serwer.

Dynamiczna kompresja Node/Express

Plik server.js odpowiada za skonfigurowanie serwera węzła, 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ć plik express i użyć oprogramowania pośredniczącego express.static, aby wczytać wszystkie statyczne pliki HTML, JS i CSS w katalogu public/ (a pliki te są tworzone w pakiecie internetowym przy każdej kompilacji).

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

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

I 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'));

//...

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

Rozmiar pakietu z kompresją dynamiczną

Z 225 KB do 61,6 KB. Nagłówek content-encoding w interfejsie Response Headers wskazuje, że serwer wysyła plik z kodowaniem gzip.

Nagłówek kodowania treści

Kompresja statyczna

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

Zalety

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

Wady

  • Zasoby muszą być skompresowane przy każdej kompilacji. Czas kompilacji może się znacznie wydłużyć w przypadku użycia wysokiego poziomu kompresji.

Kompresja statyczna z użyciem Node/Express i pakietu 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. Można do tego użyć CompressionPlugin.

Najpierw dodaj go jako devDependency w aplikacji package.json:

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

Tak jak w przypadku każdej innej wtyczki pakietu internetowego, zaimportuj ją do 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ą dyrektywy gzip. Zapoznaj się z dokumentacją, aby dowiedzieć się, jak dodać opcje użycia innego algorytmu lub uwzględnić bądź wykluczyć określone pliki.

Przy ponownym ładowaniu aplikacji tworzona jest skompresowana wersja pakietu głównego. Otwórz konsolę Glitcha, aby zobaczyć, co znajduje się w ostatecznym katalogu public/ obsługiwanym przez serwer węzłów.

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

Ostateczne pliki wyjściowe w katalogu publicznym

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

Kolejną rzeczą, jaką musisz zrobić, jest poinformowanie serwera, by wysyłał te pliki skompresowane gzip za każdym razem, gdy są wysyłane żądania ich oryginalnych wersji JS. Można to zrobić, definiując nową trasę w server.js przed udostępnieniem plików za pomocą 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 odpowiedzieć na żądanie GET określonego punktu końcowego. Następnie za pomocą funkcji wywołania zwrotnego można określić sposób obsługi tego żądania. Trasa wygląda tak:

  • Określenie '*.js' jako pierwszego argumentu oznacza, że działa on w przypadku każdego punktu końcowego, który jest wywoływany w celu pobrania pliku JS.
  • W wywołaniu zwrotnym do adresu URL żądania jest dołączony tag .gz, a nagłówek odpowiedzi Content-Encoding ma wartość gzip.
  • next() zapewnia też, że sekwencja będzie kontynuowana przy kolejnych wywołaniach zwrotnych.

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

Zmniejszenie rozmiaru pakietu dzięki kompresji statycznej

Tak jak wcześniej, znaczny spadek rozmiaru pakietów!

Podsumowanie

Omówiliśmy proces minifikacji i kompresowania kodu źródłowego. Obie te techniki stają się domyślne w wielu narzędziach dostępnych obecnie, dlatego ważne jest, aby sprawdzić, czy Twój łańcuch narzędzi je obsługuje, czy też lepiej wdrożyć oba te procesy samodzielnie.