Lewati ke konten
RazinSaid

Arsitektur Electron: Main Process vs Renderer Process

RS
Razin Said
6 menit baca
Bagian 2 dari 12 · Seri Belajar Electron dari Nol sampai Mahir

Sebelum kamu bikin aplikasi desktop yang beneran serius, ada satu hal yang wajib kamu paham dulu: arsitektur Electron. Soalnya banyak banget pemula yang bingung, kenapa kode mereka kadang jalan mulus, kadang malah error tanpa sebab yang jelas. Dan jawabannya hampir selalu sama—kodenya salah taruh, ada di proses yang keliru. Nah, tulisan ini nyambung dari bab mengenal Electron & aplikasi pertama, jadi kalau kamu belum baca yang itu, mending mampir dulu ke sana.

Dua proses, dua tugas (analogi sederhana)

Jadi gini, setiap aplikasi Electron itu sebenarnya menjalankan dua "dunia" yang terpisah sekaligus. Yang pertama namanya main process, yang kedua namanya renderer process. Keduanya jalan bareng, tapi tugasnya beda jauh.

Coba bayangin sebuah restoran. Ada dapur di belakang, ada ruang makan di depan. Dapur itu tempatnya kompor, kulkas, sama semua bahan mentah. Tamu nggak boleh masuk dapur sembarangan, soalnya bisa bahaya. Sementara ruang makan itu tempat tamu duduk santai menikmati hidangan—rapi, nyaman, dan aman.

Di Electron, main process itu ibarat dapur: akses penuh ke sistem operasi. Renderer process-nya ya ruang makan, cuma nampilin apa yang perlu dilihat pengguna. Terus di antara keduanya, ada pelayan yang bolak-balik nganter pesanan. Nah, si pelayan ini nanti kita sebut preload script.

Pemisahan ini bukan sekadar teori ya. Ini yang nentuin di mana kamu boleh nulis kode tertentu, dan kenapa sebagian kode bakal nggak jalan kalau ditaruh asal-asalan.

Main Process — otak aplikasi

Main process ini otaknya aplikasi kamu. Dia proses pertama yang jalan begitu aplikasi dibuka, dan cuma ada satu main process per aplikasi. Proses ini jalan di lingkungan Node.js penuh, makanya dia bisa langsung ngakses sistem operasi.

Terus tugasnya apa aja? Ada tiga yang utama.

Pertama, mengelola jendela. Tiap jendela aplikasi dibikin lewat objek bernama BrowserWindow. Tanpa main process, ya nggak bakal ada jendela yang muncul sama sekali.

Kedua, mengakses sumber daya sistem. Baca file dari disk, munculin dialog "Simpan File", ngatur menu, atau nampilin notifikasi sistem—semua ini cuma bisa dilakuin dari main process.

Ketiga, mengatur siklus hidup aplikasi (lifecycle). Kapan aplikasi siap, kapan jendela ditutup, sampai kapan aplikasi harus berhenti total.

Nih contoh sederhana isi file main.js:

const { app, BrowserWindow } = require('electron')
const path = require('node:path')

function createWindow() {
  const win = new BrowserWindow({
    width: 900,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
    },
  })

  win.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()

  // Di macOS, buka jendela lagi saat ikon dock diklik
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

// Keluar saat semua jendela ditutup (kecuali di macOS)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

Coba perhatiin baris app.whenReady(). Itu sebuah promise yang nungguin sampai Electron bener-bener siap, baru deh jendelanya dibuat. Habis itu win.loadFile() ngasih tahu jendela, halaman mana yang harus ditampilin. Nah, di titik inilah dunia renderer mulai hidup.

Renderer Process — wajah aplikasi

Kalau main process otaknya, renderer process ini wajahnya. Tugasnya cuma satu: nampilin antarmuka (UI) ke pengguna. Dia jalanin halaman web biasa, lengkap dengan HTML, CSS, dan JavaScript. Jadi kalau kamu udah pernah bikin website, sebenarnya kamu udah tahu cara ngisi renderer.

Tiap BrowserWindow punya renderer process-nya sendiri. Jadi kalau aplikasimu punya tiga jendela, berarti ada tiga renderer yang jalan terpisah. Ini bagus banget buat stabilitas—nanti kita bahas sebentar lagi.

Tapi yang paling penting buat kamu pahami: renderer itu jalan secara terisolasi. Secara default dia nggak punya akses ke Node.js dan nggak bisa nyentuh sistem operasi. Jadi kalau kamu nulis kode kayak gini di file JavaScript renderer, hasilnya bakal error:

// Ini TIDAK akan jalan di renderer modern
const fs = require('fs')
fs.readFileSync('/data/penting.txt')

Banyak pemula kaget pas nyampe sini. "Lah, kenapa require-nya nggak jalan?" Tenang, itu emang disengaja kok. Ada fitur keamanan bernama context isolation yang sengaja nutup akses tadi. Di Electron modern (versi 28 ke atas), context isolation ini aktif otomatis dari sananya. Gunanya buat ngelindungin pengguna dari kode jahat yang mungkin nyelinap lewat konten web.

