Dependensi hantu (phantom dependencies) terjadi ketika kode mengimpor paket pihak ketiga yang tidak dideklarasikan di package.json, tetapi bisa diakses karena package manager menempatkannya di pohon direktori node_modules. Masalah ini sering tidak terdeteksi di lingkungan lokal, lalu menyebabkan kegagalan build atau runtime error mendadak di production saat dependency graph bergeser.

Akar Masalah Phantom Dependencies

Mekanisme resolusi modul Node.js bekerja secara rekursif menelusuri folder node_modules ke atas hingga menemukan package yang cocok. Manajer paket modern seperti npm dan Yarn v1 menerapkan teknik hoisting (perataan struktur dependensi) untuk menghemat ruang disk dan mencegah duplikasi.

Ketika paket A bergantung pada lodash, proses hoisting menempatkan lodash di level teratas node_modules. Aplikasi utama dapat mengeksekusi import _ from 'lodash' tanpa mendaftarkannya di dependencies milik aplikasi sendiri.

node_modules/
├── lodash/             <-- Di-hoist ke root
└── library-a/
    └── package.json    <-- library-a mendeklarasikan lodash

Risiko muncul ketika pengelola paket A memperbarui dependensinya ke library lain atau mengubah versinya. Pada pembaruan package-lock.json berikutnya, lodash mungkin tidak lagi di-hoist ke root atau bahkan hilang sepenuhnya. Akibatnya, runtime aplikasi memunculkan eror Cannot find module 'lodash' secara tiba-tiba.

Struktur Symlink pnpm dan Batasannya

Secara default, pnpm memitigasi masalah ini dengan isolasi berbasis content-addressable storage dan struktur symlink. Aplikasi hanya memiliki akses ke modul yang secara eksplisit dicatat pada package.json. Namun, dalam arsitektur monorepo atau workspace pnpm, konfigurasi seperti shamefully-hoist=true atau ketergantungan antar-workspace dapat membuka kembali celah phantom dependency jika tidak diaudit secara ketat.

Audit Lokal via CLI

Pemeriksaan dependensi harus dilakukan sebelum perubahan didorong ke remote repository. Dua kakas yang umum digunakan untuk keperluan ini adalah depcheck dan Knip.

Opsi 1: depcheck

depcheck menganalisis import statement pada berkas sumber lalu mencocokkannya dengan daftar dependensi di package.json.

# Jalankan audit langsung tanpa instalasi global
npx depcheck --ignores="eslint,prettier,@types/*"

Kelemahan depcheck: sering menghasilkan false positive pada import dynamic atau konfigurasi framework modern (seperti Vite, Next.js, dan Tailwind).

Opsi 2: Knip (Rekomendasi untuk TypeScript/Monorepo)

knip dirancang khusus untuk ekosistem TypeScript modern. Tool ini dapat mendeteksi dependensi yang tidak terdaftar, ekspor yang tidak digunakan, serta file dead code secara simultan.

# Instalasi dev dependency
pnpm add -D knip typescript

# Eksekusi audit phantom dependency
pnpm knip --production

Konfigurasi minimal pada knip.json untuk fokus pada dependensi tak terdaftar:

{
  "$schema": "https://unpkg.com/knip@5/resources/knip-schema.json",
  "entry": ["src/index.ts!", "src/server.ts!"],
  "project": ["src/**/*.ts"],
  "ignoreDependencies": ["@types/*"]
}

Integrasi CI Pipeline: GitHub Actions

Otomatisasi pada continuous integration memastikan tidak ada Pull Request (PR) yang lolos jika menggunakan package yang belum dideklarasikan. Workflow berikut menjalankan knip dan menghentikan pipeline (exit code 1) jika ditemukan unresolved transitive package.

name: Dependency Audit

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  audit-phantom:
    name: Audit Unlisted Dependencies
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Install pnpm
        uses: pnpm/action-setup@v3
        with:
          version: 9

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      - name: Install Dependencies
        run: pnpm install --frozen-lockfile

      - name: Run Knip
        run: pnpm exec knip --dependencies --include-libs

Mitigasi dan Best Practices

  • Gunakan Strict Package Managers: Gunakan pnpm tanpa konfigurasi hoisting ekstrem, atau terapkan Yarn Berry dengan mode PnP (Plug'n'Play) yang secara bawaan melarang akses ke dependensi yang tidak dideklarasikan.
  • Deklarasikan Dependensi Langsung: Jika proyek Anda mengimpor package tertentu (misal: clsx atau ms), daftarkan langsung di package.json meskipun dependensi lain di proyek sudah memuat package tersebut.
  • Kunci Versi Dependensi: Selalu commit pnpm-lock.yaml atau package-lock.json untuk mencegah pergeseran pohon modul otomatis saat proses CI build.