Modul ES

import dan export serta scope modul ES.

Daftar isi

Program yang masih berupa satu berkas panjang cepat sulit dijaga, apalagi begitu banyak fungsi menumpuk tanpa batas yang jelas. Modul ES (ES module atau ESM) adalah mekanisme bawaan JavaScript untuk memecah program menjadi beberapa berkas yang saling mengambil nilai lewat export dan import. Topik ini membangun dua modul kecil yang saling bergantung, menjalankannya dengan Node.js, lalu menautkannya di sebuah halaman web. Di akhir topik, membagi kode ke berkas tersendiri dengan scope yang tertutup sudah menjadi pola yang siap dipakai.

Modul dan scope-nya#

Sebelum fitur modul ada, seluruh kode browser ditumpuk lewat beberapa tag <script> yang berbagi satu scope global. Dua berkas yang menaruh variabel bernama sama saling menimpa tanpa peringatan, dan sumber masalahnya sulit dilacak. Modul ES menutup masalah ini dengan aturan scope baru. Setiap berkas modul memiliki scope puncaknya sendiri, sehingga variabel, fungsi, dan kelas yang dideklarasikan di dalamnya tidak bocor ke jendela browser. Satu-satunya cara sebuah nilai keluar dari modul adalah diekspor secara sengaja, dan satu-satunya cara masuk adalah diimpor oleh modul lain.

Node.js menandai berkas ESM dengan ekstensi .mjs, sedangkan berkas .js dianggap modul CommonJS yang memakai require, kecuali package.json proyeknya menuliskan "type": "module". Keduanya berbagi gagasan yang sama, yaitu pemecahan program menjadi unit berkas, tetapi sintaksnya berbeda dan tidak saling menggantikan begitu saja. Sepanjang topik ini sintaks ESM yang dipakai karena inilah bentuk yang dikenali browser.

Mengekspor nilai dengan export#

Kata kunci export menandai nilai yang boleh dipakai modul lain. Bentuk pertama adalah ekspor bernama (named export) yang dapat berjumlah banyak dalam satu berkas, dan bentuk kedua adalah ekspor default (default export) yang hanya boleh satu, yaitu nilai utama modul tersebut. Berkas pertama berisi fungsi hitung dengan dua ekspor bernama dan satu ekspor default.

js matematika.mjs
// Fungsi hitung yang diekspor agar dapat dipakai modul lain.

export function tambah(a, b) {
  return a + b;
}

export function kali(a, b) {
  return a * b;
}

// Satu ekspor default, nilai utama dari modul ini.
export default function ringkas(a, b) {
  return { jumlah: tambah(a, b), perkalian: kali(a, b) };
}

Parameter a dan b serta seluruh isi fungsi tetap berada di scope matematika.mjs dan tidak dapat disentuh dari luar. Yang terlihat dari luar hanyalah tambah, kali, dan ringkas. Pemisahan ini sering disebut enkapsulasi, yaitu menyembunyikan detail internal dan hanya menampilkan antarmuka, dan di sini terjadi tanpa satu pun sintaks tambahan.

Mengimpor nilai dengan import#

Kata kunci import mengambil nilai dari modul lain. Ekspor default diambil tanpa kurung kurawal dan boleh dinamai bebas, sedangkan ekspor bernama diambil persis dengan nama aslinya di dalam kurung kurawal. Bila nama yang diimpor bentrok dengan kode lokal, alias as menggantinya. Berkas kedua memakai ketiga bentuk itu sekaligus, ringkas adalah nama bebas untuk ekspor default, sedangkan perkalian adalah alias dari ekspor bernama kali.

js utama.mjs
// tidak-dijalankan

// Modul utama mengimpor nilai dari modul matematika.
import ringkas, { tambah, kali as perkalian } from "./matematika.mjs";

const hasil = ringkas(12, 4);
console.log("ringkas(12, 4) =", hasil.jumlah, "dan", hasil.perkalian);
console.log("tambah(10, 5) =", tambah(10, 5));
console.log("kali(10, 5) =", perkalian(10, 5));

