Zwiększ wydajność, włączając nowoczesne zależności i dane wyjściowe JavaScriptu.
Ponad 90% przeglądarek obsługuje nowoczesny JavaScript, ale przewaga starszych wersji tego języka jest nadal źródłem problemów z wydajnością w internecie.
Nowoczesny JavaScript
Nowoczesny kod JavaScript nie jest charakteryzujący się kodem napisanym w konkretnej wersji specyfikacji ECMAScript, ale jego składnią zgodną ze wszystkimi nowoczesnymi przeglądarkami. Nowoczesne przeglądarki, takie jak Chrome, Edge, Firefox i Safari, stanowią ponad 90% rynku przeglądarek, a różne przeglądarki oparte na tych samych mechanizmach renderowania stanowią kolejne 5%. Oznacza to, że 95% globalnego ruchu w internecie pochodzi z przeglądarek, które obsługują najczęściej używane funkcje języka JavaScript w ciągu ostatnich 10 lat, w tym:
- Klasy (ES2015)
- Funkcje strzałek (ES2015)
- Generatory (ES2015)
- Blokowanie zakresu (ES2015)
- Destrukturyzacja (ES2015)
- Parametry spoczynku i rozkładania (ES2015)
- Skrócony opis obiektu (ES2015)
- Async/await (ES2017)
Funkcje nowszych wersji specyfikacji językowej są zwykle mniej obsługiwane w nowoczesnych przeglądarkach. Na przykład wiele funkcji ES2020 i ES2021 jest obsługiwanych tylko na 70% rynku przeglądarek – nadal jest to większość przeglądarek, ale to nie wystarcza, aby bezpiecznie polegać na tych funkcjach. Oznacza to, że chociaż „nowoczesny” JavaScript jest ruchomym celem, ES2017 ma najszerszy zakres zgodności z przeglądarkami, jednocześnie większość popularnych nowoczesnych funkcji składni. Inaczej mówiąc, ES2017 jest obecnie najbliższym nowoczesnej składni.
Starszy kod JavaScript
Starsza wersja JavaScriptu to kod, który w szczególności eliminuje używanie wszystkich powyższych funkcji językowych. Większość programistów pisze kod źródłowy z wykorzystaniem nowoczesnej składni, ale kompiluje wszystko do starszej składni, co zapewnia lepszą obsługę przeglądarek. Kompilowanie do starszej składni zwiększa obsługę przeglądarek, ale często efekt jest mniejszy, niż się spodziewamy. W wielu przypadkach pomoc rośnie z około 95% do 98%, ale wiąże się to ze znacznymi kosztami:
Starszy kod JavaScript jest zwykle o około 20% większy i wolniejszy niż jego odpowiednik nowoczesny. Braki w narzędziach i błędna konfiguracja często pogłębiają tę lukę jeszcze bardziej.
Zainstalowane biblioteki stanowią nawet 90% typowego produkcyjnego kodu JavaScript. Korzystanie z kodu biblioteki powoduje jeszcze większe obciążenie starszego kodu JavaScript z powodu elementów polyfill i elementów pomocniczych, których można uniknąć, publikując nowoczesny kod.
Nowoczesny JavaScript na npm
Niedawno w Node.js ustandaryzowane zostało pole "exports"
, które pozwala określać punkty wejścia dla pakietu:
{
"exports": "./index.js"
}
Moduły, do których odwołuje się pole "exports"
, wskazują na węzeł w wersji co najmniej 12.8, która obsługuje ES2019. Oznacza to, że każdy moduł, do którego odwołuje się pole "exports"
, można napisać we współczesnym kodzie JavaScript. Użytkownicy pakietów muszą założyć, że moduły z polem "exports"
zawierają nowoczesny kod i w razie potrzeby są transpilowane.
Tylko nowoczesne
Jeśli chcesz opublikować pakiet z nowoczesnym kodem i pozwolić klientowi na jego transpilację w przypadku użycia go jako zależności – używaj tylko pola "exports"
.
{
"name": "foo",
"exports": "./modern.js"
}
Nowoczesna ze starszą wersją zastępczą
Użyj pola "exports"
w połączeniu z polem "main"
, aby opublikować pakiet z wykorzystaniem nowoczesnego kodu. Uwzględnij też kreację zastępczą ES5 i CommonJS w przypadku starszych przeglądarek.
{
"name": "foo",
"exports": "./modern.js",
"main": "./legacy.cjs"
}
Nowoczesna z optymalizacją starszych kreacji zastępczych i pakietów ESM
Oprócz definiowania zastępczego punktu wejścia CommonJS możesz używać pola "module"
do wskazywania podobnego starszego pakietu kreacji zastępczych, ale takiego, który korzysta ze składni modułu JavaScript (import
i export
).
{
"name": "foo",
"exports": "./modern.js",
"main": "./legacy.cjs",
"module": "./module.js"
}
Wiele usług tworzenia pakietów, np. Webpack i Rollup, korzysta z tego pola, aby korzystać z funkcji modułu i potrząsać drzewami.
To jest starsza wersja pakietu, która nie zawiera żadnego nowoczesnego kodu poza składnią import
/export
, dlatego warto korzystać z tego podejścia, aby przesyłać nowoczesny kod ze starszą wersją kreacji zastępczej, która jest nadal optymalizowana pod kątem grupowania.
Nowoczesny JavaScript w aplikacjach
Zależności innych firm stanowią zdecydowaną większość typowego kodu JavaScript produkcyjnego w aplikacjach internetowych. Zależności npm były wcześniej publikowane jako starsza składnia ES5, ale nie jest to już bezpieczne założenie, a aktualizacje zależności mogą spowodować przerwanie obsługi przeglądarki w aplikacji.
Wraz ze wzrostem liczby pakietów npm do nowoczesnego JavaScriptu należy upewnić się, że narzędzia do kompilacji są skonfigurowane do ich obsługi. Istnieje duże prawdopodobieństwo, że niektóre z używanych przez Ciebie pakietów npm korzystają już z nowoczesnych funkcji językowych. Istnieje wiele opcji pozwalających wykorzystać nowoczesny kod z npm bez zakłócania działania aplikacji w starszych przeglądarkach, ale ogólnie pomysłem jest transpilowanie zależności systemu kompilacji z tą samą wartością docelową składni co kod źródłowy.
pakiet internetowy
Od wersji 5 można teraz określić składnię, która będzie używana podczas generowania kodu pakietów i modułów. Nie spowoduje to transpilacji kodu ani zależności, a jedynie na kod „klej” wygenerowany przez pakiet internetowy. Aby określić środowisko docelowe obsługi przeglądarek, dodaj do swojego projektu konfigurację_przeglądarki lub zrób to bezpośrednio w konfiguracji pakietu internetowego:
module.exports = {
target: ['web', 'es2017'],
};
Możesz też skonfigurować pakiet internetowy tak, aby generował zoptymalizowane pakiety, które pomijają niepotrzebne funkcje kodu podczas kierowania na nowoczesne środowisko modułów ES. Spowoduje to też skonfigurowanie pakietu internetowego w celu wczytywania pakietów podzielonych kodu za pomocą <script type="module">
.
module.exports = {
target: ['web', 'es2017'],
output: {
module: true,
},
experiments: {
outputModule: true,
},
};
Dostępnych jest wiele wtyczek do pakietów internetowych, które umożliwiają kompilowanie i wysyłanie nowoczesnego kodu JavaScriptu z zachowaniem obsługi starszych przeglądarek, takich jak Optimize Plugin i BabelEsmPlugin.
Wtyczka Optimize
wtyczka Optimize to wtyczka pakietu internetowego, która przekształca końcowy pakiet kodu ze starszego kodu JavaScript na starszy kod JavaScript zamiast z poszczególnych plików źródłowych. Jest to samodzielna konfiguracja, która umożliwia skonfigurowanie pakietu internetowego przy założeniu, że wszystko, co związane jest z nowoczesnym kodem JavaScript, bez konieczności stosowania specjalnych rozgałęzień na wiele danych wyjściowych lub składni.
Wtyczka Optimize działa w pakietach, a nie na pojedynczych modułach, więc w równym stopniu przetwarza kod aplikacji i zależności. Pozwala to na bezpieczne korzystanie z nowoczesnych zależności JavaScriptu z npm, ponieważ ich kod zostanie połączony i przetranspilowany z zachowaniem prawidłowej składni. Może też być szybszy niż tradycyjne rozwiązania obejmujące 2 etapy kompilacji, a jednocześnie generować osobne pakiety dla nowoczesnych i starszych przeglądarek. Oba zestawy pakietów są wczytywane z użyciem wzoru modułu/nomodule.
// webpack.config.js
const OptimizePlugin = require('optimize-plugin');
module.exports = {
// ...
plugins: [new OptimizePlugin()],
};
Optimize Plugin
może być szybszy i wydajniejszy niż niestandardowe konfiguracje pakietów internetowych, które zwykle osobno łączą nowoczesny i starszy kod. Obsługuje też uruchamianie Babel i minimalizuje pakiety za pomocą platformy Terser z oddzielnymi, optymalnymi ustawieniami dla nowoczesnych i starszych danych wyjściowych. Na koniec elementy polyfill wymagane przez wygenerowane pakiety starszego typu są wyodrębniane do osobnego skryptu, dzięki czemu nigdy nie są duplikowane ani ładowane niepotrzebnie w nowszych przeglądarkach.
BabelEsmPlugin
BabelEsmPlugin to wtyczka pakietu internetowego, która działa wraz z @babel/preset-env, aby generować nowoczesne wersje istniejących pakietów i przesyłać mniej po transpilacji kodu do nowoczesnych przeglądarek. Jest to najpopularniejsze gotowe rozwiązanie do stosowania w przypadku modułu/nomodule. Jest ono wykorzystywane przez interfejsy Next.js i Preact CLI.
// webpack.config.js
const BabelEsmPlugin = require('babel-esm-plugin');
module.exports = {
//...
module: {
rules: [
// your existing babel-loader configuration:
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
],
},
plugins: [new BabelEsmPlugin()],
};
BabelEsmPlugin
obsługuje szeroką gamę konfiguracji pakietów internetowych, ponieważ obsługuje 2 głównie oddzielne kompilacje aplikacji. Podwójna kompilacja może trochę potrwać w przypadku dużych aplikacji. Ta metoda umożliwia jednak BabelEsmPlugin
bezproblemową integrację z istniejącymi konfiguracjami pakietu webpack i sprawia, że jest to jedna z najwygodniejszych dostępnych opcji.
Skonfiguruj moduł ładowania Babel do transpilacji modułów node_modules
Jeśli używasz interfejsu babel-loader
bez jednej z dwóch poprzednich wtyczek, musisz wykonać ważny krok, aby korzystać z nowoczesnych modułów npm JavaScript. Zdefiniowanie 2 oddzielnych konfiguracji babel-loader
pozwala automatycznie kompilować nowoczesne funkcje językowe dostępne w node_modules
do standardu ES2017, a jednocześnie transpilować własny kod własny za pomocą wtyczek i gotowych ustawień Babel zdefiniowanych w konfiguracji projektu. Nie spowoduje to wygenerowania nowoczesnych ani starszych pakietów na potrzeby konfiguracji modułu/nomodule, ale umożliwia instalowanie i używanie pakietów npm, które zawierają nowoczesny kod JavaScript, bez zakłócania pracy starszych przeglądarek.
webpack-plugin-modern-npm używa tej metody do kompilowania zależności npm, w których package.json
występuje pole "exports"
, ponieważ mogą one zawierać nowoczesną składnię:
// webpack.config.js
const ModernNpmPlugin = require('webpack-plugin-modern-npm');
module.exports = {
plugins: [
// auto-transpile modern stuff found in node_modules
new ModernNpmPlugin(),
],
};
Możesz też wdrożyć tę metodę ręcznie w konfiguracji pakietu internetowego, sprawdzając pole "exports"
w package.json
modułach w miarę ich rozwiązania. Pominięcie buforowania ze względu na zwięzłość może wyglądać tak:
// webpack.config.js
module.exports = {
module: {
rules: [
// Transpile for your own first-party code:
{
test: /\.js$/i,
loader: 'babel-loader',
exclude: /node_modules/,
},
// Transpile modern dependencies:
{
test: /\.js$/i,
include(file) {
let dir = file.match(/^.*[/\\]node_modules[/\\](@.*?[/\\])?.*?[/\\]/);
try {
return dir && !!require(dir[0] + 'package.json').exports;
} catch (e) {}
},
use: {
loader: 'babel-loader',
options: {
babelrc: false,
configFile: false,
presets: ['@babel/preset-env'],
},
},
},
],
},
};
Korzystając z tego podejścia, musisz upewnić się, że minimalna składnia obsługuje nowoczesną składnię. Zarówno w klasach Terser, jak i uglify-es dostępna jest opcja określenia wartości {ecma: 2017}
, aby zachować składnię ES2017, a w niektórych przypadkach generować składnię ES2017 podczas kompresji i formatowania.
Podsumowanie
Funkcja o pełnym zakresie ma wbudowaną obsługę generowania wielu zestawów pakietów w ramach 1 kompilacji i domyślnie generuje nowoczesny kod. W rezultacie usługę o pełnym zakresie można skonfigurować tak, aby generowała nowoczesne i starsze pakiety z oficjalnymi wtyczkami, których prawdopodobnie już używasz.
@rollup/plugin-babel
Jeśli korzystasz z usługi o pełnym zakresie, metoda getBabelOutputPlugin()
(udostępniona przez oficjalną wtyczkę Babel firmy Rollup) przekształca kod w wygenerowane pakiety, a nie w pojedyncze moduły źródłowe.
Funkcja o pełnym zakresie ma wbudowaną obsługę generowania wielu zestawów pakietów w ramach 1 kompilacji, z których każdy ma własne wtyczki. W ten sposób możesz utworzyć różne pakiety dla współczesnych i starszych wersji, przesyłając je przez inną konfigurację wtyczki wyjściowej Babel:
// rollup.config.js
import {getBabelOutputPlugin} from '@rollup/plugin-babel';
export default {
input: 'src/index.js',
output: [
// modern bundles:
{
format: 'es',
plugins: [
getBabelOutputPlugin({
presets: [
[
'@babel/preset-env',
{
targets: {esmodules: true},
bugfixes: true,
loose: true,
},
],
],
}),
],
},
// legacy (ES5) bundles:
{
format: 'amd',
entryFileNames: '[name].legacy.js',
chunkFileNames: '[name]-[hash].legacy.js',
plugins: [
getBabelOutputPlugin({
presets: ['@babel/preset-env'],
}),
],
},
],
};
Dodatkowe narzędzia do tworzenia
Podsumowania i pakiet webpack można łatwo konfigurować, co oznacza, że każdy projekt musi zaktualizować swoją konfigurację, aby w zależnościach mogła zostać zaktualizowana składnia JavaScriptu. Istnieją też narzędzia wyższego poziomu, które preferują konwencję i domyślne ustawienia zamiast konfiguracji, np. Parcel, Snowpack, Vite i WMR. Większość z tych narzędzi zakłada, że zależności npm mogą zawierać nowoczesną składnię i zostaną one transpilowane na odpowiednie poziomy składni podczas tworzenia na potrzeby środowiska produkcyjnego.
Oprócz dedykowanych wtyczek dla pakietu internetowego i usługi Rollup do każdego projektu można dodać nowoczesne pakiety JavaScript ze starszymi wartościami zastępczymi, korzystając z funkcji devolution. Devolution to osobne narzędzie, które przekształca dane wyjściowe z systemu kompilacji w celu wygenerowania starszych wariantów JavaScriptu. Umożliwia to łączeniem i przekształcaniom nowych założeń wyjściowych.