Module — import dan export

export membuat value di sebuah file bisa dipakai file lain, dan import memuatnya. Artikel ini menjelaskan named vs default export, type="module", serta perbedaan ESM dan CommonJS.

Kalau semua kode layar pesanan terus kamu tambahkan ke satu file, perhitungan pajak, keranjang belanja, dan logika checkout akan bercampur dalam ratusan baris, dan mencari satu function pun makan waktu. Walaupun kodenya kamu pecah ke beberapa file dan kamu memasang beberapa tag <script> (tag yang memuat file JavaScript) di HTML, kode yang memakai sebuah function tidak bisa tahu function itu ada di file mana.

Artikel ini membahas export dan import, yang dipakai untuk berbagi value antarfile.

import Tidak Bisa Dipakai di Console

Console latihan menjalankan kodemu dengan menaruhnya di dalam satu function. import dan export hanya bisa ditulis di level teratas sebuah file, jadi keduanya melempar SyntaxError di sana. Contoh kode di artikel ini dimaksudkan untuk dijalankan sebagai file terpisah di Node.js atau browser; hasilnya ditulis sebagai komentar di akhir baris.

Memakai Function yang Sama di Beberapa File — Named Export

Misalkan cart-page.js (layar keranjang) dan checkout.js (layar checkout) sama-sama butuh perhitungan pajak yang sama. Kalau fungsinya kamu salin ke kedua file, perubahan tarif pajak berarti kamu harus memperbaiki dua tempat, dan kalau satu terlewat, kedua layar menampilkan nominal yang berbeda.

Variabel dan function yang dideklarasikan di sebuah module (file JavaScript yang berbagi value lewat import dan export; cara memuatnya dibahas di bagian berikutnya) tidak terlihat dari file lain. Menaruh export di depan deklarasi membuatnya bisa dipakai file lain; ini disebut named export. File yang memakainya menulis nama yang sama di dalam kurung kurawal: import { withTax } from "./price.js";.

// ---- price.js ----
const TAX_RATE = 0.1;                              // Tanpa export: hanya dipakai di dalam price.js

export function withTax(price) {                   // Dengan export: file lain bisa meng-import-nya
  return Math.floor(price * (1 + TAX_RATE));
}
export function formatYen(price) {
  return `${price} yen`;
}

// ---- cart-page.js ----
import { withTax, formatYen } from "./price.js";   // Tulis nama yang akan dipakai di dalam kurung kurawal
console.log(formatYen(withTax(4800)));             // 5280 yen

// Kalau kamu meng-import nama yang tidak di-export
// import { TAX_RATE } from "./price.js";
// SyntaxError: The requested module './price.js' does not provide an export named 'TAX_RATE'
Nama yang Bisa dan Tidak Bisa Di-import
import { withTax,formatYen }price.jsmeng-export-nyaKedua functiontermuatMenampilkan5280 yenimport{ TAX_RATE }TAX_RATE tidakdi-exportGagal denganSyntaxErrorTidak ada bariscart-page.jsyang berjalan
Dua nama di baris atas di-export, tetapi TAX_RATE di baris bawah tidak. Kamu hanya bisa meng-import nama yang di-export.

TAX_RATE dibaca oleh withTax di dalam price.js, jadi cart-page.js mendapat harga termasuk pajak tanpa perlu tahu tarif pajaknya. Kalau tarifnya berubah, kamu cukup memperbaiki satu baris TAX_RATE di price.js, dan semua file yang meng-import-nya ikut berubah.

Membuat Satu Class Bisa Dipakai File Lain — Default Export

Kalau cart.js berisi class keranjang, export utamanya hanya satu, yaitu Cart. Kamu akan sering melihat import Cart from "./cart.js"; di kode orang lain, tetapi menambahkan kurung kurawal seperti di bagian sebelumnya melempar SyntaxError.

Default export (paling banyak satu per file; file yang meng-import tidak perlu menyamakan namanya) ditulis sebagai export default class Cart { ... }. File yang meng-import menuliskannya tanpa kurung kurawal dan bebas memakai nama apa pun sebagai pengganti Cart.

