Sepanjang Section ini kita berperan sebagai pemakai package: memasangnya, membaca versinya, memanggilnya lewat pintasan npm. Sekarang kita membalik posisi menjadi penyedia — membungkus kode sendiri menjadi package yang rapi, terlindungi, dan siap dibagikan ke registry npm. Kabar baiknya, hampir semua bahan bangunannya sudah kita kuasai: package.json sebagai identitas, field main sebagai pintu masuk, dan semantic versioning sebagai janji perubahan. Chapter ini merangkai semuanya menjadi satu package utuh bernama paket-ucapan, lengkap dengan field exports yang mengatur batas akses dan field files yang mengatur isi terbitan.
Struktur Package #
Package pada dasarnya adalah folder berisi package.json ditambah berkas-berkas kode. Bundle chapter ini memuat satu folder package lengkap yang akan kita bedah sepanjang Chapter:
paket-ucapan/
├── package.json
├── lib/
│ ├── utama.mjs
│ ├── format.mjs
│ └── rahasia.mjs
├── coba-paket.mjs
└── coba-terlarang.mjsKode yang hendak dibagikan ditempatkan di lib/, terbagi tiga berkas dengan peran berbeda: utama.mjs sebagai wajah utama package, format.mjs sebagai utilitas pendukung yang tetap dibuka untuk umum, dan rahasia.mjs sebagai helper internal yang tidak boleh diakses dari luar. Dua berkas coba-*.mjs adalah berkas percobaan yang berperan sebagai pemakai — perannya seperti berkas uji, bukan bagian dari produk. Berkas identitasnya sendiri layak dibaca ulang:
{
"name": "paket-ucapan",
"version": "1.0.0",
"description": "Kumpulan function ucapan untuk contoh Course Node.js",
"type": "module",
"main": "lib/utama.mjs",
"exports": {
".": "./lib/utama.mjs",
"./format": "./lib/format.mjs"
},
"files": ["lib"],
"license": "MIT"
}Field name, version, description, dan type telah kita kenal tuntas pada Chapter package.json. Dua field berikutnya — exports dan files — adalah materi baru yang membedakan package sekadar berjalan dari package yang enak dipakai orang lain, dan keduanya masing-masing mendapat section sendiri.
Field exports dan Subpath #
Field exports memetakan specifier yang boleh dipakai pemakai menuju berkas di dalam package. Kunci "." adalah pintu masuk utama — specifier "paket-ucapan" — sedangkan kunci "./format" membuka subpath "paket-ucapan/format". Isi kedua berkas tersebut:
export function sapa(nama) {
return `Halo, ${nama}!`;
}
export { kapitalisasi } from "./format.mjs";export function kapitalisasi(teks) {
return teks.charAt(0).toUpperCase() + teks.slice(1).toLowerCase();
}utama.mjs mengekspor sapa sekaligus meneruskan kapitalisasi dari format.mjs — pola export ... from dari Chapter ES Modules — sehingga pemakai yang hanya membutuhkan keduanya cukup mengimpor dari pintu utama. Sekarang saatnya berperan sebagai pemakai:
import { sapa, kapitalisasi } from "paket-ucapan";
import { kapitalisasi as kapital } from "paket-ucapan/format";
console.log(sapa("budi santoso"));
console.log(kapitalisasi("jalan merdeka"));
console.log("dua jalur menunjuk function yang sama?", kapital("kota bandung") === kapitalisasi("kota bandung"));Output yang Dihasilkan:
Halo, budi santoso!
Jalan merdeka
dua jalur menunjuk function yang sama? trueBaris terakhir output membandingkan hasil dua jalur impor — kapitalisasi dari pintu utama dan kapital dari subpath — dan keduanya menunjuk function yang sama karena export ... from hanya meneruskan referensi. Perlu dicatat pula hubungan exports dengan main: field main tetap kita isi sebagai pintu masuk versi lama untuk alat yang belum membaca exports, tetapi begitu exports ada, exports yang menang — resolusi specifier hanya mengikuti peta yang exports tetapkan.
Merapikan Batas Akses #
Kekuatan sesungguhnya exports bukan pada apa yang dibuka, melainkan pada apa yang tertutup. Berkas lib/rahasia.mjs memang ada di dalam package, tetapi tidak tercantum di exports:
const kunciApi = "YOUR_API_TOKEN";
function tandaTangan() {
return `ditandatangani dengan ${kunciApi.slice(0, 7)}...`;
}
export { tandaTangan };Pemakai yang nekat mengimpor subpath tersebut langsung ditahan Node.js sebelum berkasnya tersentuh:
try {
await import("paket-ucapan/rahasia");
console.log("rahasia berhasil dimuat");
} catch (error) {
console.log(error.code);
console.log(error.message.replaceAll(process.cwd() + "/", ""));
}Output yang Dihasilkan:
ERR_PACKAGE_PATH_NOT_EXPORTED
Package subpath './rahasia' is not defined by "exports" in paket-ucapan/package.json imported from paket-ucapan/coba-terlarang.mjsKode error ERR_PACKAGE_PATH_NOT_EXPORTED menyatakan dengan tegas: subpath ./rahasia bukan bagian dari kontrak publik package. Tanpa field exports, semua berkas dalam package dapat diimpor siapa saja lewat path relatifnya — termasuk helper internal dan berkas yang menyimpan rahasia. Dengan exports, permukaan publik package menjadi kontrak yang eksplisit: kita bebas menata ulang isi lib/ tanpa merusak kode pemakai, selama peta di exports tetap stabil.
Field files dan Isi Terbitan #
Saat dipublikasikan, package dikemas menjadi satu berkas tarball berekstensi .tgz. Field files menentukan folder dan berkas mana yang ikut dikemas — pada contoh kita, hanya "lib". Perintah npm pack --dry-run mengemas secara simulasi tanpa menulis berkas apa pun, sehingga aman untuk melihat pratinjau isi terbitan:
cd paket-ucapan && npm pack --dry-runOutput yang Dihasilkan:
paket-ucapan-1.0.0.tgz
npm notice
npm notice 📦 [email protected]
npm notice Tarball Contents
npm notice 108B lib/format.mjs
npm notice 149B lib/rahasia.mjs
npm notice 104B lib/utama.mjs
npm notice 294B package.json
npm notice Tarball Details
npm notice name: paket-ucapan
npm notice version: 1.0.0
npm notice filename: paket-ucapan-1.0.0.tgz
npm notice package size: 537 B
npm notice unpacked size: 655 B
npm notice shasum: 27e82843b454a0a65bba669ad15dfb5832f76bc9
npm notice integrity: sha512-xSgpcmCGGx1KA[...]hSKMBkz5YXk5A==
npm notice total files: 4
npm noticeDaftar Tarball Contents memperlihatkan empat berkas yang akan terbit: tiga berkas di lib/ dan package.json. Kedua berkas coba-*.mjs tidak ikut karena tersimpan di luar folder lib — persis kerja field files. Sebaliknya, beberapa berkas selalu ikut tanpa perlu dicatat: package.json, README.md, LICENSE, dan CHANGELOG.md bila ada.
Perhatikan pula bahwa lib/rahasia.mjs tetap ikut terbit di dalam tarball — field files mengatur isi kemasan, sedangkan field exports mengatur akses. Kombinasi keduanya adalah pola yang sehat: helper internal boleh ikut terkirim karena dibutuhkan kode utama, tetapi tidak dapat diimpor pemakai.
Menuju Registry npm #
Menerbitkan package ke registry membutuhkan akun npm dan koneksi internet, sehingga perintahnya kita tampilkan sebagai ilustrasi. Alur lengkapnya berjalan dari akun, verifikasi, hingga penerbitan:
npm login
npm whoami
npm publishPerintah npm login membuka peramban untuk masuk ke akun npm, npm whoami mencetak nama akun yang aktif sebagai verifikasi, dan npm publish — dijalankan dari folder package — mengunggah tarball ke registry. Begitu terbit, siapa pun di dunia dapat memasang package kita dengan npm install paket-ucapan. Penerbitan menimpa versi yang sama tidak diizinkan: naikkan version mengikuti aturan SemVer lebih dulu, atau manfaatkan perintah npm version patch yang menaikkan angka sekaligus menandainya di Git.
Kebiasaan terakhir yang layak diadopsi sebelum menerbitkan adalah memasang pengaman agar kode yang belum teruji tidak sempat terkirim. Field scripts menyediakan hook khusus untuk hal ini:
{
"scripts": {
"test": "node --test",
"prepublishOnly": "npm test"
}
}Hook prepublishOnly berjalan otomatis tepat sebelum npm publish mengeksekusi — persis pola pre dan post dari Chapter npm Scripts. Bila pengujian gagal, penerbitan berhenti sebelum apa pun terunggah, dan versi yang buruk tidak pernah sampai ke tangan pemakai.
Kita telah menyelesaikan satu putaran penuh ekosistem: memecah kode menjadi module dengan CommonJS dan ESM, menelusuri algoritma resolusi, mengelola proyek lewat package.json dan npm, membaca janji versi SemVer, mengotomatiskan perintah lewat scripts, hingga membungkus karya sendiri menjadi package. Semua itu berdiri di atas satu fondasi yang belum kita inventarisasi: puluhan core module yang dibawa Node.js sejak awal — path, url, events, dan kawan-kawannya. Section Core Modules mengajak kita mengenalnya satu per satu.