Soal 1Apa yang terjadi kalau default export Cart kamu muat dengan import { Cart } from "./cart.js"?
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'
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'
'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 import | Yang didapat | Nama di file peng-import |
|---|---|---|
| import { withTax } from … | Named export | Sama dengan nama export |
| import Cart from … | Default export | Nama apa pun |
| import Cart, { MAX_ITEMS } from … | Default dan named | Cart 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
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
- File berakhiran
.jsdimuat sebagai ESM
- Bisa memuatnya dengan
import stock from "./stock.cjs" - Menulis
requiremelemparReferenceError
stock.cjs— mengekspos value lewatmodule.exportsreport.cjs— bisa memuat denganrequire
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.
| Aspek | ESM | CommonJS |
|---|---|---|
| Memuat | import { reserve } from "./stock.js" | const { reserve } = require("./stock.js") |
| Mengekspos value | export function reserve() { ... } | module.exports = { reserve: reserve } |
| Extension | Wajib 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) |
| Browser | Dimuat dengan type="module" | Tidak didukung langsung |
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
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"?