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:

js versi.js
// 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:

bash
node versi.js

Output yang Dihasilkan:

text
versi aplikasi : undefined
mode saat ini  : development

Kini kita membangun berkas yang sama dengan esbuild, sambil menyuntikkan nilai APP_VERSION lewat opsi --define:

bash
npx --yes esbuild@latest versi.js --bundle --define:process.env.APP_VERSION='"2.1.0"' --outfile=dist/versi.js

Output yang Dihasilkan:

text
  dist/versi.js  499b 

⚡ Done in 2ms

Isi dist/versi.js membuktikan sesuatu yang penting — pernyataan cetaknya sudah tidak berisi process.env lagi:

js dist/versi.js (hasil build, bagian inti)
  // 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:

bash
node dist/versi.js
APP_VERSION=9.9.9 NODE_ENV=production node dist/versi.js

Output yang Dihasilkan:

text
versi aplikasi : 2.1.0
mode saat ini  : development
versi aplikasi : 2.1.0
mode saat ini  : development

Empat 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.

AspekSaat buildSaat runtime
Yang mengeksekusiBundler, linter, formatter, test runnerBrowser atau Node.js
Berkas yang dibacaKode sumber (src/, berkas .mjs)Hasil build (dist/)
process.envNilai di mesin build (atau hasil --define)Tidak ada di browser; nilai Node saat berjalan di server
Galat sintaksTerdeteksi seketika oleh linter dan bundlerTak pernah sampai — berkas hasil build sudah sah
Tree-shaking dan minifyDilakukan di siniTidak ada lagi; kode sudah final
Perubahan kodePerlu build ulangTidak 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:

json scripts pada package.json
{
  "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
javascript
// tipe-galat.js — sah secara sintaks, tetapi galat saat berjalan
const data = null;
data.panggil();

versi.js

Unduh berkas
javascript
// 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"}`);