Tingkatkan performa dengan mengaktifkan dependensi dan output JavaScript modern.
Lebih dari 90% browser mampu menjalankan JavaScript modern, tetapi keberadaan JavaScript lama tetap menjadi sumber masalah performa terbesar di web saat ini.
JavaScript Modern
JavaScript modern tidak dikarakterisasi sebagai kode yang ditulis dalam versi spesifikasi ECMAScript tertentu, tetapi dalam sintaksis yang didukung oleh semua browser modern. Browser web modern seperti Chrome, Edge, Firefox, dan Safari membentuk lebih dari 90% pasar browser, dan browser yang berbeda yang mengandalkan mesin rendering yang mendasarinya yang sama membentuk 5% tambahan. Artinya, 95% traffic web global berasal dari browser yang mendukung fitur bahasa JavaScript yang paling banyak digunakan selama 10 tahun terakhir, termasuk:
- Class (ES2015)
- Fungsi panah (ES2015)
- Generator (ES2015)
- Cakupan blok (ES2015)
- Destrukturisasi (ES2015)
- Parameter istirahat dan penyebaran (ES2015)
- Singkatan objek (ES2015)
- Async/await (ES2017)
Fitur dalam spesifikasi bahasa versi yang lebih baru umumnya memiliki dukungan yang kurang konsisten di seluruh browser modern. Misalnya, banyak fitur ES2020 dan ES2021 hanya didukung di 70% pasar browser—masih merupakan mayoritas browser, tetapi tidak cukup aman untuk mengandalkan fitur tersebut secara langsung. Artinya, walaupun JavaScript "modern" adalah target yang terus berubah, ES2017 memiliki kompatibilitas browser dengan rentang terluas sekaligus menyertakan sebagian besar fitur sintaksis modern yang umum digunakan. Dengan kata lain, ES2017 adalah yang paling mendekati sintaksis modern saat ini.
JavaScript Versi Lama
JavaScript lama adalah kode yang secara khusus menghindari penggunaan semua fitur bahasa di atas. Sebagian besar developer menulis kode sumber mereka menggunakan sintaksis modern, tetapi mengompilasi semuanya ke sintaksis lama untuk meningkatkan dukungan browser. Mengompilasi ke sintaksis lama memang meningkatkan dukungan browser, tetapi efeknya sering kali lebih kecil dari yang kita sadari. Dalam banyak kasus, dukungan meningkat dari sekitar 95% menjadi 98% sekaligus menimbulkan biaya yang signifikan:
JavaScript lama biasanya berukuran sekitar 20% lebih besar dan lebih lambat daripada kode modern yang setara. Kekurangan alat dan kesalahan konfigurasi sering kali memperlebar kesenjangan ini lebih jauh.
Library yang diinstal mencakup hingga 90% kode JavaScript produksi biasa. Kode library menimbulkan overhead JavaScript lama yang lebih tinggi karena duplikasi polyfill dan 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 minimal
12.8, yang mendukung ES2019. Artinya, modul apa pun yang dirujuk 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 memublikasikan paket dengan kode modern dan menyerahkannya kepada konsumen untuk menangani transpilasinya saat mereka menggunakannya sebagai dependensi, hanya gunakan 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 penggantian ES5 + CommonJS untuk browser lama.
{
"name": "foo",
"exports": "./modern.js",
"main": "./legacy.cjs"
}
Modern dengan pengoptimalan fallback lama dan bundler ESM
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 bundler, seperti webpack dan Rollup, mengandalkan kolom ini untuk memanfaatkan
fitur modul dan mengaktifkan
tree shaking.
Ini masih merupakan paket lama yang tidak berisi kode modern selain
sintaksis import
/export
, jadi gunakan pendekatan ini untuk mengirimkan kode modern dengan
fallback lama yang masih dioptimalkan untuk bundling.
JavaScript modern dalam aplikasi
Dependensi pihak ketiga membentuk sebagian besar kode JavaScript produksi yang umum di aplikasi web. Meskipun dependensi npm secara historis telah dipublikasikan sebagai sintaksis ES5 lama, hal ini tidak lagi merupakan asumsi yang aman dan berisiko menyebabkan update dependensi merusak dukungan browser di aplikasi Anda.
Dengan semakin banyaknya paket npm yang beralih ke JavaScript modern, penting untuk memastikan bahwa alat build disiapkan untuk menanganinya. Ada kemungkinan besar bahwa beberapa paket npm yang Anda andalkan sudah menggunakan fitur bahasa modern. Ada sejumlah opsi yang tersedia untuk menggunakan kode modern dari npm tanpa merusak aplikasi Anda di browser lama, tetapi ide umumnya adalah membuat sistem build mentranspile dependensi ke target sintaksis yang sama dengan kode sumber Anda.
webpack
Mulai webpack 5, Anda kini dapat mengonfigurasi sintaksis yang akan digunakan webpack saat membuat kode untuk paket dan modul. Tindakan ini tidak mentranspile kode atau dependensi Anda, tetapi hanya memengaruhi kode "lem" 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 yang
menghapus fungsi wrapper yang tidak diperlukan saat menargetkan lingkungan Modul
ES modern. Tindakan ini juga mengonfigurasi webpack untuk memuat paket pemisahan kode menggunakan
<script type="module">
.
module.exports = {
target: ['web', 'es2017'],
output: {
module: true,
},
experiments: {
outputModule: true,
},
};
Ada sejumlah plugin webpack yang tersedia yang memungkinkan untuk mengompilasi dan mengirimkan JavaScript modern sekaligus tetap mendukung browser lama, seperti Optimize Plugin dan BabelEsmPlugin.
Plugin Optimize
Plugin Optimize adalah plugin webpack yang mengubah kode paket akhir dari JavaScript modern menjadi JavaScript lama, bukan setiap file sumber. Ini adalah penyiapan mandiri yang memungkinkan konfigurasi webpack Anda mengasumsikan bahwa semuanya adalah JavaScript modern tanpa cabang khusus untuk beberapa output atau sintaksis.
Karena Plugin Pengoptimalan beroperasi pada paket, bukan modul individual, plugin ini memproses kode aplikasi dan dependensi Anda secara merata. Hal ini membuat dependensi JavaScript modern dari npm aman digunakan, karena kodenya akan dipaketkan dan ditranspile ke sintaksis yang benar. Solusi ini juga dapat lebih cepat daripada solusi tradisional yang melibatkan dua langkah kompilasi, sambil tetap menghasilkan paket terpisah untuk browser modern dan lama. Kedua kumpulan paket dirancang untuk dimuat menggunakan pola module/nomodule.
// webpack.config.js
const OptimizePlugin = require('optimize-plugin');
module.exports = {
// ...
plugins: [new OptimizePlugin()],
};
Optimize Plugin
dapat lebih cepat dan lebih efisien daripada konfigurasi webpack
kustom, yang biasanya memaketkan kode modern dan lama secara terpisah. Alat ini
juga menangani pengoperasian Babel untuk Anda, dan meminimalkan
paket menggunakan Terser dengan setelan optimal terpisah untuk
output modern dan lama. Terakhir, polyfill yang diperlukan oleh paket lama
yang dihasilkan diekstrak ke dalam skrip khusus sehingga tidak pernah
diduplikasi atau tidak perlu dimuat di browser yang lebih baru.
BabelEsmPlugin
BabelEsmPlugin adalah plugin webpack yang berfungsi bersama dengan @babel/preset-env untuk membuat versi modern dari paket yang ada untuk mengirimkan kode yang kurang ditranspil ke browser modern. Ini adalah solusi siap pakai yang paling populer untuk modul/nomodule, yang digunakan oleh Next.js dan 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
mendukung berbagai konfigurasi webpack, karena
menjalankan dua build aplikasi yang sebagian besar terpisah. Mengompilasi dua kali dapat memerlukan
sedikit waktu tambahan untuk aplikasi besar, tetapi teknik ini memungkinkan
BabelEsmPlugin
berintegrasi dengan lancar ke dalam konfigurasi webpack yang ada
dan menjadikannya salah satu opsi paling praktis yang tersedia.
Mengonfigurasi babel-loader untuk mentranspile node_modules
Jika Anda menggunakan babel-loader
tanpa salah satu dari dua plugin sebelumnya,
ada langkah penting yang diperlukan untuk menggunakan modul npm JavaScript
modern. Menentukan dua konfigurasi babel-loader
terpisah memungkinkan
kompilasi fitur bahasa modern yang ditemukan di node_modules
ke
ES2017 secara otomatis, sambil tetap mentranspilasi kode pihak pertama Anda sendiri dengan plugin
dan preset Babel yang ditentukan dalam konfigurasi project Anda. Tindakan ini tidak
membuat paket modern dan lama untuk penyiapan modul/nomodule, tetapi
memungkinkan penginstalan dan penggunaan paket npm yang berisi JavaScript modern
tanpa merusak browser lama.
webpack-plugin-modern-npm
menggunakan teknik ini untuk mengompilasi dependensi npm yang memiliki kolom "exports"
di package.json
-nya, karena kolom ini 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 dalam konfigurasi webpack
dengan memeriksa kolom "exports"
di package.json
modul saat di-resolve. Dengan menghapus penyimpanan dalam cache untuk mempersingkat, implementasi kustom 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 sintaksis modern didukung oleh
minifier. Terser
dan uglify-es
memiliki opsi untuk menentukan {ecma: 2017}
guna mempertahankan dan dalam beberapa kasus
menghasilkan sintaksis ES2017 selama kompresi dan pemformatan.
Penggabungan
Rollup memiliki dukungan bawaan untuk membuat beberapa kumpulan paket sebagai bagian dari satu build, dan menghasilkan kode modern secara default. Akibatnya, Rollup dapat dikonfigurasi untuk membuat paket modern dan lama dengan plugin resmi yang mungkin sudah Anda gunakan.
@rollup/plugin-babel
Jika Anda menggunakan Rollup,
metode getBabelOutputPlugin()
(disediakan oleh
plugin Babel resmi Rollup)
akan mengubah kode dalam paket yang dihasilkan, bukan modul sumber individual.
Rollup memiliki dukungan bawaan untuk membuat beberapa kumpulan paket sebagai bagian dari
satu build, masing-masing dengan pluginnya sendiri. Anda dapat menggunakannya untuk menghasilkan
paket yang berbeda untuk modern dan lama dengan meneruskan setiap paket melalui
konfigurasi plugin output Babel yang berbeda:
// 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 umumnya berarti setiap project harus memperbarui konfigurasinya untuk mengaktifkan sintaksis JavaScript modern dalam dependensi. Ada juga alat build tingkat tinggi yang lebih menyukai konvensi dan default daripada konfigurasi, seperti Parcel, Snowpack, Vite, dan WMR. Sebagian besar alat ini menganggap dependensi npm dapat berisi sintaksis modern, dan akan mentranspilenya ke tingkat sintaksis yang sesuai saat mem-build untuk produksi.
Selain plugin khusus untuk webpack dan Rollup, paket JavaScript modern dengan penggantian lama dapat ditambahkan ke project apa pun menggunakan devolusi. Devolution adalah alat mandiri yang mengubah output dari sistem build untuk menghasilkan varian JavaScript lama, yang memungkinkan penggabungan dan transformasi untuk mengasumsikan target output modern.