Seorang peserta lokakarya menanyai Siti: aplikasi toko menampilkan versi 2.1.0, padahal ia sudah menjalankan APP_VERSION=9.9.9 di terminal dan angkanya tidak berubah. Kode aplikasinya benar — yang salah asumsinya adalah waktu. Ada dua momen yang berbeda di siklus hidup aplikasi JavaScript, dan membedakan keduanya menjelaskan perilaku yang sering membingungkan pemula: kenapa ada nilai yang “terkunci”, kenapa galat bisa muncul jauh setelah kode ditulis, dan kenapa berkas yang diunduh pengguna bukan berkas yang kita tulis.
Chapter penutup section ini memisahkan kedua momen tersebut: apa yang terjadi saat build (alat-alat dari section ini bekerja di mesin pengembang) dan apa yang terjadi saat runtime (browser atau Node mengeksekusi hasilnya), lalu merangkainya menjadi pipeline sederhana.
Membekukan Nilai Saat Build #
Berkas kecil berikut membaca versi aplikasi dari environment — pola yang persis kita pelajari di chapter Environment Configuration:
// versi.js — dari mana angka versi ini datangnya?
console.log(`versi aplikasi : ${process.env.APP_VERSION}`);
console.log(`mode saat ini : ${process.env.NODE_ENV ?? "development"}`);Dijalankan langsung oleh Node, nilainya mengikuti lingkungan saat itu:
node versi.jsOutput yang Dihasilkan:
versi aplikasi : undefined
mode saat ini : developmentKini kita membangun berkas yang sama dengan esbuild, sambil menyuntikkan nilai APP_VERSION lewat opsi --define:
npx --yes esbuild@latest versi.js --bundle --define:process.env.APP_VERSION='"2.1.0"' --outfile=dist/versi.jsOutput yang Dihasilkan:
dist/versi.js 499b
⚡ Done in 2msIsi dist/versi.js membuktikan sesuatu yang penting — pernyataan cetaknya sudah tidak berisi process.env lagi:
// versi.js
var require_versi = __commonJS({
"versi.js"() {
console.log(`versi aplikasi : ${"2.1.0"}`);
console.log(`mode saat ini : ${"development"}`);
}
});esbuild mengganti process.env.APP_VERSION dengan literal string "2.1.0" saat build. Konsekuensinya terlihat saat hasil build dijalankan — baik tanpa variabel maupun dengan variabel yang berbeda:
node dist/versi.js
APP_VERSION=9.9.9 NODE_ENV=production node dist/versi.jsOutput yang Dihasilkan:
versi aplikasi : 2.1.0
mode saat ini : development
versi aplikasi : 2.1.0
mode saat ini : developmentEmpat baris keluaran itu adalah inti chapter ini. Nilai 9.9.9 yang diatur di baris kedua diabaikan sepenuhnya, karena pada saat Node membaca berkas tersebut, teks process.env.APP_VERSION sudah tidak ada — yang ada adalah string "2.1.0" hasil build. Inilah yang kita maksud membekukan nilai: keputusan yang dibuat saat build ikut tercetak ke berkas dan tidak lagi bisa diubah dari lingkungan.
Dua Momen yang Berbeda #
Dari eksperimen di atas, kontrasnya bisa dirangkum. Momen build terjadi di mesin pengembang atau CI, dikerjakan alat seperti esbuild, dan produknya berkas statis di dist/. Momen runtime terjadi kemudian, dikerjakan browser atau Node, atas berkas hasil build tersebut.
| Aspek | Saat build | Saat runtime |
|---|---|---|
| Yang mengeksekusi | Bundler, linter, formatter, test runner | Browser atau Node.js |
| Berkas yang dibaca | Kode sumber (src/, berkas .mjs) | Hasil build (dist/) |
process.env | Nilai di mesin build (atau hasil --define) | Tidak ada di browser; nilai Node saat berjalan di server |
| Galat sintaks | Terdeteksi seketika oleh linter dan bundler | Tak pernah sampai — berkas hasil build sudah sah |
| Tree-shaking dan minify | Dilakukan di sini | Tidak ada lagi; kode sudah final |
| Perubahan kode | Perlu build ulang | Tidak mengubah apa pun tanpa build ulang |
Satu catatan penting dari tabel itu: process.env adalah milik Node. Di browser tidak ada object process — maka pola --define tadi justru menjadi cara baku menyuntikkan konfigurasi ke kode klien: bundler yang menggantikan teksnya saat build, dan sisa nilai apa pun yang masih merujuk process.env akan mengalami ReferenceError saat runtime.
Pipeline Sederhana dari Sumber ke Pengguna #
Semua topik section ini kini bisa dirangkai menjadi satu alur kerja yang berjalan satu kali setiap kali kode berubah:
Alur itu lahir dari field scripts pada package.json. Script build di manifes contoh contoh-package.json sudah berisi bentuk akhirnya:
{
"scripts": {
"dev": "node --watch src/index.js",
"test": "node --test",
"build": "esbuild src/index.js --bundle --minify --sourcemap --outfile=dist/bundle.js"
}
}Urutan kerjanya: npm run lint (setelah script lint ditambahkan) menahan pipeline bila ada pelanggaran — kode keluar 1 dari ESLint menghentikan rantai; npm test memastikan perilaku tetap benar; npm run build menghasilkan dist/ yang siap diunggah. Di lingkungan CI, npm ci memasang persis dependensi terkunci sebelum rantai itu dijalankan. Saat pengembangan sehari-hari, npm run dev lewat node --watch memutasi ulang proses setiap berkas sumber berubah — tanpa menunggu build penuh.
Dengan ini, siklus section Tooling dan Code Quality tertutup: ESLint dan Prettier menjaga kode sumber, npm mengelola alatnya, environment memisahkan nilai per lingkungan, dan bundler mengubah semuanya menjadi berkas hasil build — yang selanjutnya dieksekusi pengguna di runtime. Section berikutnya, Performance, membahas apa yang terjadi setelah aplikasi berjalan: mengukur dan memperbaiki kecepatannya.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
tipe-galat.js
Unduh berkas// tipe-galat.js — sah secara sintaks, tetapi galat saat berjalan
const data = null;
data.panggil();
versi.js
Unduh berkas// versi.js — dari mana angka versi ini datangnya?
console.log(`versi aplikasi : ${process.env.APP_VERSION}`);
console.log(`mode saat ini : ${process.env.NODE_ENV ?? "development"}`);