W tym laboratorium programistycznym możesz zwiększyć wydajność prostej aplikacji, która umożliwia użytkownikom ocenianie losowych kotów. Dowiedz się, jak zoptymalizować pakiet JavaScript, minimalizując ilość kodu poddawanego transpilacji.
W próbnej aplikacji możesz wybrać słowo lub emotikon, aby wyrazić, jak bardzo podoba Ci się dany kot. Gdy klikniesz przycisk, aplikacja wyświetli wartość przycisku pod aktualnym obrazem kota.
Zmierz odległość
Zanim zaczniesz optymalizować witrynę, warto najpierw ją sprawdzić:
- Aby wyświetlić podgląd strony, kliknij Wyświetl aplikację. Następnie kliknij Pełny ekran .
- Aby otworzyć Narzędzia dla programistów, naciśnij `Control+Shift+J` (lub `Command+Option+J` na Macu).
- Kliknij kartę Sieć.
- Zaznacz pole wyboru Disable cache (Wyłącz pamięć podręczną).
- Przeładuj aplikację.
Ta aplikacja zajmuje ponad 80 KB! Czas sprawdzić, czy części pakietu nie są używane:
Naciśnij
Control+Shift+P
(lubCommand+Shift+P
na Macu), aby otworzyć menu Command.Aby wyświetlić kartę Pokrycie, wpisz
Show Coverage
i naciśnijEnter
.Na karcie Pokrycie kliknij Odśwież, aby ponownie załadować aplikację podczas rejestrowania pokrycia.
Sprawdź, ile kodu zostało użyte i ile zostało załadowane w przypadku głównego pakietu:
Ponad połowa pakietu (44 KB) nie jest nawet wykorzystana. Dzieje się tak, ponieważ wiele elementów kodu składa się z polyfills, aby zapewnić działanie aplikacji w starszych przeglądarkach.
Użyj @babel/preset-env.
Składnia języka JavaScript jest zgodna ze standardem ECMAScript lub ECMA-262. Nowe wersje specyfikacji są publikowane co roku i zawierają nowe funkcje, które przeszły proces zgłaszania. Każda z głównych przeglądarek jest na innym etapie obsługi tych funkcji.
W aplikacji są używane te funkcje ES2015:
Używana jest też ta funkcja ES2017:
Możesz zapoznać się z kodem źródłowym w pliku src/index.js
, aby zobaczyć, jak to działa.
Wszystkie te funkcje są obsługiwane w najnowszej wersji Chrome, ale co z innymi przeglądarkami, które ich nie obsługują? Babel, który jest dołączony do aplikacji, to najpopularniejsza biblioteka używana do kompilowania kodu zawierającego nowszą składnię na kod zrozumiały dla starszych przeglądarek i środowisk. Robi to na 2 sposoby:
- Polyfille są uwzględniane, aby emulować nowsze funkcje ES2015+, dzięki czemu można używać ich interfejsów API, nawet jeśli przeglądarka ich nie obsługuje. Oto przykład polyfilla metody
Array.includes
. - Wtyczki służą do przekształcania kodu ES2015 (lub nowszego) w starszą składnię ES5. Są to zmiany związane z składnią (np. funkcje strzałek), więc nie można ich emulować za pomocą polyfillów.
Aby sprawdzić, które biblioteki Babel są uwzględnione, otwórz plik package.json
:
"dependencies": {
"@babel/polyfill": "^7.0.0"
},
"devDependencies": {
//...
"babel-loader": "^8.0.2",
"@babel/core": "^7.1.0",
"@babel/preset-env": "^7.1.0",
//...
}
@babel/core
to podstawowy kompilator Babel. W tym przypadku wszystkie konfiguracje Babel są zdefiniowane w pliku.babelrc
w katalogu głównym projektu.babel-loader
zawiera Babel w procesie kompilacji webpack.
Teraz spójrz na webpack.config.js
, aby zobaczyć, jak babel-loader
jest uwzględnione jako reguła:
module: { rules: [ //... { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } ] },
@babel/polyfill
udostępnia wszystkie niezbędne polyfille dla nowszych funkcji ECMAScript, aby mogły działać w środowiskach, które ich nie obsługują. Został już zaimportowany i znajduje się na samej górzesrc/index.js.
import "./style.css";
import "@babel/polyfill";
@babel/preset-env
określa, które przekształcenia i polyfille są potrzebne w przypadku przeglądarek lub środowisk wybranych jako cele.
Aby sprawdzić, jak jest on uwzględniony, otwórz plik konfiguracji Babel (.babelrc
):
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions"
}
]
]
}
Oto konfiguracja Babel i webpack. Dowiedz się, jak uwzględnić Babel w aplikacji, jeśli używasz innego modułu niż webpack.
Atrybut targets
w sekcji .babelrc
wskazuje, które przeglądarki są kierowane. @babel/preset-env
integruje się z listą browserslist, co oznacza, że w dokumentacji browserslist znajdziesz pełną listę zgodnych zapytań, których możesz używać w tym polu.
Wartość "last 2 versions"
przetłumaczy kod w aplikacji na dwie ostatnie wersje każdej przeglądarki.
Debugowanie
Aby uzyskać pełny przegląd wszystkich celów Babel w przeglądarce oraz wszystkich zawartych w nich przekształceń i polyfilli, dodaj pole debug
do .babelrc:
.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
}
]
]
}
- Kliknij Narzędzia.
- Kliknij Logi.
Załaduj ponownie aplikację i sprawdź logi stanu Glitch na dole edytora.
Przeglądarki docelowe
Babel rejestruje w konsoli wiele szczegółów dotyczących procesu kompilacji, w tym wszystkie środowiska docelowe, dla których został skompilowany kod.
Zwróć uwagę, że na tej liście znajdują się przeglądarki wycofane, takie jak Internet Explorer. Jest to problem, ponieważ w nieobsługiwanych przeglądarkach nie będą dostępne nowsze funkcje, a Babel będzie nadal transpilować dla nich określoną składnię. To niepotrzebnie zwiększa rozmiar pakietu, jeśli użytkownicy nie korzystają z tego przeglądarki, aby uzyskać dostęp do Twojej witryny.
Babel rejestruje też listę użytych wtyczek transformacji:
To dość długa lista. To wszystkie wtyczki, których Babel musi używać do przekształcania składni ES2015+ w starsza składnię we wszystkich docelowych przeglądarkach.
Babel nie pokazuje jednak konkretnych polyfillów, które są używane:
Dzieje się tak, ponieważ cały plik @babel/polyfill
jest importowany bezpośrednio.
Ładowanie polyfilli pojedynczo
Domyślnie Babel zawiera wszystkie polyfille potrzebne do pełnego środowiska ES2015+, gdy @babel/polyfill
jest importowany do pliku. Aby zaimportować konkretne polyfille wymagane w przypadku przeglądarek docelowych, dodaj do konfiguracji useBuiltIns: 'entry'
.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
"useBuiltIns": "entry"
}
]
]
}
Załaduj ponownie aplikację. Możesz teraz zobaczyć wszystkie uwzględnione polyfille:
Chociaż uwzględniliśmy tylko potrzebne rozwiązania polyfill dla "last 2 versions"
, lista jest nadal bardzo długa. Dzieje się tak, ponieważ nadal są uwzględniane polyfille potrzebne w przypadku przeglądarek docelowych dla każdej nowszej funkcji. Zmień wartość atrybutu na usage
, aby uwzględnić tylko te, które są potrzebne do obsługi funkcji używanych w kodzie.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true,
"useBuiltIns": "entry"
"useBuiltIns": "usage"
}
]
]
}
Dzięki temu w razie potrzeby polyfille są dołączane automatycznie.
Oznacza to, że możesz usunąć import @babel/polyfill
w sekcji src/index.js.
.
import "./style.css";
import "@babel/polyfill";
Teraz uwzględniane są tylko wymagane polyfille potrzebne do aplikacji.
Rozmiar pakietu aplikacji jest znacznie mniejszy.
Zawężenie listy obsługiwanych przeglądarek
Liczba uwzględnionych docelowych przeglądarek jest nadal dość duża, a niewielu użytkowników korzysta z przeglądarek wycofanych z obsługi, takich jak Internet Explorer. Zaktualizuj konfiguracje, aby wyglądały tak:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"targets": [">0.25%", "not ie 11"],
"debug": true,
"useBuiltIns": "usage",
}
]
]
}
Sprawdź szczegóły pobranego pakietu.
Aplikacja jest bardzo mała, więc te zmiany nie mają większego znaczenia. Zalecamy jednak stosowanie procentowego udziału przeglądarki w rynku (np. ">0.25%"
) oraz wykluczanie konkretnych przeglądarek, których Twoi użytkownicy na pewno nie używają. Aby dowiedzieć się więcej, przeczytaj artykuł „Ostatnie 2 wersje uznane za szkodliwe” autorstwa Jamesa Kyle’a.
Użyj tagu <script type="module">
Nadal jest miejsce na poprawę. Chociaż usunięto wiele nieużywanych polyfillów, wiele z nich jest nadal dostarczanych, ale nie jest potrzebnych w niektórych przeglądarkach. Dzięki modułom nowszą składnię można pisać i przesyłać bezpośrednio do przeglądarek bez używania zbędnych polyfilli.
Moduły JavaScript to stosunkowo nowa funkcja obsługiwana w wszystkich głównych przeglądarkach.
Aby zdefiniować skrypty, które mają być importowane i eksportowane z innych modułów, możesz utworzyć moduły za pomocą atrybutu type="module"
. Na przykład:
// math.mjs
export const add = (x, y) => x + y;
<!-- index.html -->
<script type="module">
import { add } from './math.mjs';
add(5, 2); // 7
</script>
Wiele nowszych funkcji ECMAScript jest już obsługiwanych w środowiskach, które obsługują moduły JavaScript (bez konieczności korzystania z Babel). Oznacza to, że plik konfiguracji Babel można zmodyfikować, aby wysyłać do przeglądarki 2 różne wersje aplikacji:
- Wersja, która działa w nowszych przeglądarkach obsługujących moduły i zawiera moduł, który jest w większości nieprzetłumaczone, ale ma mniejszy rozmiar pliku.
- wersja, która zawiera większy skompilowany skrypt, który działa w dowolnej starszej przeglądarce;
Korzystanie z modułów ES w Babelu
Aby mieć osobne ustawienia @babel/preset-env
dla 2 wersji aplikacji, usuń plik .babelrc
. Ustawienia Babel można dodać do konfiguracji webpack, podając 2 różne formaty kompilacji dla każdej wersji aplikacji.
Zacznij od dodania konfiguracji starszego skryptu do pliku webpack.config.js
:
const legacyConfig = {
entry,
output: {
path: path.resolve(__dirname, "public"),
filename: "[name].bundle.js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
targets: {
esmodules: false
}
}]
]
}
},
cssRule
]
},
plugins
}
Zwróć uwagę, że zamiast wartości targets
dla elementu "@babel/preset-env"
używana jest wartość esmodules
z wartością false
. Oznacza to, że Babel zawiera wszystkie niezbędne przekształcenia i polyfille, aby kierować na każdą przeglądarkę, która nie obsługuje jeszcze modułów ES.
Dodaj obiekty entry
, cssRule
i corePlugins
na początku pliku webpack.config.js
. Wszystkie te dane są współdzielone przez moduł i starszą wersję skryptu wyświetlanego w przeglądarce.
const entry = {
main: "./src"
};
const cssRule = {
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
};
const plugins = [
new ExtractTextPlugin({filename: "[name].css", allChunks: true}),
new HtmlWebpackPlugin({template: "./src/index.html"})
];
Teraz w podobny sposób utwórz obiekt konfiguracji dla skryptu modułu poniżej, w którym zdefiniowano zmienną legacyConfig
:
const moduleConfig = {
entry,
output: {
path: path.resolve(__dirname, "public"),
filename: "[name].mjs"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
targets: {
esmodules: true
}
}]
]
}
},
cssRule
]
},
plugins
}
Główna różnica polega na tym, że w nazwie pliku wyjściowego jest używane rozszerzenie .mjs
. Wartość esmodules
jest tutaj ustawiona na „PRAWDA”, co oznacza, że kod generowany w tym module jest krótszy i mniej skompilowany, a w tym przykładzie nie przechodzi przez żadną transformację, ponieważ wszystkie używane funkcje są już obsługiwane w przeglądarkach obsługujących moduły.
Pod koniec pliku wyeksportuj obie konfiguracje w jednej tablicy.
module.exports = [
legacyConfig, moduleConfig
];
Teraz tworzy on mniejszy moduł dla przeglądarek, które go obsługują, oraz większy skompilowany skrypt dla starszych przeglądarek.
Przeglądarki, które obsługują moduły, ignorują skrypty z atrybutem nomodule
.
Z kolei przeglądarki, które nie obsługują modułów, ignorują elementy skryptu z wartością type="module"
. Oznacza to, że możesz dołączyć moduł i skompilowany plik zastępczy. W idealnej sytuacji obie wersje aplikacji powinny być w formacie index.html
, na przykład:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js"></script>
Przeglądarki, które obsługują moduły, pobierają i wykonują main.mjs
, a ignorują main.bundle.js.
. Przeglądarki, które nie obsługują modułów, robią odwrotnie.
Pamiętaj, że w odróżnieniu od zwykłych skryptów skrypty modułów są zawsze domyślnie opóźnione.
Jeśli chcesz, aby odpowiedni skrypt nomodule
był również opóźniony i uruchamiany dopiero po przeanalizowaniu, musisz dodać atrybut defer
:
<script type="module" src="main.mjs"></script>
<script nomodule src="main.bundle.js" defer></script>
Ostatnią rzeczą, którą musisz zrobić, jest dodanie atrybutów module
i nomodule
odpowiednio do modułu i starszego skryptu. Zaimportuj ScriptExtHtmlWebpackPlugin na samym szczycie pliku webpack.config.js
:
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
Teraz zaktualizuj tablicę plugins
w konfiguracjach, aby uwzględnić tę wtyczkę:
const plugins = [ new ExtractTextPlugin({filename: "[name].css", allChunks: true}), new HtmlWebpackPlugin({template: "./src/index.html"}), new ScriptExtHtmlWebpackPlugin({ module: /\.mjs$/, custom: [ { test: /\.js$/, attribute: 'nomodule', value: '' }, ] }) ];
Te ustawienia wtyczki dodają atrybut type="module"
do wszystkich elementów skryptu .mjs
, a także atrybut nomodule
do wszystkich modułów skryptu .js
.
Wyświetlanie modułów w dokumencie HTML
Ostatnią rzeczą, którą trzeba zrobić, jest wyprowadzenie do pliku HTML zarówno starszych, jak i nowych elementów skryptu. Niestety wtyczka, która tworzy finalny plik HTML (HTMLWebpackPlugin
), nie obsługuje obecnie wyjścia skryptów module i nomodule. Chociaż istnieją obejścia i osobne wtyczki stworzone w celu rozwiązania tego problemu, takie jak BabelMultiTargetPlugin i HTMLWebpackMultiBuildPlugin, w tym samouczku zastosowano prostsze podejście polegające na ręcznym dodawaniu elementu skryptu modułu.
Dodaj do pliku src/index.js
te informacje na końcu pliku:
...
</form>
<script type="module" src="main.mjs"></script>
</body>
</html>
Teraz otwórz aplikację w przeglądarce obsługującej moduły, np. w najnowszej wersji Chrome.
Pobierany jest tylko moduł, który ma znacznie mniejszy rozmiar, ponieważ nie jest w dużej mierze przetłumaczony. Inny element skryptu jest całkowicie ignorowany przez przeglądarkę.
Jeśli ładujesz aplikację w starszej przeglądarce, pobierany jest tylko większy skompilowany skrypt ze wszystkimi niezbędnymi polyfillami i transformacjami. Oto zrzut ekranu ze wszystkimi żądaniami wysłanymi w starszej wersji Chrome (38).
Podsumowanie
Teraz wiesz, jak używać @babel/preset-env
, aby udostępniać tylko te polyfille, które są wymagane w przypadku wybranych przeglądarek. Wiesz też, że moduły JavaScript mogą zwiększyć wydajność, przesyłając 2 różne przetłumaczone wersje aplikacji. Teraz, gdy już wiesz, jak te 2 techniki mogą znacznie zmniejszyć rozmiar pakietu, możesz przystąpić do optymalizacji.