Publikuj, wysyłaj i instaluj nowoczesny kod JavaScript, aby przyspieszyć działanie aplikacji

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.

Porównanie: 2 transpilacje modułów źródłowych i transpilacja wygenerowanych pakietów.

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.