Catatan dari produksi
Design SystemsMonorepoCI/CD

Merilis design system di ui.pangaea.id

ui.pangaea.id adalah katalog design system Pangaea — tempat setiap komponen UI kami hidup sebagai halaman yang bisa Anda telusuri, kendalikan lewat kontrol, dan salin cara pakainya. Artikel ini menceritakan bagaimana kami mengeluarkannya dari satu app, mengemasnya jadi paket ber-versi, merilisnya ke sebuah registry, lalu men-deploy katalognya ke subdomain sendiri — dan mengotomatiskan rilis tiap komponen dengan Changesets.

Katalog design system Pangaea di ui.pangaea.id, tema terang: menu komponen berkelompok yang bisa dicari di kiri, kanvas komponen live di tengah, plus kontrol lengkap dan cuplikan cara pakai yang bisa disalin di kanan.
ui.pangaea.id — penjelajah 3 panel: menu komponen (kiri), kanvas live (tengah), kontrol + cuplikan cara pakai (kanan). Sama persis di tema terang maupun gelap.

Kenapa kami mengeluarkan UI-nya

Semua tampilan pangaea.id lahir dari satu sistem rv-*: token warna, tipografi, dan komponen React — tapi semuanya terkurung di dalam satu app. Tak ada surface lain yang bisa memakainya tanpa menyalin-tempel. Tujuannya jadi jelas: keluarkan sistem itu jadi primitif yang berdiri sendiri, ber-versi, dan bisa dikonsumsi — supaya pangaea.id dan surface Pangaea berikutnya menarik dari satu sumber yang sama.

Katalognya sendiri punya tugas: satu tempat untuk menelusuri tiap komponen, menguji tiap state-nya (tekan tombolnya, buka modalnya), dan menyalin cara pakainya yang persis. Panel kanan memunculkan satu kontrol per prop, jadi Anda bisa memutar sebuah komponen ke bentuk yang Anda mau, lalu menyalin snippet yang sudah jadi.

Satu repo, banyak paket — keputusannya

Ada tiga cara mengemas sekumpulan komponen. Kami memilih yang ketiga, dan alasannya penting.

  • Semua komponen dalam satu @labspangaea/ui. Perbaiki satu tombol → semua orang kebagian versi baru, walau tak ada yang berubah untuk mereka. Consumer menarik seluruh sistem hanya untuk memakai satu kartu.

  • Tiap komponen jadi repo GitHub sendiri (polyrepo). Ber-versi rapi, tapi 37 repo untuk di-clone, di-CI, dan dijaga sinkron. Mengubah satu token warna berarti pull request di banyak tempat.

  • Satu monorepo berisi satu paket ber-versi per komponen@labspangaea/ds-button, @labspangaea/ds-card, dan seterusnya. Satu tempat untuk dibaca dan di-CI, tapi tiap komponen naik versi sendiri-sendiri. Ini bentuk yang dipakai Radix, MUI, dan Chakra.

Perekat yang membuatnya bekerja ada di setiap package.json: React dipasang sebagai peer dependency (bukan dependency biasa), jadi consumer tak pernah menarik salinan React kedua; tiap paket mengirim CSS-nya sendiri yang sudah dikompilasi dari --rv-* token; dan semua paket duduk dalam satu npm workspace sehingga saling terhubung secara lokal saat pengembangan. Hasilnya: @labspangaea/ds-button bisa dirilis ke 0.1.2 sementara 36 paket lain tetap di 0.1.0.

Cara katalognya di-deploy ke ui.pangaea.id

Katalog itu situs statis (dibangun oleh Astro). Ia dikompilasi ke folder dist/, lalu di-deploy ke Cloudflare Pages — pipeline yang persis sama seperti pangaea.id sendiri. Bagian yang khas hanya dua: membuat project Pages-nya, lalu memasang subdomain-nya.

Langkah 1 — buat project Pages (sekali)

wrangler pages deploy tak otomatis membuat project; ia error Project not found. Buat sekali dulu, dari terminal:

npx wrangler login   # OAuth di browser, sekali saja
npx wrangler pages project create pangaea-ui --production-branch main

Namanya, pangaea-ui, harus sama persis dengan --project-name di workflow deploy. Setelah itu tiap push ke main membangun katalog dan menjalankan wrangler pages deploy apps/gallery/dist --project-name=pangaea-ui — deploy live dalam hitungan detik.

Langkah 2 — arahkan subdomain lewat CNAME

Situs sudah live di pangaea-ui.pages.dev. Untuk memberinya alamat cantik ui.pangaea.id, tambahkan custom domain ke project Pages-nya, lalu satu CNAME di DNS:

ui.pangaea.id   CNAME   pangaea-ui.pages.dev

Karena DNS pangaea.id sudah ada di Cloudflare, menambahkan custom domain di Workers & Pages → pangaea-ui → Custom domains → ui.pangaea.id membuat record CNAME itu otomatis dan menyediakan sertifikat TLS-nya. Buka ui.pangaea.id, dan katalognya muncul di root.

Merilis satu komponen — cara kerja Changesets

Ini bagian intinya. Bagaimana perubahan pada satu tombol berubah jadi versi 0.1.2 yang terpublikasi, tanpa Anda pernah mengetik nomor versi dengan tangan.

