Publikasikan, kirimkan, dan instal JavaScript modern untuk aplikasi yang lebih cepat

Tingkatkan performa dengan mengaktifkan dependensi dan output JavaScript modern.

Lebih dari 90% browser mampu menjalankan JavaScript modern, tetapi prevalensi JavaScript lama tetap menjadi sumber masalah performa yang besar di web saat ini.

JavaScript Modern

JavaScript modern tidak dicirikan sebagai kode yang ditulis dalam ECMAScript tertentu versi spesifikasi, melainkan dalam sintaksis yang didukung oleh semua browser. Browser web modern seperti Chrome, Edge, Firefox, dan Safari terdiri dari lebih dari 90% pasar browser, dan browser berbeda yang bergantung pada mesin render dasar yang sama membentuk tambahan sebesar 5%. Artinya, 95% lalu lintas web global berasal dari browser yang mendukung fitur bahasa JavaScript yang paling banyak digunakan dalam sepuluh tahun, termasuk:

  • Kelas (ES2015)
  • Fungsi panah (ES2015)
  • Generator (ES2015)
  • Cakupan blok (ES2015)
  • Destrukturisasi (ES2015)
  • Parameter istirahat dan sebar (ES2015)
  • Singkatan objek (ES2015)
  • Asinkron/await (ES2017)

Fitur di versi spesifikasi bahasa yang lebih baru umumnya memiliki lebih sedikit dukungan yang konsisten di seluruh browser modern. Misalnya, beberapa ES2020 dan ES2021 hanya didukung di 70% pasar {i>browser<i}—masih sebagian besar {i>browser<i} web, tetapi tidak cukup bagi keamanan untuk mengandalkan fitur tersebut secara langsung. Ini berarti bahwa meskipun “modern” JavaScript adalah target yang bergerak, ES2017 memiliki beragam kompatibilitas browser sekaligus menyertakan sebagian besar fitur sintaksis modern yang umum digunakan. Dengan kata lain, ES2017 adalah yang paling dekat dengan sintaksis modern saat ini.

JavaScript Lama

JavaScript lama adalah kode yang secara khusus menghindari penggunaan semua bahasa di atas baru. Sebagian besar developer menulis kode sumber menggunakan sintaksis modern, tetapi mengompilasi semuanya ke {i>syntax<i} lama untuk meningkatkan dukungan browser. Mengompilasi ke sintaksis lama memang meningkatkan dukungan browser, namun efeknya sering lebih kecil dari yang kita sadari. Dalam banyak kasus, dukungan meningkat dari sekitar 95% hingga 98% sekaligus menimbulkan biaya yang signifikan:

  • JavaScript lama biasanya berukuran sekitar 20% lebih besar dan lebih lambat dari kode modern yang setara. Kekurangan alat dan kesalahan konfigurasi sering kali semakin memperluas kesenjangan ini.

  • Library yang diinstal menyumbang hingga 90% dari produksi standar kode JavaScript. Kode library menimbulkan JavaScript lama yang lebih tinggi overhead karena polyfill dan duplikasi helper yang dapat dihindari dengan memublikasikan kode modern.

JavaScript modern di npm

Baru-baru ini, Node.js telah menstandarkan kolom "exports" untuk menentukan titik entri untuk paket:

{
  "exports": "./index.js"
}

Modul yang dirujuk oleh kolom "exports" menyiratkan versi Node dari setidaknya 12.8, yang mendukung ES2019. Ini berarti bahwa setiap modul yang direferensikan menggunakan Kolom "exports" dapat ditulis dalam JavaScript modern. Konsumen paket harus menganggap modul dengan kolom "exports" berisi kode modern dan melakukan transpilasi jika diperlukan.

Khusus modern

Jika Anda ingin mempublikasikan paket dengan kode modern dan menyerahkannya pada untuk menangani transpilasi saat mereka menggunakannya sebagai dependensi—gunakan hanya Kolom "exports".

{
  "name": "foo",
  "exports": "./modern.js"
}

Modern dengan penggantian lama

Gunakan kolom "exports" bersama dengan "main" untuk memublikasikan paket Anda menggunakan kode modern tetapi juga menyertakan fallback ES5 + CommonJS untuk browser.

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs"
}

Modern dengan pengoptimalan pemaket ESM dan penggantian lama

Selain menentukan titik entri CommonJS penggantian, kolom "module" dapat digunakan untuk mengarah ke paket penggantian lama yang serupa, tetapi yang menggunakan Sintaksis modul JavaScript (import dan export).

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs",
  "module": "./module.js"
}

