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

W tym laboratorium dowiesz się, jak minifikacja i kompresja pakietu JavaScript w przypadku tej aplikacji zwiększa wydajność strony przez zmniejszenie rozmiaru żądania aplikacji.

Zrzut ekranu aplikacji

Pomiary

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

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

Ta aplikacja, która została również omówiona w ćwiczeniach z programowania „Usuwanie nieużywanego kodu”, umożliwia głosowanie na ulubionego kociaka. 🐈

Teraz sprawdź rozmiar tej aplikacji:

  1. Aby otworzyć Narzędzia dla programistów, naciśnij Ctrl+Shift+J (lub Command+Option+J na Macu).
  2. Kliknij kartę Sieć.
  3. Zaznacz pole wyboru Wyłącz pamięć podręczną.
  4. Ponownie załaduj aplikację.

Rozmiar oryginalnego pakietu w panelu Sieć

W samouczku „Usuwanie nieużywanego kodu” udało się znacznie zmniejszyć rozmiar pakietu, ale 225 KB to nadal dość dużo.

Minifikacja

Spójrz na 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 bajtów.

Jeśli usuniesz wszystkie białe znaki, wynikowy kod będzie wyglądać tak:

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

Rozmiar pliku będzie teraz wynosić około 83 B. Jeśli zostanie on dodatkowo 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 wynosi teraz 62 B.

Z każdym krokiem kod staje się trudniejszy do odczytania. Jednak silnik JavaScript przeglądarki interpretuje każdy z nich w dokładnie ten sam sposób. Zaletą zaciemniania kodu w ten sposób jest możliwość zmniejszenia rozmiaru plików. 112 B to niewiele, ale i tak udało się zmniejszyć rozmiar o 50%!

W tej aplikacji jako narzędzia do tworzenia pakietów modułów używamy webpacka w wersji 4. Konkretną wersję znajdziesz w package.json.

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

Wersja 4 domyślnie minimalizuje pakiet w trybie produkcyjnym. Wykorzystuje TerserWebpackPluginwtyczkę do Terser. Terser to popularne narzędzie służące do kompresowania kodu JavaScript.

Aby zobaczyć, jak wygląda zminimalizowany kod, kliknij main.bundle.js w panelu Sieć w Narzędziach deweloperskich. Teraz kliknij kartę Odpowiedź.

Zminimalizowana odpowiedź

Kod w ostatecznej postaci, zminimalizowany i zniekształcony, jest wyświetlany w treści odpowiedzi. Aby dowiedzieć się, jak duży byłby pakiet, gdyby nie został zminimalizowany, 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 spora różnica. 😅

Zanim przejdziesz dalej, cofnij tutaj zmiany.

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

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

  • Jeśli używasz webpacka w wersji 4 lub nowszej, nie musisz niczego dodatkowo robić, ponieważ w trybie produkcyjnym kod jest domyślnie minimalizowany. 👍
  • Jeśli używasz starszej wersji webpacka, zainstaluj i uwzględnij TerserWebpackPlugin w procesie kompilacji webpacka. Szczegółowe informacje znajdziesz w dokumentacji.
  • Istnieją też inne wtyczki do minifikacji, których można używać zamiast tej, np. BabelMinifyWebpackPluginClosureCompilerPlugin.
  • Jeśli nie używasz żadnego narzędzia do łączenia modułów, użyj Terser jako narzędzia CLI lub dodaj go bezpośrednio jako zależność.

Kompresja

Chociaż termin „kompresja” jest czasami używany w luźnym znaczeniu do wyjaśnienia, w jaki sposób kod jest zmniejszany podczas procesu minifikacji, w dosłownym sensie nie jest on kompresowany.

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

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

  • General to ogólne nagłówki dotyczące całej interakcji żądanie-odpowiedź.
  • Nagłówki odpowiedzi zawierają listę nagłówków specyficznych dla rzeczywistej odpowiedzi z serwera.
  • Nagłówki żądań to lista nagłówków dołączonych do żądania przez klienta.

Zapoznaj się z nagłówkiem accept-encoding w sekcji Request Headers.

Nagłówek Accept-Encoding