// ---- cart.js ----
import { withTax } from "./price.js";
export const MAX_ITEMS = 20;                       // Named export bisa berada di file yang sama
export default class Cart {                        // Satu default export per file
  #prices = [];
  add(price) { this.#prices.push(price); }
  get total() { return withTax(this.#prices.reduce((sum, price) => sum + price, 0)); }
}

// ---- checkout.js ----
import Cart, { MAX_ITEMS } from "./cart.js";       // Default di luar kurung kurawal, named di dalamnya
import ShoppingCart from "./cart.js";              // Default bisa di-import dengan nama lain
const cart = new ShoppingCart();
cart.add(1200); cart.add(3600);
console.log(cart.total, MAX_ITEMS);                // 5280 20
console.log(Cart === ShoppingCart);                // true (keduanya class yang sama)

// Dengan kurung kurawal, yang dicari adalah named export bernama Cart
// import { Cart } from "./cart.js";
// SyntaxError: The requested module './cart.js' does not provide an export named 'Cart'
Kurung Kurawal Menentukan Export yang Dicari
import Cartfrom "./cart.js"Tanpa kurungkurawal: mencaridefaultAda exportdefault class CartKamu bisa memakainew Cart()import { Cart }from "./cart.js"Dengan kurungkurawal: mencarinamed exportTidak ada namedexport bernamaCartGagal denganSyntaxError
Baris bawah mencari named export bernama Cart dan gagal. import tanpa kurung kurawal mendapat default export, bukan named export.

'Cart' di akhir pesan error adalah nama named export yang dicari dan tidak ditemukan. Pesannya tidak menyebut bahwa ada default Cart, jadi periksa apakah kamu memakai kurung kurawal. Tabel di bawah merangkum bentuk import yang dipakai di artikel ini.

Bentuk importYang didapatNama di file peng-import
import { withTax } from …Named exportSama dengan nama export
import Cart from …Default exportNama apa pun
import Cart, { MAX_ITEMS } from …Default dan namedCart dan MAX_ITEMS (default dulu)

Membiarkan Browser Mengikuti import — type="module"

Berikutnya, HTML halaman checkout perlu memuat checkout.js. Dengan <script src="./checkout.js"></script> biasa, Chrome melaporkan SyntaxError: Cannot use import statement outside a module pada import di baris 1, dan tidak ada satu baris pun dari file itu yang berjalan.

Dengan type="module" (pengaturan <script> yang menjalankan file yang dimuat sebagai module), browser mengikuti import di file entry dan ikut mengambil file-file lainnya. Hubungan import ini disebut dependency graph (file mana meng-import file mana).

// ---- index.html: cukup cantumkan file entry, checkout.js ----
// <script type="module" src="./checkout.js"></script>

// ---- price.js ----
console.log("Menjalankan price.js");               // Ditampilkan sekali, walaupun di-import 2 file
const TAX_RATE = 0.1;
export function withTax(price) { return Math.floor(price * (1 + TAX_RATE)); }

// ---- cart.js ----
import { withTax } from "./price.js";
console.log("Menjalankan cart.js");
export const cartTotal = withTax(4800);

// ---- checkout.js ----
import { cartTotal } from "./cart.js";
import { withTax } from "./price.js";              // Memuat price.js yang sama dengan cart.js
console.log(`Total tagihan: ${cartTotal + withTax(500)} yen`);

// Urutan output di console: Menjalankan price.js → Menjalankan cart.js → Total tagihan: 5830 yen
Dependency Graph yang Berawal dari checkout.js
index.htmltype="module"checkout.js③ berjalanterakhircart.js② berjalanberikutnyaprice.js① berjalanpertama, sekali
Selain panah dari index.html, setiap panah menunjukkan arah import. Eksekusi dimulai dari price.js, dan price.js hanya berjalan sekali walaupun ditunjuk dua panah.

Sebuah module menjalankan file yang di-import-nya sebelum menjalankan dirinya sendiri. File entry, checkout.js, menunggu sampai cart.js dan price.js yang di-import-nya selesai, jadi checkout.js berjalan terakhir. Karena urutannya ditentukan oleh import, kamu tidak perlu memikirkan urutan file di HTML.

Tidak Berjalan di HTML yang Dibuka lewat file://

Kalau kamu mengklik dua kali file HTML dan membukanya sebagai file://, Chrome menganggap file di folder yang sama pun berasal dari origin (sumber tempat file diambil) yang berbeda, dan CORS (mekanisme yang membatasi pemuatan dari origin lain) memblokir pemuatan type="module". Jalankan web server, misalnya dengan extension Live Server di VS Code, lalu buka halamannya dari sana.

Membedakan Format di Node.js — ESM dan CommonJS

Di kode yang ditulis untuk Node.js, kamu akan menemukan file yang memakai const { reserve } = require("./stock"); alih-alih import. Kalau baris seperti itu kamu salin ke file yang memakai import, JavaScript melempar ReferenceError saat dijalankan.

Selain ESM (singkatan dari ES Modules, format standar yang memakai import dan export), ada juga CommonJS (format khusus Node.js yang memuat dengan require dan mengekspos value lewat module.exports). Node.js menentukan format yang dipakai sebuah file berdasarkan extension-nya dan field "type" di package.json (file konfigurasi project).

// ---- shop/package.json (file berisi pengaturan project Node.js) ----
// { "type": "module" }

// ---- shop/stock.cjs: extension-nya .cjs, jadi dimuat sebagai CommonJS ----
function reserve(count) { return `Dipesan: ${count}`; }
module.exports = { reserve: reserve, LIMIT: 3 };   // Masukkan semua value yang diekspos ke satu object

// ---- shop/report.cjs: file CommonJS memuat dengan require ----
const { reserve, LIMIT } = require("./stock.cjs");
console.log(reserve(2), LIMIT);                    // Dipesan: 2 3

// ---- shop/checkout.js: ada "type": "module", jadi dimuat sebagai ESM ----
import stock from "./stock.cjs";                   // Tanpa kurung kurawal: mendapat value module.exports
console.log(stock.reserve(1));                     // Dipesan: 1
const { LIMIT } = require("./stock.cjs");
// ReferenceError: require is not defined in ES module scope, you can use import instead
Cakupan "type": "module"
Folder shop — "type": "module" di package.json
  • File berakhiran .js dimuat sebagai ESM
checkout.js — ESM
  • Bisa memuatnya dengan import stock from "./stock.cjs"
  • Menulis require melempar ReferenceError
Extension .cjs — CommonJS
  • stock.cjs — mengekspos value lewat module.exports
  • report.cjs — bisa memuat dengan require
"type": "module" membuat file .js di folder itu menjadi ESM. File .cjs tetap CommonJS, walaupun berada di folder yang sama.

import stock tidak memakai kurung kurawal, jadi cara kerjanya sama dengan meng-import default export: yang didapat adalah object module.exports. Jangan mencampur import dan require di satu file; pilih salah satu saja. Tabel di bawah menunjukkan cara menulis setiap format dan aturan yang menentukan format sebuah file.

AspekESMCommonJS
Memuatimport { reserve } from "./stock.js"const { reserve } = require("./stock.js")
Mengekspos valueexport function reserve() { ... }module.exports = { reserve: reserve }
ExtensionWajib ditulis di Node.js maupun browser ("./stock" tidak ditemukan)Bisa dihilangkan ("./stock" menemukan stock.js)
File yang dimuat dengan format ini.mjs, dan .js di bawah "type": "module".cjs, dan .js di bawah "commonjs" atau tanpa "type" (tanpa "type", .js yang memakai import dibaca ulang sebagai ESM)
BrowserDimuat dengan type="module"Tidak didukung langsung
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Apa yang terjadi kalau default export Cart kamu muat dengan import { Cart } from "./cart.js"?

Soal 2price.js di-import dari dua tempat. Berapa kali console.log di bagian atasnya menampilkan output?

Soal 3Apa yang terjadi kalau kamu memanggil require di checkout.js dengan "type": "module"?