Sebuah changeset adalah satu berkas .changeset/*.md kecil yang Anda commit bersama perubahan Anda. Isinya cuma dua hal: paket mana yang naik dan seberapa besar (patch/minor/major), plus satu baris changelog.

---
"@labspangaea/ds-button": patch
---

Tambah prop `loading` ke Button.

Dari situ, tiga perintah melakukan sisanya — dan Anda hampir selalu hanya menjalankan yang pertama:

  • changeset — membuat berkas .md di atas (memilih paket + ukuran + ringkasan).
  • changeset versionmenerapkan semua changeset: menaikkan version di package.json, menulis CHANGELOG.md, lalu menghapus berkas changeset yang sudah dipakai.
  • changeset publishmempublikasikan paket mana pun yang versinya belum ada di registry, dan memasang git tag-nya.

Ukuran naiknya versi mengikuti semantic versioning: patch (0.1.00.1.1) untuk perbaikan, minor (0.1.00.2.0) untuk fitur baru yang kompatibel, major (0.1.01.0.0) untuk perubahan yang memutus kompatibilitas. Publikasinya menuju GitHub Packages di bawah scope @labspangaea — cocok dengan pemilik repo, karena itulah syarat GitHub Packages.

Pipeline rilisnya, ujung ke ujung

Ketika sebuah changeset mendarat di main, satu CI/CD run melakukan seluruh rantai ini sendiri:

changeset di main.changeset/*.mdCI — GitHub Actionsrelease.ymlbuka Version PRchore: version packagesbot auto-merge Version PRtanpa merge manualpublish → GitHub Packages+ git tag @…/[email protected]
Satu changeset di main → CI membuka Version PR, meng-auto-merge-nya, lalu mempublikasikan hanya paket yang berubah — semuanya dalam satu run.

Dari 2 PR jadi 1

Rilis pertama kali butuh dua merge manual. Kami memperbaikinya jadi satu. Bedanya ada di siapa yang men-merge Version PR — Anda, atau bot-nya.

  • PR Anda (kode + changeset) → Anda merge.
  • Version PR (naikkan versi + changelog) → Anda merge lagi. Langkah manual kedua ini yang merepotkan.

  • PR Anda (kode + changeset) → Anda merge.
  • Version PRbot yang membuka dan men-merge-nya, lalu mempublikasikan — semua dalam satu run. Anda tak menyentuh PR kedua.

Triknya: merge yang dilakukan GITHUB_TOKEN bawaan tidak memicu run workflow baru (GitHub memblokirnya agar tak jadi loop). Jadi alih-alih menunggu merge memicu publish, workflow-nya men-merge Version PR lalu mem-publish di run yang sama. Tak perlu Personal Access Token, tak perlu merge kedua. Satu-satunya langkah manual yang tersisa di seluruh rilis adalah menulis changeset-nya.

Berikutnya — memindahkan www.pangaea.id ke design system

Katalog di ui.pangaea.id adalah rumah paket-paketnya; langkah berikutnya adalah membuat www.pangaea.id mengonsumsinya. apps/labs akan menambahkan paket @labspangaea/ds-* yang dipakainya, meng-import-nya saat build, dan vite-react-ssg mem-prerender-nya — jadi HTML tetap lengkap untuk crawler, CSP tetap bersih, dan deploy tetap statis (model yang aman untuk SEO; tak ada fetch saat runtime).

revamp.css lalu digantikan bertahap oleh CSS dari @labspangaea/ds-tokens plus komponen-komponennya. Karena nama --rv-* token dipertahankan, migrasinya mekanis: satu komponen ditukar dalam satu waktu, diverifikasi, lalu lanjut ke berikutnya. Sistem yang tadinya terkurung di satu app kini jadi lapisan UI bersama untuk seluruh surface Pangaea — dengan www yang pertama.

Cerita di balik keputusannya — runtime module federation yang nyaris kami bangun tapi tidak — ada di build diary: Federasi yang tak kami bangun.

Pertanyaan umum

Kenapa satu paket per komponen, bukan satu paket besar?

Supaya tiap komponen naik versi sendiri-sendiri. Dengan satu paket besar, memperbaiki satu tombol memaksa versi baru untuk semua orang — walau tak ada yang berubah bagi mereka, dan consumer harus menarik seluruh sistem hanya untuk memakai satu kartu. Satu paket ber-versi per komponen (bentuk Radix/MUI/Chakra) berarti @labspangaea/ds-button bisa naik ke 0.1.2 sementara 36 paket lain tetap di 0.1.0. Semuanya tetap dalam satu monorepo, jadi masih satu tempat untuk dibaca dan di-CI.

Kenapa GitHub Packages, bukan npm publik?

Karena paket-paketnya hidup di repo yang sama dengan pemiliknya (labspangaea), dan GitHub Packages memetakan scope npm ke akun GitHub bernama sama — jadi @labspangaea/* terbit langsung di bawah akun yang memiliki repo, tanpa registry terpisah untuk dikelola. Consumer menambahkan satu baris .npmrc untuk menunjuk scope @labspangaea ke GitHub Packages.

Apakah katalognya butuh JavaScript untuk berjalan?

Katalognya adalah situs statis (Astro) dengan satu island React untuk kontrol interaktif. Halamannya di-prerender jadi HTML, lalu island-nya menghidupkan kontrol dan kanvas live. Komponen yang dipublikasikan sendiri mengirim CSS biasa dari --rv-* token, jadi consumer non-Tailwind pun bisa memakainya tanpa runtime tambahan.

Bagaimana saya merilis update sebuah komponen?

Jalankan npm run changeset, pilih paket dan ukuran naiknya (patch/minor/major) plus satu baris changelog, lalu commit dan push berkas itu ke main. CI membuka Version PR, meng-auto-merge-nya, dan mempublikasikan hanya paket yang berubah — satu run, tanpa merge manual. Satu-satunya langkah manual adalah menulis changeset-nya.

Sources

  1. ui.pangaea.id — katalog design system Pangaea
  2. Changesets — versioning + publikasi per paket
  3. GitHub Packages — registry npm ber-scope
  4. Cloudflare Pages — deploy situs statis