Banyak pemaket, seperti webpack dan Rollup, bergantung pada kolom ini untuk memanfaatkan fitur modul dan mengaktifkan tree shaking. Paket ini masih merupakan paket lama yang tidak berisi kode modern apa pun selain import/export, jadi gunakan pendekatan ini untuk mengirimkan kode modern dengan penggantian versi lama yang masih dioptimalkan untuk pemaketan.

JavaScript modern pada aplikasi

Dependensi pihak ketiga mengisi sebagian besar dari produksi standar kode JavaScript dalam aplikasi web. Meskipun dependensi npm secara historis telah diterbitkan sebagai {i>syntax<i} ES5 lama, ini bukan lagi asumsi yang aman dan memperbarui dependensi risiko yang merusak dukungan browser di aplikasi Anda.

Dengan meningkatnya jumlah paket npm yang beralih ke JavaScript modern, untuk memastikan bahwa peralatan build telah disiapkan untuk menanganinya. Terdapat kemungkinan besar beberapa paket npm yang Anda andalkan sudah menggunakan bahasa. Ada sejumlah opsi yang tersedia untuk menggunakan kode modern dari npm tanpa merusak aplikasi Anda di browser lama, tetapi idenya adalah membuat dependensi transpilasi sistem build ke sintaksis yang sama target sebagai kode sumber.

paket web

Mulai dari webpack 5, sekarang kita dapat mengkonfigurasi {i>syntax<i} apa yang akan digunakan webpack saat membuat kode untuk paket dan modul. Data ini tidak mentranspilasi kode atau dependensi, itu hanya mempengaruhi “{i>glue<i}” (perekat) kode yang dihasilkan oleh webpack. Untuk menentukan target dukungan browser, tambahkan konfigurasi browserslist ke project Anda, atau lakukan langsung di konfigurasi webpack:

module.exports = {
  target: ['web', 'es2017'],
};

Anda juga dapat mengonfigurasi webpack untuk menghasilkan paket yang dioptimalkan menghilangkan fungsi wrapper yang tidak perlu saat menargetkan Modul ES modern lingkungan fleksibel App Engine. Ini juga mengonfigurasi webpack untuk memuat paket yang dipisah kode menggunakan <script type="module">.

module.exports = {
  target: ['web', 'es2017'],
  output: {
    module: true,
  },
  experiments: {
    outputModule: true,
  },
};

Ada sejumlah {i>plugin<i} webpack yang tersedia yang memungkinkan untuk mengompilasi dan mengirimkan JavaScript modern sambil tetap mendukung {i>browser<i} lama, seperti Plugin Optimize dan BabelEsmPlugin.

Plugin Optimize

Plugin Optimize adalah paket web plugin yang mengubah kode paket akhir dari JavaScript modern menjadi JavaScript lama alih-alih setiap file sumber. Ini adalah pengaturan mandiri yang memungkinkan konfigurasi webpack Anda untuk menganggap semuanya adalah {i>JavaScript <i}modern tanpa percabangan khusus untuk beberapa {i>output<i} atau sintaks.

Karena Plugin Optimize beroperasi dalam paket, bukan modul individual, plugin ini memproses kode aplikasi dan dependensi Anda secara setara. Hal ini membuatnya aman untuk menggunakan dependensi JavaScript modern dari npm, karena kodenya akan dibundel dan ditranspilasi ke {i>syntax<i} yang benar. Bisa juga lebih cepat daripada solusi tradisional yang melibatkan dua langkah kompilasi, sambil tetap menghasilkan paket terpisah untuk browser modern dan lama. Kedua set paket tersebut yang dirancang untuk dimuat menggunakan pola modul/nomodule.

// webpack.config.js
const OptimizePlugin = require('optimize-plugin');

module.exports = {
  // ...
  plugins: [new OptimizePlugin()],
};

Optimize Plugin bisa lebih cepat dan lebih efisien daripada webpack kustom yang biasanya menggabungkan kode modern dan kode lama secara terpisah. Ini juga menangani Babel yang berjalan untuk Anda, dan meminimalkan paket menggunakan Terser dengan setelan optimal terpisah untuk output modern dan output lama. Terakhir, polyfill yang dibutuhkan oleh paket lama diekstrak ke dalam skrip khusus sehingga tidak akan pernah diduplikasi atau tidak perlu dimuat di {i>browser<i} yang lebih baru.

Perbandingan: mentranspilasi modul sumber dua kali versus mentranspilasi paket yang dihasilkan.

BabelEsmPlugin

