Vendoring #
Vendoring adalah praktik menyertakan kode dependensi pihak ketiga langsung ke dalam repositori proyek — alih-alih mengandalkan package manager untuk mengunduhnya saat build. Konsep ini lebih tua dari npm itu sendiri, berakar dari ekosistem Go dan C/C++ di mana tidak ada package manager terpusat. Di ekosistem TypeScript dan Node.js, vendoring jarang dilakukan secara penuh karena npm/yarn/pnpm sudah sangat matang, tapi pemahaman tentang kapan dan bagaimana melakukannya tetap relevan — terutama untuk lingkungan yang airgapped (tanpa akses internet), kebutuhan kustomisasi mendalam pada library pihak ketiga, atau mitigasi risiko supply chain attack. Artikel ini membahas vendoring secara komprehensif: definisi, kapan tepat digunakan, cara implementasi yang benar, dan alternatif modern yang sering lebih baik.
Apa Itu Vendoring? #
Dalam ekosistem Node.js/TypeScript, “vendor” merujuk pada kode yang bukan milik proyekmu sendiri tapi disertakan langsung dalam repositori:
Tanpa vendoring (cara umum):
package.json → dependensi terdaftar
npm install → npm mengunduh dari registry ke node_modules/
node_modules/ → ada di .gitignore, tidak di-commit
Dengan vendoring:
kode library → disalin ke vendor/ atau node_modules/ di repositori
node_modules/ → DI-COMMIT ke git (atau bagian tertentu saja)
npm install → tidak diperlukan atau hanya untuk dependensi non-vendor
Perlu dibedakan antara beberapa konsep yang sering dicampuradukkan:
| Konsep | Deskripsi |
|---|---|
| Full vendoring | Seluruh node_modules/ di-commit ke git |
| Selective vendoring | Hanya library tertentu yang disalin ke vendor/ |
| Lockfile pinning | package-lock.json/yarn.lock di-commit, versi terkunci tapi tidak di-vendor |
| Private registry | Library di-mirror ke registry internal (Artifactory, Verdaccio) |
Kapan Vendoring Tepat Digunakan #
Vendoring adalah trade-off yang memiliki biaya nyata (ukuran repo lebih besar, maintenance tambahan). Gunakan hanya saat manfaatnya jelas melebihi biayanya:
Skenario yang Tepat untuk Vendoring #
✓ Lingkungan airgapped (tanpa akses internet saat build/deploy)
→ Server produksi di jaringan terisolasi yang tidak bisa akses npm registry
✓ Kustomisasi mendalam pada library pihak ketiga
→ Perlu memodifikasi source library dan tidak ingin fork + publish sendiri
✓ Library tidak dipelihara (abandoned) tapi masih dibutuhkan
→ Tidak ada lagi release baru, tapi fungsinya masih kritis
✓ Kebutuhan reproducible build yang ekstrem
→ npm registry bisa down, paket bisa diunpublish (ingat kejadian left-pad 2016)
✓ Audit keamanan ketat pada kode yang dijalankan
→ Perlu review manual setiap baris kode yang masuk ke sistem
Skenario yang Tidak Tepat untuk Vendoring #
✗ "Supaya build lebih cepat" → Gunakan npm cache atau Turbopack
✗ "Supaya lebih stabil" → Gunakan lockfile dan dependency pinning
✗ "Takut library hilang dari npm" → Gunakan private registry mirror
✗ Library yang sering update → Maintenance overhead sangat tinggi
✗ Library besar dengan banyak sub-dependensi → Repo akan sangat membengkak
Struktur Direktori Vendor #
Jika memutuskan untuk vendor library tertentu, gunakan struktur yang konsisten dan mudah dipahami:
my-project/
├── src/ # Kode sumber proyek
│ ├── index.ts
│ └── services/
├── vendor/ # Library yang di-vendor
│ ├── README.md # PENTING: dokumentasi mengapa setiap lib di-vendor
│ ├── some-utils/ # Library pertama
│ │ ├── index.ts
│ │ ├── package.json # Pertahankan package.json asli untuk referensi
│ │ ├── LICENSE # WAJIB: pertahankan lisensi
│ │ └── VENDOR_INFO.md # Versi, tanggal, alasan, dan modifikasi yang dilakukan
│ └── another-lib/
│ ├── index.js
│ ├── index.d.ts # Type declaration jika library adalah JavaScript
│ └── VENDOR_INFO.md
├── tsconfig.json
└── package.json
VENDOR_INFO.md — Dokumentasi Wajib untuk Setiap Library yang Di-vendor
#
Setiap library yang di-vendor harus memiliki file dokumentasi yang menjelaskan konteksnya:
# VENDOR INFO: some-utils
## Metadata
- **Versi asli:** 2.3.1
- **Sumber:** https://github.com/contoh/some-utils
- **Tanggal di-vendor:** 2025-05-07
- **Di-vendor oleh:** Budi Santoso (@budi)
## Alasan Vendoring
Library ini di-vendor karena server produksi kami berada di jaringan airgapped
dan tidak bisa mengakses npm registry saat deployment.
## Modifikasi yang Dilakukan
- Baris 47 di `utils.ts`: Perbaikan bug #1234 — fungsi parseDate tidak menangani
timezone offset negatif dengan benar. PR sudah diajukan ke upstream:
https://github.com/contoh/some-utils/pull/567
## Jadwal Review
Review keamanan berikutnya: 2025-08-07 (3 bulan sekali)
## Lisensi
MIT License — lihat file LICENSE
Konfigurasi TypeScript untuk Vendor #
Agar TypeScript mengenali modul yang ada di direktori vendor/, konfigurasikan tsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"some-utils": ["vendor/some-utils/index.ts"],
"some-utils/*": ["vendor/some-utils/*"],
"another-lib": ["vendor/another-lib/index.d.ts"]
}
},
"include": [
"src/**/*",
"vendor/**/*.ts",
"vendor/**/*.d.ts"
]
}
Dengan konfigurasi ini, import di kode sumber terlihat identik dengan import library biasa:
// src/services/utils.ts
import { parseDate, formatDuration } from "some-utils"; // ✓ Resolves ke vendor/some-utils/
// TypeScript mengetahui tipe secara penuh karena ada source .ts di vendor
const tanggal = parseDate("2025-05-07");
Menulis Type Declaration untuk Library JavaScript #
Banyak library lama yang di-vendor hanya tersedia dalam format JavaScript tanpa type declaration. Kamu perlu menulis file .d.ts sendiri:
Deklarasi Sederhana #
// vendor/legacy-lib/index.d.ts
// Opsi 1: declare module — untuk library yang diimport dengan nama
declare module "legacy-lib" {
// Ekspor fungsi dengan tipe yang kamu tahu
export function hitungHarga(
harga: number,
kuantitas: number,
diskon?: number
): number;
export function formatMata(
jumlah: number,
matauang?: string
): string;
// Ekspor interface untuk tipe data
export interface KonfigurasiHarga {
ppn: number;
maksDiskon: number;
matauangDefault: string;
}
export function inisialisasi(konfig: KonfigurasiHarga): void;
// Default export jika library menggunakan module.exports = ...
export default {
hitungHarga,
formatMata,
inisialisasi,
};
}
Deklarasi untuk Library yang Menggunakan Global Variable #
Beberapa library lama menyuntikkan variabel global (misalnya library yang dimuat via <script> tag):
// vendor/legacy-analytics/index.d.ts
// Augmentasi global Window object
declare global {
interface Window {
LegacyAnalytics: {
track(event: string, properties?: Record<string, unknown>): void;
identify(userId: string, traits?: Record<string, unknown>): void;
page(name?: string): void;
};
}
// Atau sebagai variabel global langsung
const LegacyAnalytics: Window["LegacyAnalytics"];
}
export {}; // Membuat file ini menjadi modul
Deklarasi Bertahap — Mulai dari any
#
Jika library sangat besar dan kamu tidak punya waktu untuk menulis seluruh deklarasi, mulai dari any dan perbaiki bertahap:
// vendor/massive-library/index.d.ts
// TODO: Tambahkan tipe yang lebih spesifik secara bertahap
// Issue: https://github.com/organisasi/proyek/issues/123
declare module "massive-library" {
// Dimulai dengan any — akan diperbaiki bertahap
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const lib: any;
export = lib;
}
Alternatif Vendoring yang Sering Lebih Baik #
Sebelum memutuskan untuk vendor, pertimbangkan alternatif ini yang umumnya lebih mudah dikelola:
1. Lockfile Pinning (Cara Paling Umum) #
Commit package-lock.json atau yarn.lock ke git. Ini memastikan semua developer dan CI/CD menggunakan versi yang persis sama:
# npm — package-lock.json otomatis dihasilkan
npm install
# Instalasi yang konsisten menggunakan lockfile (untuk CI/CD)
npm ci # Lebih cepat dari npm install, strict menggunakan lockfile
# yarn — yarn.lock otomatis dihasilkan
yarn install
yarn install --frozen-lockfile # Gagal jika lockfile perlu update (untuk CI/CD)
# pnpm — pnpm-lock.yaml otomatis dihasilkan
pnpm install
pnpm install --frozen-lockfile
2. npm pack — Vendor Satu Paket sebagai Tarball
#
Alternatif yang lebih bersih dari menyalin source code — unduh paket sebagai file .tgz dan commit ke repo:
# Unduh paket sebagai tarball tanpa menginstallnya
npm pack [email protected]
# Menghasilkan: some-utils-2.3.1.tgz
# Pindahkan ke direktori vendor
mkdir -p vendor
mv some-utils-2.3.1.tgz vendor/
// package.json — referensikan tarball lokal sebagai dependency
{
"dependencies": {
"some-utils": "file:./vendor/some-utils-2.3.1.tgz"
}
}
# Install seperti biasa — npm akan menggunakan tarball lokal
npm install
Keuntungan pendekatan ini: kode library tidak tersebar di direktori vendor, tetap dalam format yang dikenal npm, dan mudah diperbarui dengan mengganti file .tgz.
3. Private Registry — Untuk Tim yang Lebih Besar #
Untuk organisasi yang perlu mirror seluruh paket npm atau mempublikasikan paket internal:
# Verdaccio — private npm registry yang ringan dan self-hosted
npm install -g verdaccio
verdaccio
# Konfigurasi npm untuk menggunakan private registry
npm set registry http://localhost:4873
# Publish paket internal ke private registry
npm publish --registry http://localhost:4873
// .npmrc — konfigurasi registry per scope
@organisasi:registry=https://npm.organisasi.com
//npm.organisasi.com/:_authToken=${NPM_TOKEN}
4. patch-package — Patching tanpa Vendoring Penuh
#
Jika hanya butuh memodifikasi satu atau dua baris di library pihak ketiga, patch-package adalah solusi yang jauh lebih baik dari vendoring penuh:
# Install patch-package
npm install --save-dev patch-package
# Edit file di node_modules (hanya untuk membuat patch, tidak permanen)
# Misalnya: node_modules/some-utils/utils.js
# Buat patch file dari perubahan
npx patch-package some-utils
# Patch tersimpan di patches/some-utils+2.3.1.patch
# File ini di-commit ke git
# Patch diterapkan otomatis setelah npm install via postinstall script
// package.json
{
"scripts": {
"postinstall": "patch-package"
},
"devDependencies": {
"patch-package": "^8.0.0"
}
}
Risiko Keamanan dan Supply Chain Attack #
Vendoring sering dimotivasi oleh keamanan, tapi perlu dipahami jenis ancaman yang ditangani dan yang tidak:
flowchart TD
A[Ancaman Supply Chain] --> B[Typosquatting]
A --> C[Dependency Confusion]
A --> D[Malicious Publish]
A --> E[Compromised Maintainer]
B --> B1[Nama paket mirip yang populer\ncth: 'lodahs' bukan 'lodash']
C --> C1[Paket internal yang nama\nsamanya ada di npm publik]
D --> D1[Paket yang awalnya baik\nlalu diubah jadi berbahaya]
E --> E1[Akun maintainer\ndiretas, kode berubah]
B1 --> F[Mitigasi: Lockfile + audit]
C1 --> G[Mitigasi: Scope paket + private registry]
D1 --> H[Mitigasi: Lockfile + SRI hash]
E1 --> I[Mitigasi: Vendoring + review manual]
style F fill:#51cf66,color:#fff
style G fill:#51cf66,color:#fff
style H fill:#339af0,color:#fff
style I fill:#fcc419,color:#000Praktik Keamanan Dependensi #
# Audit kerentanan yang diketahui
npm audit
npm audit fix # Perbaiki otomatis
npm audit fix --force # Termasuk breaking changes (gunakan dengan hati-hati)
# Periksa lisensi semua dependensi
npx license-checker --summary
# Periksa paket yang sudah usang
npm outdated
# Verifikasi integritas package setelah install
# package-lock.json mengandung integrity hash (SHA-512) untuk setiap paket
Perbandingan Strategi Manajemen Dependensi #
Strategi Keuntungan Kerugian
─────────────────────────────────────────────────────────────────────
Lockfile saja Mudah, standar industri Bergantung pada npm registry
Selective vendoring Kontrol penuh atas lib kritis Maintenance overhead tinggi
npm pack (tarball) Lebih bersih dari salin source Masih perlu update manual
patch-package Modifikasi kecil tanpa vendor Tergantung patch-package
Private registry Scalable untuk tim besar Infrastruktur tambahan
Full vendoring Independen dari internet Repo sangat besar
Ringkasan #
- Vendoring jarang diperlukan di ekosistem Node.js/TypeScript modern — lockfile (
package-lock.json,yarn.lock) sudah cukup untuk sebagian besar kebutuhan reproducible build.- Vendor hanya jika ada alasan kuat — lingkungan airgapped, kustomisasi mendalam pada library yang tidak bisa di-fork, atau library abandoned yang masih kritis.
- Selalu dokumentasikan vendor — buat
VENDOR_INFO.mduntuk setiap library yang di-vendor; jelaskan versi, tanggal, alasan, dan modifikasi yang dilakukan.- Pertahankan file
LICENSEdari library asli — vendoring tidak mengubah lisensi; melanggar lisensi library adalah risiko hukum.- Tulis type declaration (
.d.ts) untuk library JavaScript yang di-vendor agar TypeScript tetap type-safe; mulai darianyjika library sangat besar dan perbaiki bertahap.npm packlebih bersih dari menyalin source code — library tersimpan sebagai tarball.tgzdannpm installtetap berfungsi normal.patch-packageadalah solusi terbaik untuk modifikasi kecil** pada library pihak ketiga tanpa perlu vendoring penuh — patch disimpan sebagai diff dan diterapkan otomatis setelahnpm install.- Private registry (Verdaccio, Artifactory) adalah solusi yang lebih baik dari vendoring untuk tim besar yang butuh kontrol atas dependensi namun tidak ingin ukuran repo membengkak.
- Audit dependensi secara rutin dengan
npm audit— lockfile dan vendoring tidak melindungi dari kerentanan yang ditemukan setelah library di-install/di-vendor.