pnpm Catalogs: Atasi Version Drift Dependency Monorepo

Foto oleh Yoav Aziz on Unsplash
pnpm menambahkan catalogs di versi 9.5, dirilis pada 7 Juli 2024. Sejak versi itu Anda bisa mendeklarasikan catalogs di pnpm-workspace.yaml dan merujuknya dengan protokol catalog:. Fitur ini sudah dibahas sebagai RFC sejak 2022 sebelum akhirnya dirilis.
Alih-alih rentang versi konkret, Anda menulis catalog: untuk menarik versi dari default catalog, atau catalog:nama untuk memakai named catalog. Ini bekerja di dependencies, devDependencies, peerDependencies, dan optionalDependencies. Saat pnpm publish atau pnpm pack, pnpm menggantinya dengan rentang versi nyata yang dipecahkan.
Tanpa catalogs, versi dependency diulang di setiap package.json yang memakainya, sehingga menaikkannya di dua branch bertabrakan di tiap file. Catalogs memindahkan versi ke satu entri di pnpm-workspace.yaml, jadi file package.json tetap tak tersentuh saat upgrade dan tabrakan tidak punya tempat untuk terjadi.
Default catalog adalah peta tanpa nama di bawah kunci catalog dan dirujuk dengan catalog: polos. Named catalogs berada di bawah kunci catalogs dan dirujuk dengan nama, seperti catalog:react17. Pakai named catalog ketika Anda sengaja perlu lebih dari satu versi sebuah dependency, misalnya saat migrasi.
Tidak. Catalogs memecahkan pengelolaan versi dependency: apa yang dipasang dan pada versi berapa di seluruh workspace. Turborepo adalah build system dan task runner yang menjadwalkan tugas dan meng-cache outputnya, serta menyerahkan pengelolaan dependency ke package manager Anda. Keduanya saling melengkapi, bukan alternatif.