BabelEsmPlugin adalah webpack plugin yang berfungsi bersama @babel/preset-env untuk menghasilkan versi modern dari paket yang sudah ada guna mengirimkan lebih sedikit kode yang ditranspilasi ke browser modern. Ini adalah solusi siap pakai yang paling populer untuk modul/nomodule, digunakan oleh Next.js dan CLI Preact.

// 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 mendukung berbagai konfigurasi webpack, karena menjalankan dua build yang sebagian besar terpisah dari aplikasi Anda. Mengompilasi dua kali dapat memakan waktu sedikit waktu ekstra untuk aplikasi besar, namun teknik ini memungkinkan BabelEsmPlugin untuk diintegrasikan secara lancar ke dalam konfigurasi webpack yang ada dan menjadikannya salah satu opsi paling nyaman yang tersedia.

Mengonfigurasi babel-loader untuk melakukan transpile node_modules

Jika Anda menggunakan babel-loader tanpa salah satu dari dua plugin sebelumnya, ada langkah penting yang diperlukan untuk menggunakan npm JavaScript modern modul. Dengan menentukan dua konfigurasi babel-loader terpisah, Anda dapat untuk otomatis mengompilasi fitur bahasa modern yang ditemukan di node_modules untuk ES2017, sambil tetap melakukan transpilasi kode pihak pertama Anda sendiri dengan Babel plugin dan preset yang ditentukan dalam konfigurasi project Anda. Ini tidak menghasilkan paket modern dan lama untuk penyiapan modul/nomodule, tetapi cara ini memungkinkan penginstalan dan penggunaan paket npm yang berisi JavaScript modern tanpa merusak browser yang lebih lama.

webpack-plugin-modern-npm menggunakan teknik ini untuk mengompilasi dependensi npm yang memiliki kolom "exports" di package.json, karena mungkin berisi sintaksis modern:

// webpack.config.js
const ModernNpmPlugin = require('webpack-plugin-modern-npm');

module.exports = {
  plugins: [
    // auto-transpile modern stuff found in node_modules
    new ModernNpmPlugin(),
  ],
};

Atau, Anda dapat menerapkan teknik ini secara manual di paket web konfigurasi dengan memeriksa isian "exports" di package.json saat diselesaikan. Menghilangkan penyimpanan cache agar lebih singkat, implementasinya mungkin terlihat seperti ini:

// 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'],
          },
        },
      },
    ],
  },
};

Saat menggunakan pendekatan ini, Anda harus memastikan {i>syntax<i} modern didukung oleh minifier Anda. Kedua Terser dan uglify-es memiliki opsi untuk menentukan {ecma: 2017} guna mempertahankan dan dalam beberapa kasus menghasilkan sintaks ES2017 selama kompresi dan pemformatan.

Gabungan

Penggabungan memiliki dukungan bawaan untuk membuat beberapa kumpulan paket sebagai bagian dari build tunggal, dan menghasilkan kode modern secara default. Akibatnya, Rollup dapat dikonfigurasi untuk menghasilkan paket modern dan lama dengan plugin resmi yang mungkin sudah Anda gunakan.

@rollup/plugin-babel

Jika Anda menggunakan Rollup, Metode getBabelOutputPlugin() (disediakan oleh Rollup's plugin Babel resmi) mengubah kode dalam paket yang dihasilkan, bukan dalam modul sumber individual. Penggabungan memiliki dukungan bawaan untuk membuat beberapa kumpulan paket sebagai bagian dari satu build, masing-masing dengan pluginnya sendiri. Anda dapat menggunakannya untuk membuat paket modern dan lama dengan meneruskan masing-masing paket Konfigurasi plugin output 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'],
        }),
      ],
    },
  ],
};

Alat build tambahan

Rollup dan webpack sangat dapat dikonfigurasi, yang pada umumnya berarti setiap project harus memperbarui konfigurasinya untuk mengaktifkan sintaksis JavaScript modern dalam dependensi. Ada juga alat build level lebih tinggi yang mendukung konvensi dan default daripada konfigurasinya, seperti Parcel, Snowpack, Vite, dan WMR. Sebagian besar alat ini mengasumsikan dependensi npm mungkin berisi sintaksis modern, dan akan mentranspilasinya ke level sintaksis yang sesuai saat membangun untuk produksi.

Selain plugin khusus untuk webpack dan Rollup, JavaScript modern paket dengan penggantian lama dapat ditambahkan ke project mana pun menggunakan pengabdian. Devolusi adalah alat mandiri yang mengubah output dari sistem build untuk menghasilkan Varian JavaScript, yang memungkinkan pemaketan dan transformasi untuk mengasumsikan target output.