accept-encoding jest używane przez przeglądarkę do określania obsługiwanych formatów kodowania treści lub algorytmów kompresji. Istnieje wiele algorytmów kompresji tekstu, ale w tym przypadku do kompresji (i dekompresji) żądań sieciowych HTTP obsługiwane są tylko 3:

  • Gzip (gzip): najczęściej używany format kompresji w interakcjach serwera i klienta. Jest on oparty na algorytmie Deflate i jest obsługiwany przez wszystkie obecne przeglądarki.
  • Deflate (deflate): rzadko używane.
  • Brotli (br): nowszy algorytm kompresji, który ma na celu dalsze zwiększenie współczynnika kompresji, co może skutkować jeszcze szybszym ładowaniem stron. Jest ona obsługiwana w najnowszych wersjach większości przeglądarek.

Przykładowa aplikacja w tym samouczku jest identyczna z aplikacją utworzoną w ćwiczeniach z programowania „Usuwanie nieużywanego kodu”, z wyjątkiem tego, że jako platformy serwera używamy teraz Express. W kolejnych sekcjach omówimy kompresję statyczną i dynamiczną.

Kompresja dynamiczna

Kompresja dynamiczna polega na kompresowaniu zasobów na bieżąco, gdy przeglądarka wysyła żądanie.

Zalety

  • Nie trzeba tworzyć ani aktualizować zapisanych skompresowanych wersji komponentów.
  • Kompresja w czasie rzeczywistym sprawdza się szczególnie 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 negatywnie wpłynąć na wydajność, ponieważ użytkownik musi poczekać, aż zasoby zostaną skompresowane, zanim serwer je wyśle.

Kompresja dynamiczna w Node/Express

Plik server.js odpowiada za skonfigurowanie serwera Node, który hostuje aplikację.

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 importuje to tylko express i używa express.static oprogramowania pośredniczącego do wczytywania wszystkich statycznych plików HTML, JS i CSS w katalogu public/ (a pliki te są tworzone przez webpack przy każdej kompilacji).

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

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

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

//...

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

Rozmiar pakietu z kompresją dynamiczną

Z 225 KB do 61,6 KB! W 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 statycznej kompresji polega na wcześniejszym skompresowaniu i zapisaniu zasobów.

Zalety

  • Opóźnienia spowodowane wysokim poziomem kompresji nie stanowią już problemu. Nie trzeba kompresować plików na bieżąco, ponieważ można je teraz pobierać bezpośrednio.

Wady

  • Zasoby muszą być kompresowane przy każdej kompilacji. Czas kompilacji może się znacznie wydłużyć, jeśli używane są wysokie poziomy kompresji.

Statyczna kompresja za pomocą Node/Express i webpack

Kompresja statyczna polega na kompresowaniu plików z wyprzedzeniem, więc ustawienia webpacka można zmodyfikować tak, aby kompresować komponenty w ramach etapu kompilacji. Do tego celu możesz użyć CompressionPlugin.

Zacznij od dodania go jako devDependency w package.json:

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

Podobnie jak w przypadku innych wtyczek webpacka zaimportuj ją w pliku konfiguracji:webpack.config.js:

const path = require("path");

//...

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

i dodaj go do 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 umożliwiające użycie innego algorytmu lub uwzględnienie/wykluczenie określonych plików.

Gdy aplikacja zostanie ponownie załadowana i przebudowana, zostanie utworzona skompresowana wersja głównego pakietu. Otwórz konsolę Glitch, aby zobaczyć, 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

Pliki wyjściowe w katalogu publicznym

Skompresowana wersja pakietu, main.bundle.js.gz, jest teraz zapisana tutaj. CompressionPlugin domyślnie kompresuje też index.html.

Następnie musisz poinformować serwer, aby wysyłał te skompresowane pliki, gdy tylko będą żądane ich oryginalne wersje w JavaScript. Możesz to zrobić, definiując nową ścieżkę w server.js, zanim pliki zostaną udostępnione 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 służy do informowania serwera, jak ma odpowiadać na żądanie GET dotyczące określonego punktu końcowego. Funkcja wywołania zwrotnego służy następnie do określania sposobu obsługi tego żądania. Trasa wygląda tak:

  • Określenie '*.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 ustawiany na wartość gzip.
  • Na koniec next() zapewnia, że sekwencja będzie kontynuowana w przypadku kolejnego wywołania zwrotnego.

Po ponownym załadowaniu aplikacji jeszcze raz sprawdź panel Network.

Zmniejszanie rozmiaru pakietu dzięki kompresji statycznej

Podobnie jak wcześniej, nastąpiło znaczne zmniejszenie rozmiaru pakietu.

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 warto sprawdzić, czy Twój zestaw narzędzi już je obsługuje, czy też musisz zacząć stosować oba procesy samodzielnie.