Foto oleh Yoav Aziz on Unsplash
Ringkasan Utama
pnpm Catalogs memungkinkan monorepo menetapkan setiap versi dependency sekali saja di pnpm-workspace.yaml, lalu merujuknya dari setiap package.json lewat protokol catalog:. Upgrade cukup mengubah satu baris, versi tetap sama di seluruh paket, dan konflik merge hilang. Catalogs mengelola versi; task runner seperti Turborepo menangani build dan caching.
Setiap monorepo yang pernah saya kerjakan pada akhirnya mengidap penyakit diam yang sama. Satu paket mengunci React di 18.2.0, paket lain di 18.3.1, paket ketiga menulis caret longgar dan mendapat versi apa pun yang terakhir dipecahkan lockfile. Tidak ada yang jelas rusak, jadi tidak ada yang membetulkannya, lalu muncul bug halus yang hanya kambuh ketika dua salinan library yang sama berakhir di dalam bundle. Mendiagnosisnya menghabiskan satu sore yang tak akan kembali.
Akar masalahnya adalah bahwa dalam workspace biasa, versi sebuah dependency bersama ditulis di sebanyak jumlah paket yang memakainya. Tidak ada satu sumber kebenaran, jadi drift menjadi keadaan bawaan dan menjaga sinkronisasi adalah kerja manual. pnpm Catalogs, ditambahkan di pnpm 9.5 pada Juli 2024, mengatasi ini di tingkat perkakas: Anda mendeklarasikan versi sekali dan merujuknya di mana pun.
Sebelum catalogs, menjaga sebuah dependency selaras di dua puluh paket berarti menyunting dua puluh file package.json untuk setiap kenaikan versi. Dalam praktiknya itu jarang berjalan bersih. Seseorang memperbarui tiga paket yang sedang dia sentuh, melupakan sisanya, dan repo pun tergelincir kembali ke campuran versi. Lockfile kemudian memasang banyak salinan, yang menggelembungkan ukuran install dan, untuk library stateful seperti React atau library validasi dengan registry bersama, bisa memicu bug runtime nyata.
Ada biaya kedua yang lebih sepele: konflik merge. Ketika dua branch sama-sama menaikkan sebuah dependency yang dipakai luas, keduanya bertabrakan di setiap package.json yang menyebutnya. Reviewer membuang waktu menyelesaikan string versi yang sama berulang kali. Catalogs memindahkan string versi itu keluar dari setiap package.json sepenuhnya, sehingga tabrakan tidak punya tempat untuk terjadi.
Sebuah pnpm workspace didefinisikan oleh file pnpm-workspace.yaml di akar repositori; field packages-nya mendaftar glob yang membentuk monorepo. Catalogs hidup di file yang sama. Default catalog adalah peta tanpa nama dari nama paket ke rentang versi di bawah kunci catalog. Anda juga bisa mendeklarasikan named catalogs di bawah catalogs ketika Anda sengaja perlu lebih dari satu versi sesuatu berjalan sekaligus.
# pnpm-workspace.yaml (at the repo root)
packages:
- "apps/*"
- "packages/*"
# The default catalog: unnamed version ranges
catalog:
react: ^18.3.1
react-dom: ^18.3.1
typescript: ^5.5.4
zod: ^3.23.8
# Named catalogs: deliberate, parallel version sets
catalogs:
react17:
react: ^17.0.2
react-dom: ^17.0.2Rentang versi yang Anda taruh di sebuah catalog adalah specifier semver biasa, persis yang jika tidak akan Anda tulis di package.json. Catalog tidak mengubah cara resolusi bekerja; ia hanya memindahkan di mana rentang itu ditulis. Itulah sebabnya mengadopsi catalogs adalah refaktor mekanis tanpa perubahan versi terpasang di hari pertama.
Di dalam sebuah paket, Anda mengganti rentang versi konkret dengan protokol catalog:. Ditulis polos, catalog: menarik versi dari default catalog. Ia bekerja di dependencies, devDependencies, peerDependencies, dan optionalDependencies, sehingga sebuah package.json bisa mengarahkan setiap dependency bersama ke catalog dan berhenti membawa nomor versinya sendiri.
{
"name": "@acme/web",
"dependencies": {
"react": "catalog:",
"react-dom": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"typescript": "catalog:"
}
}Ketika Anda menjalankan pnpm publish atau pnpm pack, pnpm mengganti rujukan catalog: dengan rentang versi nyata yang dipecahkannya, persis seperti yang dilakukannya untuk protokol workspace:. Paket yang dipublikasikan karenanya berisi rentang versi biasa, sehingga konsumen di luar monorepo Anda tak pernah melihat atau perlu memahami catalogs.
Kadang Anda memang perlu dua versi hidup berdampingan, misalnya saat memigrasikan widget lama dari React 17 ke React 18. Sebuah named catalog menangkap niat itu. Anda merujuknya dengan nama lewat catalog:react17, yang terbaca jelas sebagai pengecualian yang disengaja alih-alih drift yang tak sengaja. Namanya mendokumentasikan alasan pemisahan itu ada.
{
"name": "@acme/legacy-widget",
"dependencies": {
"react": "catalog:react17",
"react-dom": "catalog:react17"
}
}Inilah beda antara drift dan keputusan. Drift tidak terlihat dan tak seorang pun memilihnya; sebuah named catalog adalah pilihan berlabel yang sudah ditinjau, duduk di satu file. Ketika migrasi selesai Anda menghapus named catalog itu dan mengembalikan paket-paket tersebut ke default, dan pengecualian pun lenyap dalam satu commit yang jelas.
Catalogs memusatkan rentang versi, bukan mekanisme pembaruan. Ketika catalogs pertama dirilis, pnpm update tidak menulis ulang entri catalog, jadi Anda meng-upgrade dengan menyunting pnpm-workspace.yaml langsung lalu memasang ulang. Selalu periksa dokumentasi versi pnpm Anda untuk dukungan perintah update terkini sebelum berasumsi perintah kenaikan versi akan menyentuh catalog untuk Anda.
Kontrasnya paling mudah dilihat berdampingan. Tiga kepedulian yang sama, ditangani cara lama dan cara catalog, menunjukkan mengapa fitur ini sepadan dengan migrasi kecilnya.
| Kepedulian | Tanpa catalogs | Dengan catalogs |
|---|---|---|
| Sumber kebenaran sebuah versi | Diulang di setiap package.json | Satu entri di pnpm-workspace.yaml |
| Meng-upgrade dependency bersama | Sunting setiap paket yang memakainya | Sunting satu baris catalog |
| Konflik merge saat kenaikan versi | Satu per package.json terdampak | Tidak ada: package.json tak tersentuh |
| Risiko versi ganda | Tinggi, drift adalah bawaan | Rendah, versi tetap selaras |
Catalogs memecahkan tepat satu masalah: versi setiap dependency yang disepakati seluruh workspace. Ia tidak berkata apa pun tentang bagaimana Anda membangun, menguji, atau mengirim paket-paket itu. Setelah versi dipusatkan, rasa sakit monorepo berikutnya adalah orkestrasi tugas, dan itu adalah pekerjaan alat yang berbeda.
Turborepo adalah build system dan task runner untuk monorepo JavaScript dan TypeScript. Dokumentasinya sendiri tegas bahwa ia menyerahkan pengelolaan dependency ke package manager Anda dan bekerja dengan npm, yarn, atau pnpm. Turborepo bertanggung jawab atas sekumpulan kepedulian yang terpisah:
Jadi pembagiannya bersih. pnpm workspaces plus Catalogs memutuskan apa yang dipasang dan pada versi berapa; Turborepo memutuskan bagaimana tugas yang dihasilkan berjalan dan bagaimana outputnya di-cache. Raih catalogs begitu sebuah dependency muncul di lebih dari satu paket, dan tambahkan task runner ketika menjalankan build dalam urutan yang benar menjadi bagian yang lambat.