Siklus feedback loop lokal yang lambat merupakan salah satu faktor utama degradasi developer experience (DX) pada proyek React Native skala menengah hingga besar. Kombinasi konvensional menggunakan Husky dan lint-staged sering kali menjadi bottleneck pada fase pre-commit. Saat repository membengkak dengan ratusan komponen, aset, dan kode native, proses validasi lokal sebelum commit dapat memakan waktu puluhan detik.

Masalah ini dapat diselesaikan dengan memigrasikan git hooks ke Lefthook, sebuah hook manager berbasis Go yang mendukung eksekusi tugas secara paralel, alokasi resource efisien, dan integrasi minim overhead ke dalam repository React Native.

Problem Bottleneck: Mengapa Husky dan Lint-Staged Melambat

Husky dan lint-staged mengandalkan runtime Node.js. Setiap kali developer menjalankan git commit, Node.js harus memuat runtime, mem-parsing script, dan mengeksekusi dependensi JavaScript satu per satu. Selain itu, proses git stashing internal milik lint-staged—yang dirancang untuk mengisolasi perubahan non-staged—memerlukan I/O disk signifikan pada repository yang memuat dependensi native kompleks.

Kelemahan mendasar alur konvensional meliputi:

  • Overhead Startup Runtime: Node.js memerlukan puluhan hingga ratusan milidetik hanya untuk inisialisasi lingkungan eksekusi sebelum linter mulai berjalan.
  • Eksekusi Serial Default: Lint-staged secara default menjalankan task secara sekuensial atau memiliki kontrol konkurensi terbatas terhadap proses multi-threading sistem operasi.
  • Beban Stash Disk I/O: Operasi git stash pada repositori React Native berukuran gigabyte memicu latensi tinggi dan risiko konflik stash saat commit dibatalkan di tengah jalan.

Arsitektur Lefthook: Keunggulan Komparatif

Lefthook dikompilasi ke dalam binary native Go mandiri tanpa dependensi runtime eksternal saat dieksekusi oleh Git. Fitur utamanya mencakup:

  • Paralelisasi Asli (True Concurrency): Menjalankan linter, formatter, dan type checker secara bersamaan di thread CPU terpisah menggunakan goroutine.
  • Filter Argumen Efisien: Menyediakan variabel interpolasi seperti {staged_files} dan {all_files} yang diproses langsung di level binary sistem tanpa wrapper JavaScript tambahan.
  • Konfigurasi Deklaratif Tunggal: Seluruh konfigurasi git hooks didefinisikan dalam satu file lefthook.yml tanpa memerlukan file skrip terpisah di direktori .husky/.

Instalasi dan Konfigurasi Dasar

Tambahkan Lefthook ke dalam devDependencies menggunakan package manager proyek:

# Menggunakan Yarn
yarn add -D @evilmartians/lefthook

# Menggunakan NPM
npm install --save-dev @evilmartians/lefthook

Inisialisasi konfigurasi Lefthook dengan membuat file lefthook.yml pada root direktori repositori:

# Inisialisasi hook git lokal
npx lefthook install

Konfigurasi Komprehensif: lefthook.yml

Berikut adalah konfigurasi produksi lefthook.yml yang dioptimalkan untuk React Native. Konfigurasi ini menjalankan ESLint, Prettier, dan verifikasi TypeScript secara paralel pada staged files, mengecualikan folder native, serta menambahkan audit dependensi pada tahap pre-push.

# lefthook.yml
pre-commit:
  parallel: true
  commands:
    eslint:
      glob: "*.{js,jsx,ts,tsx}"
      exclude:
        - "android/"
        - "ios/"
        - "vendor/"
      run: yarn eslint --cache --fix {staged_files}
      stage_fixed: true

    prettier:
      glob: "*.{json,md,yml,yaml,css,scss}"
      exclude:
        - "android/"
        - "ios/"
      run: yarn prettier --write {staged_files}
      stage_fixed: true

    type-check:
      glob: "*.{ts,tsx}"
      run: yarn tsc --noEmit

