W tym ćwiczeniu poprawisz wydajność prostej aplikacji, która pozwala użytkownikom oceniać losowe koty. Dowiedz się, jak zoptymalizować pakiet JavaScriptu, minimalizując ilość kodu poddawanego transpilacji.

W przykładowej 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 bieżącym obrazem kota.
Zmierz odległość
Zawsze warto zacząć od sprawdzenia witryny przed dodaniem jakichkolwiek optymalizacji:
- Aby wyświetlić podgląd witryny, naciśnij Wyświetl aplikację, a następnie Pełny ekran
.
- Naciśnij `Control+Shift+J` (lub `Command+Option+J` na Macu), aby otworzyć Narzędzia deweloperskie.
- Kliknij kartę Sieć.
- Zaznacz pole wyboru Wyłącz pamięć podręczną.
- Załaduj ponownie aplikację.

Ta aplikacja używa ponad 80 KB. Czas sprawdzić, czy niektóre części pakietu nie są używane:
Naciśnij
Control+Shift+P(lubCommand+Shift+Pna Macu), aby otworzyć menu Polecenie.
Wpisz
Show Coveragei naciśnijEnter, aby wyświetlić kartę Pokrycie.Na karcie Pokrycie kliknij Załaduj ponownie, aby ponownie załadować aplikację podczas rejestrowania pokrycia.

Sprawdź, ile kodu zostało użyte, a ile załadowane w przypadku głównego pakietu:
Ponad połowa pakietu (44 KB) nie jest nawet wykorzystywana. Dzieje się tak, ponieważ duża część kodu składa się z polyfilli, które zapewniają działanie aplikacji w starszych przeglądarkach.
Używanie @babel/preset-env
Składnia języka JavaScript jest zgodna ze standardem ECMAScript, czyli ECMA-262. Nowe wersje specyfikacji są publikowane co roku i zawierają nowe funkcje, które przeszły proces propozycji. Każda z głównych przeglądarek jest zawsze na innym etapie obsługi tych funkcji.
W aplikacji używane są te funkcje ES2015:
Używana jest też ta funkcja ES2017:
Możesz przejrzeć kod źródłowy w pliku src/index.js, aby zobaczyć, jak to wszystko jest używane.
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, który mogą zrozumieć starsze przeglądarki i środowiska. Dzieje się tak na 2 sposoby:
- Dołączone są biblioteki polyfill, które emulują nowsze funkcje ES2015+, dzięki czemu można używać ich interfejsów API, nawet jeśli nie są obsługiwane przez przeglądarkę. Oto przykład polyfill metody
Array.includes. - Wtyczki służą do przekształcania kodu ES2015 (lub nowszego) na starszą składnię ES5. Ponieważ są to zmiany związane ze składnią (np. funkcje strzałkowe), nie można ich emulować za pomocą polyfilli.
Sprawdź w pliku package.json, które biblioteki Babel są uwzględnione:
"dependencies": {
"@babel/polyfill": "^7.0.0"
},
"devDependencies": {
//...
"babel-loader": "^8.0.2",
"@babel/core": "^7.1.0",
"@babel/preset-env": "^7.1.0",
//...
}
@babel/coreto podstawowy kompilator Babel. Dzięki temu wszystkie konfiguracje Babel są zdefiniowane w pliku.babelrcw katalogu głównym projektu.babel-loaderzawiera Babel w procesie kompilacji webpack.
Teraz spójrz na webpack.config.js, aby zobaczyć, jak babel-loader jest uwzględniony jako reguła:
module: {
rules: [
//...
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
},
@babel/polyfilludostępnia wszystkie niezbędne polyfille dla nowszych funkcji ECMAScript, dzięki czemu mogą one działać w środowiskach, które ich nie obsługują. Jest już zaimportowany na samej górze plikusrc/index.js.
import "./style.css";
import "@babel/polyfill";
@babel/preset-envokreśla, które przekształcenia i polyfille są niezbędne w przypadku przeglądarek lub środowisk wybranych jako docelowe.
Spójrz na plik konfiguracji Babel, .babelrc, aby zobaczyć, jak jest uwzględniony:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions"
}
]
]
}
To jest konfiguracja Babel i webpack. Jeśli używasz innego bundlera modułów niż webpack, dowiedz się, jak dołączyć Babel do aplikacji.
Atrybut targets w .babelrc określa, na które przeglądarki jest kierowana reklama. @babel/preset-env jest zintegrowany z browserslist, co oznacza, że pełną listę zgodnych zapytań, których można używać w tym polu, znajdziesz w dokumentacji browserslist.
Wartość "last 2 versions" transpiluje kod w aplikacji dla 2 ostatnich wersji każdej przeglądarki.
Debugowanie
Aby uzyskać pełny obraz wszystkich elementów docelowych Babel w przeglądarce, a także wszystkich uwzględnionych 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 spójrz na logi stanu Glitch u dołu edytora.
Przeglądarki docelowe
Babel rejestruje w konsoli szereg szczegółów dotyczących procesu kompilacji, w tym wszystkie środowiska docelowe, dla których kod został skompilowany.