Hubungan kedua berkas itu digambarkan pada diagram berikut, modul utama mengirim perintah import, sedangkan matematika.mjs membalas dengan nilai hasil ekspornya.

utama.mjsmodul yang mengimpormatematika.mjsmodul yang mengeksporimportnilai hasil ekspor
Alur import antar modul, utama.mjs mengimpor dan matematika.mjs mengekspor

Jalur "./matematika.mjs" dibaca relatif terhadap berkas yang mengimpor. Awalan ./ wajib ditulis pada ESM, lengkap dengan ekstensinya, sehingga bentuknya "./matematika.mjs" dan bukan "matematika" sebagaimana kebiasaan CommonJS. Aturan ini membuat maksud kode langsung terlihat karena berkas lokal terbedakan dari nama package tanpa perlu menebak.

Menjalankan modul di Node.js#

Node.js menjalankan berkas .mjs sebagai modul ESM tanpa langkah tambahan. Perintahnya sama seperti menjalankan skrip biasa, yaitu node diikuti nama berkasnya, dan berkas yang diimpor cukup berada di folder yang sama.

bash
# jalankan modul utama dari folder tempat kedua berkas berada
node utama.mjs
text Keluaran
ringkas(12, 4) = 16 dan 48
tambah(10, 5) = 15
kali(10, 5) = 50

Keluaran itu baru muncul setelah kedua modul selesai dimuat. Saat utama.mjs mulai berjalan, JavaScript menautkan dulu seluruh modul yang diimpornya, sehingga matematika.mjs selesai dimuat sebelum baris pertama modul utama dieksekusi. Penautan semacam ini bersifat statis karena ditentukan dari tulisan kode, bukan dari jalannya program. Sifat statis itulah yang membuat linter, perkakas pemeriksa kode tanpa menjalankannya, dapat menelusuri rantai import dengan mudah.

Modul ES di browser#

Browser modern mengenali modul ES lewat atribut type="module" pada tag script.

html Menautkan modul utama dari halaman web
<script type="module" src="./utama.mjs"></script>

Browser kemudian mengunduh utama.mjs, membaca daftar importnya, lalu mengunduh matematika.mjs dari server. Eksekusinya ditunda sampai dokumen selesai dimuat, mirip perilaku atribut defer, sehingga posisi tag script tidak lagi krusial. Seluruh tumpukan tag <script> lama yang urutannya saling bergantung kini cukup diganti dengan satu tag modul.

Kesalahan umum#

Tiga kekeliruan berikut muncul hampir setiap kali modul ES baru dipraktikkan, dan semuanya mudah dikenali dari pesan errornya.

  • Lupa awalan ./ pada jalur relatif. Menulis import ... from "matematika.mjs" membuat JavaScript menganggapnya nama package, bukan berkas, sehingga muncul error modul tidak ditemukan. Tulis "./matematika.mjs" untuk berkas pada folder yang sama.
  • Menaruh lebih dari satu export default dalam satu berkas. Aturannya hanya mengizinkan satu, dan pelanggarannya langsung berupa error sintaks. Nilai lain sebaiknya menjadi ekspor bernama.
  • Mengimpor nama yang tidak diekspor. Baris import { kaliDua } from "./matematika.mjs" berakhir dengan error penautan, dan pesan errornya menyebutkan nama ekspor yang hilang. Periksa ejaan nama di kedua berkas, termasuk huruf besar-kecilnya.

Ringkasan#

  • Setiap berkas modul ES memiliki scope puncaknya sendiri dan berjalan dalam mode ketat, sehingga tidak ada kebocoran nama antarberkas.
  • Ekspor bernama dapat berjumlah banyak, sedangkan ekspor default hanya satu per berkas dan diimpor dengan nama bebas.
  • Alias as mengganti nama saat mengimpor, dan jalur relatif wajib diawali ./ lengkap dengan ekstensinya.
  • Node.js menjalankan berkas .mjs langsung, sedangkan browser menuntut atribut type="module" dan halaman disajikan lewat server lokal.

Referensi#