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

W tym laboratorium programistycznym dowiesz się, jak zoptymalizowanie i skompresowanie pakietu JavaScript w przypadku tej aplikacji poprawia wydajność strony przez zmniejszenie rozmiaru żądania aplikacji.

Zrzut ekranu aplikacji

Zmierz odległość

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

  • Aby wyświetlić podgląd strony, kliknij Wyświetl aplikację. Następnie kliknij Pełny ekran peł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. 🐈

Sprawdź, jak duży jest ten wniosek:

  1. Aby otworzyć Narzędzia dla programistów, naciśnij `Control+Shift+J` (lub `Command+Option+J` na Macu).
  2. Kliknij kartę Sieć.
  3. Zaznacz pole wyboru Disable cache (Wyłącz pamięć podręczną).
  4. Przeł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ż ten 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 w ten sam sposób. Zastosowanie tego rodzaju zaciemnienia kodu może pomóc w zmniejszeniu rozmiaru pliku. 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 TerserWebpackPlugin wtyczkę 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 zminimalizowany 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óre możesz użyć zamiast tej, na przykład BabelMinifyWebpackPluginClosureCompilerPlugin.
  • Jeśli nie używasz w ogóle narzędzia do tworzenia pakietów modułów, użyj Terser jako narzędzia wiersza poleceń lub dołącz je 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 kompresji, to w rzeczywistości nie jest on kompresowany w dosłownym tego słowa znaczeniu.

Kompresja zwykle odnosi się do kodu zmodyfikowanego za pomocą algorytmu kompresji danych. W przeciwieństwie do kompresji, która powoduje, że kod jest w pełni prawidłowy, skompresowany kod musi zostać rozpakowany przed użyciem.

Do każdego żądania i odpowiedzi HTTP przeglądarki i serwery internetowe mogą dodawać nagłówki, aby uwzględnić dodatkowe informacje o pobieranym lub otrzymanym zasobie. Można to sprawdzić na karcie Headers w panelu sieci w Narzędziach deweloperskich, gdzie wyświetlane są 3 typy:

  • Ogólne to nagłówki ogólne dotyczące 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 akceptacji

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 w interakcjach serwera i klienta. 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ą ukończoną 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 trzeba 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 funkcja tylko importuje express i używa pośrednika express.static do wczytywania wszystkich statycznych plików HTML, JS i CSS w katalogu public/ (pliki te są tworzone przez webpack przy każdym tworzeniu).

Aby mieć pewność, że wszystkie zasoby są kompresowane za każdym razem, gdy są żądane, możesz użyć biblioteki pośredniczącej compression. 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 element pośredniczący przed zamontowaniem 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

  • Opóźnienia spowodowane wysokim poziomem kompresji nie stanowią już problemu. 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

Ponieważ kompresja statyczna polega na wcześniejszym skompresowaniu plików, ustawienia webpacka można zmodyfikować, aby kompresować zasoby w ramach etapu kompilacji. 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. Aby dowiedzieć się, jak dodać opcje, aby użyć innego algorytmu lub uwzględnić/wykluczyć określone pliki, zapoznaj się z tą dokumentacją.

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). Domyślnie CompressionPlugin kompresuje też 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 używa się funkcji wywołania zwrotnego, aby 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

Podobnie jak wcześniej, rozmiar pakietu został znacznie zmniejszony.

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.