Zwróć uwagę, że na tej liście znajdują się wycofane przeglądarki, takie jak Internet Explorer. To problem, ponieważ nieobsługiwane przeglądarki nie będą miały dodanych nowszych funkcji, a Babel nadal będzie transpilować dla nich konkretną składnię. Jeśli użytkownicy nie korzystają z tej przeglądarki, aby uzyskać dostęp do Twojej witryny, niepotrzebnie zwiększa to rozmiar pakietu.
Babel rejestruje też listę używanych wtyczek przekształceń:

To długa lista. Są to wszystkie wtyczki, których Babel musi używać, aby przekształcić dowolną składnię ES2015+ w starszą składnię dla wszystkich docelowych przeglądarek.
Babel nie pokazuje jednak żadnych konkretnych używanych polyfilli:
![]()
Dzieje się tak, ponieważ cały plik @babel/polyfill jest importowany bezpośrednio.
Wczytywanie 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 potrzebne w docelowych przeglądarkach, dodaj do konfiguracji useBuiltIns: 'entry'.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true
"useBuiltIns": "entry"
}
]
]
}
Załaduj ponownie aplikację. Teraz możesz zobaczyć wszystkie uwzględnione polyfille:

Chociaż teraz uwzględniane są tylko potrzebne polyfille dla "last 2 versions", lista nadal jest bardzo długa. Dzieje się tak, ponieważ nadal uwzględniane są polyfille wymagane przez przeglądarki docelowe w przypadku każdej nowszej funkcji. Zmień wartość atrybutu na usage, aby uwzględnić tylko te, które są potrzebne do funkcji używanych w kodzie.
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"debug": true,
"useBuiltIns": "entry"
"useBuiltIns": "usage"
}
]
]
}
Dzięki temu polyfille są automatycznie dołączane tam, gdzie są potrzebne.
Oznacza to, że możesz usunąć import @babel/polyfill w pliku src/index.js..
import "./style.css";
import "@babel/polyfill";
Teraz uwzględniane są tylko wymagane polyfille potrzebne aplikacji.

Rozmiar pakietu aplikacji jest znacznie mniejszy.

Zawężenie listy obsługiwanych przeglądarek
Liczba uwzględnionych przeglądarek jest nadal dość duża, a niewielu użytkowników korzysta z wycofanych przeglądarek, takich jak Internet Explorer. Zaktualizuj konfiguracje w ten sposób:
{
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 versions",
"targets": [">0.25%", "not ie 11"],
"debug": true,
"useBuiltIns": "usage",
}
]
]
}
Sprawdź szczegóły pobranego pakietu.