pre-push:
  parallel: false
  commands:
    audit:
      run: yarn audit --level high

Menangani Batasan Type-Checking TypeScript

Kesalahan umum saat mengonfigurasi Git hooks adalah meneruskan daftar file yang di-stage ke TypeScript compiler, misalnya: tsc --noEmit {staged_files}. Perintah tersebut menyebabkan tsc mengabaikan opsi compilerOptions di dalam tsconfig.json, memutus resolusi path alias (seperti @components/*), dan menghilangkan deklarasi tipe global.

Solusi Lefthook: Menggunakan opsi glob: "*.{ts,tsx}" tanpa menyertakan placeholder {staged_files} pada atribut run. Lefthook mengevaluasi apakah ada file TypeScript di area staged. Jika ada, Lefthook menjalankan yarn tsc --noEmit secara utuh terhadap seluruh proyek. Jika tidak ada file TypeScript yang diubah, proses ini dilewati total (no-op).

Isolasi Aset dan Direktori Native

Aplikasi React Native mencakup folder android/ dan ios/ yang berisi kode Java/Kotlin, Objective-C/Swift, file resource Gradle, serta pods. Menyertakan direktori tersebut ke dalam validasi JavaScript menghabiskan resource.

Konfigurasi di atas menggunakan properti exclude untuk memblokir traversal ke sub-direktori native dan pihak ketiga. Selain itu, parameter stage_fixed: true memastikan bahwa modifikasi otomatis yang dilakukan oleh ESLint (--fix) atau Prettier (--write) langsung dimasukkan kembali ke staging git index secara aman.

Benchmark: Lefthook vs Husky + Lint-Staged

Pengujian dilakukan pada repositori React Native berukuran ~180 komponen TypeScript, dengan 12 staged files (kombinasi file .tsx dan .json).

Metrik PengujianHusky + Lint-Staged (Sekuensial)Lefthook (Paralel)Dampak DX
Waktu Inisialisasi Hook~480 ms (Node runtime boot)~12 ms (Native Go binary)97% lebih cepat
Durasi Validasi (Lint + Typecheck)7.20 detik2.45 detik~66% reduksi durasi
Overhead Stash IndexTinggi (Stash/Unstash via JS)Rendah (Penyimpanan memori internal)Bebas disk churn
Total Durasi Pre-Commit~7.68 detik~2.46 detikSiklus commit 3.1x lebih cepat

Troubleshoot: Mengatasi Hook Terlewat di Lingkungan Tim

Masalah berulang dalam implementasi Git hook di tim adalah developer baru yang melakukan clone repositori namun hook tidak tereksekusi secara otomatis.

1. Otomasi Instalasi via Lifecycle Script

Pastikan binary Lefthook terdaftar ke direktori .git/hooks/ setiap kali proses instalasi dependensi selesai. Tambahkan script prepare pada package.json:

// package.json
{
  "scripts": {
    "prepare": "lefthook install"
  }
}

NPM dan Yarn otomatis mengeksekusi prepare setelah npm install atau yarn install berhasil dilakukan di mesin lokal developer atau CI.

2. Konflik Konfigurasi core.hooksPath

Jika Husky pernah digunakan sebelumnya, konfigurasi Git global atau lokal mungkin masih mengarah ke folder lain. Periksa nilai hook path:

git config --local --get core.hooksPath

Jika output menunjukkan direktori selain .git/hooks (misalnya .husky), hapus konfigurasi tersebut agar Lefthook dapat mengambil alih:

git config --local --unset core.hooksPath
npx lefthook install

3. Bypass GUI Git Client

Aplikasi GUI Git (seperti Sourcetree, VS Code Git, atau GitKraken) sering kali gagal menemukan path binary node atau yarn saat hook dieksekusi karena perbedaan environment variable PATH sistem operasi (terutama di macOS). Pastikan symlink Node runtime terdeteksi di /usr/local/bin atau deklarasikan environment shell di ~/.zshrc / ~/.bashrc agar hook dapat mengenali command package manager.