Terus gimana dong caranya renderer baca file kalau dia nggak boleh pakai Node.js? Nah, di sinilah si pelayan tadi mulai turun tangan.

Preload Script — jembatan yang aman antara keduanya

Preload script ini penghubung antara main process dan renderer process. Bentuknya file JavaScript khusus yang dijalanin sebelum halaman web-nya dimuat. Posisinya unik: dia jalan di lingkungan renderer, tapi punya akses terbatas ke Node.js. Dan inilah satu-satunya tempat yang aman buat buka pintu antara dua dunia tadi.

Cara kerjanya pakai alat bernama contextBridge. Lewat alat ini, kamu sendiri yang milih fungsi mana aja yang boleh diakses renderer. Jadi kamu nggak buka semua pintu—cukup bikin jendela kecil yang terkontrol.

Contoh file preload.js:

const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('appAPI', {
  bacaFile: (namaFile) => ipcRenderer.invoke('baca-file', namaFile),
  versiApp: () => process.versions.electron,
})

Habis itu, kode di renderer bisa manggil fungsi tadi lewat objek global appAPI. Bersih dan aman:

// Di file JavaScript renderer
const isi = await window.appAPI.bacaFile('catatan.txt')
console.log(isi)

Perhatiin ya, renderer sama sekali nggak nyentuh fs langsung. Dia cuma manggil appAPI.bacaFile. Permintaannya terus dikirim ke main process lewat ipcRenderer.invoke. Di sisi main, kamu tangkep permintaan itu pakai ipcMain.handle:

// Di main.js
const { ipcMain } = require('electron')
const fs = require('node:fs/promises')

ipcMain.handle('baca-file', async (event, namaFile) => {
  return await fs.readFile(namaFile, 'utf-8')
})

Pola kayak gini—kirim permintaan dari renderer, terus dikerjain di main—namanya IPC (Inter-Process Communication). IPC bakal kita kupas tuntas di bab berikutnya. Buat sekarang, cukup pahamin alurnya aja: renderer minta, preload jadi jembatan, main yang ngerjain.

Kenapa dipisah? (keamanan & stabilitas)

Mungkin kamu mikir, kenapa sih harus seribet ini? Kenapa nggak satu proses aja biar simpel? Ada dua alasan yang cukup kuat.

Alasan pertama, keamanan. Renderer kan nampilin konten web, dan konten web itu isinya bisa apa aja—mulai dari skrip iklan, library pihak ketiga, sampai data dari internet. Coba bayangin kalau renderer punya akses penuh ke Node.js dan sistem operasi: satu skrip jahat aja udah cukup buat ngehapus file pengguna atau nyuri data. Dengan misahin proses dan nutup akses langsung, risiko kayak gitu bisa kamu tekan drastis.

Makanya, jangan asal ngaktifin nodeIntegration: true terus matiin contextIsolation. Pola lama kayak gitu ngasih kekuasaan penuh ke renderer, dan itu justru pintu masuk favoritnya penyerang. Biarin aja pengaturan keamanannya tetap di nilai bawaan yang udah aman. Kalau memang perlu buka akses, pakai preload dan contextBridge secukupnya.

Alasan kedua, stabilitas. Karena tiap jendela punya renderer sendiri-sendiri, satu jendela yang crash nggak bakal ngejatuhin seluruh aplikasi. Bayangin kamu lagi buka editor dengan tiga dokumen sekaligus. Kalau salah satu tab-nya bermasalah, kamu pasti nggak mau kan dua dokumen lainnya ikut hilang? Nah, pemisahan proses inilah yang bikin aplikasimu lebih tahan banting.

Intinya, arsitektur dua proses ini bukan beban—malah lebih tepat disebut hadiah. Dia maksa kamu buat nulis kode yang lebih aman dan lebih rapi sejak awal.

Kesimpulan

Jadi, arsitektur Electron itu berdiri di atas dua proses: main process yang jadi dapur dengan akses penuh ke sistem, dan renderer process yang jadi ruang makan buat nampilin UI secara terisolasi. Di tengah-tengah keduanya, ada preload script yang berperan sebagai pelayan yang aman, pakai contextBridge buat buka akses secukupnya. Percaya deh, paham konsep ini bakal ngirit banyak waktu debugging kamu ke depannya.

Sekarang kamu udah ngerti peran tiap proses dan kenapa mereka dipisah. Langkah selanjutnya, kamu bakal belajar gimana caranya mereka bener-bener "ngobrol" satu sama lain. Lanjut ke bab IPC: menghubungkan main & renderer, atau balik dulu ke halaman utama seri Belajar Electron buat lihat peta lengkap perjalanan belajarmu.

Bagikan: XWhatsAppFacebookLinkedIn

Artikel terkait