Ponieważ aplikacja jest bardzo mała, te zmiany nie mają większego znaczenia. Zalecamy jednak użycie udziału przeglądarki w rynku (np. ">0.25%") i wykluczenie konkretnych przeglądarek, z których Twoi użytkownicy na pewno nie korzystają. Aby dowiedzieć się więcej, przeczytaj artykuł Jamesa Kyle'a „Last 2 versions” considered harmful.
Użyj <script type="module">
Można jeszcze coś poprawić. Chociaż usunięto wiele nieużywanych polyfilli, wiele z nich jest dostarczanych, mimo że nie są potrzebne w niektórych przeglądarkach. Dzięki modułom można pisać nowszą składnię i wysyłać ją bezpośrednio do przeglądarek bez używania niepotrzebnych polyfilli.
Moduły JavaScript to stosunkowo nowa funkcja obsługiwana we wszystkich najważniejszych przeglądarkach.
Moduły można tworzyć za pomocą atrybutu type="module", aby definiować skrypty, które importują i eksportują z innych modułów. 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 JavaScriptu (zamiast Babel). Oznacza to, że konfigurację Babel można zmodyfikować tak, aby wysyłała do przeglądarki 2 różne wersje aplikacji:
- Wersja, która działałaby w nowszych przeglądarkach obsługujących moduły i zawiera moduł, który w dużej mierze nie jest transpilowany, ale ma mniejszy rozmiar pliku.
- wersję zawierającą większy, transpilowany skrypt, który działa w każdej starszej przeglądarce;
Używanie modułów ES z Babel
Aby mieć osobne ustawienia @babel/preset-env dla obu wersji aplikacji, usuń plik .babelrc. Ustawienia Babel można dodać do konfiguracji webpack, określając 2 różne formaty kompilacji dla każdej wersji aplikacji.
Zacznij od dodania do pliku webpack.config.js konfiguracji starszego skryptu:
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
}
Zauważ, że zamiast wartości targets dla "@babel/preset-env" użyto esmodules z wartością false. Oznacza to, że Babel zawiera wszystkie niezbędne przekształcenia i polyfill, aby obsługiwać 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. Są one współdzielone przez moduł i starsze skrypty wyświetlane 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 miejsca, w którym zdefiniowano 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łówną różnicą jest to, że w nazwie pliku wyjściowego używane jest rozszerzenie .mjs. Wartość esmodules jest ustawiona na „true”, co oznacza, że kod wyjściowy w tym module jest mniejszym, mniej skompilowanym skryptem, który w tym przykładzie nie przechodzi żadnej transformacji, ponieważ wszystkie używane funkcje są już obsługiwane w przeglądarkach obsługujących moduły.
Na samym końcu pliku wyeksportuj obie konfiguracje w jednej tablicy.
module.exports = [
legacyConfig, moduleConfig
];
Teraz tworzy to zarówno mniejszy moduł dla przeglądarek, które go obsługują, jak i większy skrypt po transpilacji dla starszych przeglądarek.
Przeglądarki obsługujące moduły ignorują skrypty z atrybutem nomodule.
Z kolei przeglądarki, które nie obsługują modułów, ignorują elementy skryptu z atrybutem type="module". Oznacza to, że możesz dołączyć moduł, a także skompilowaną kreację zastępczą. Najlepiej, aby obie wersje aplikacji były w index.html, na przykład tak:
<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ą na odwrót.
Ważne jest, aby pamiętać, że w przeciwieństwie do zwykłych skryptów skrypty modułów są zawsze domyślnie odroczone.
Jeśli chcesz, aby odpowiedni skrypt nomodule również był odroczony i wykonywany 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ą należy zrobić, jest dodanie atrybutów module i nomodule odpowiednio do modułu i starszego skryptu. Zaimportuj ScriptExtHtmlWebpackPlugin na samej górze 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 oraz atrybut nomodule do wszystkich modułów skryptu .js.
Wyświetlanie modułów w dokumencie HTML
Ostatnią rzeczą, którą należy zrobić, jest wygenerowanie zarówno starszych, jak i nowoczesnych elementów skryptu do pliku HTML. Niestety wtyczka, która tworzy ostateczny plik HTML, HTMLWebpackPlugin, nie obsługuje obecnie danych wyjściowych skryptów module i nomodule. Chociaż istnieją obejścia i osobne wtyczki, które rozwiązują ten problem, takie jak BabelMultiTargetPlugin i HTMLWebpackMultiBuildPlugin, w tym samouczku używamy prostszego podejścia polegającego na ręcznym dodaniu elementu skryptu modułu.
Dodaj do pliku src/index.js na końcu te wiersze:
...
</form>
<script type="module" src="main.mjs"></script>
</body>
</html>
Teraz wczytaj aplikację w przeglądarce obsługującej moduły, np. w najnowszej wersji Chrome.

Pobierany jest tylko moduł, który ma znacznie mniejszy rozmiar pakietu, ponieważ jest w dużej mierze nietranspilowany. Drugi element skryptu jest całkowicie ignorowany przez przeglądarkę.
Jeśli wczytasz aplikację w starszej przeglądarce, pobrany zostanie tylko większy, transpilowany skrypt ze wszystkimi potrzebnymi elementami polyfill i przekształceniami. Oto zrzut ekranu ze wszystkimi żądaniami wysłanymi w starszej wersji Chrome (wersja 38).

Podsumowanie
Wiesz już, jak używać @babel/preset-env, aby udostępniać tylko niezbędne polyfille wymagane przez przeglądarki docelowe. Wiesz też, jak moduły JavaScriptu mogą dodatkowo zwiększyć wydajność dzięki dostarczaniu 2 różnych transpilowanych wersji aplikacji. Teraz, gdy już wiesz, jak te 2 techniki mogą znacznie zmniejszyć rozmiar pakietu, możesz zacząć optymalizację.