⚡ Jawaban Singkat / Key Takeaways: ts-pattern memang elegan, tapi weight-nya 2.8kB gzipped dan tree-shaking tidak selalu bekerja sempurna. Native pattern matching TypeScript (switch + discriminated union + never fallback) bisa ganti 90% use case ts-pattern dengan jaminan compile-time safety tanpa dependensi tambahan. Tapi native approach masih belum bisa handle guard clause dan chaining sefleksibel ts-pattern. Pahami batasannya sebelum migrasi penuh.

Kamu Lagi Enak-Enak Code Review, Lalu Someone Nambah 3KB Gara-Gara Library Pattern Matching

Kamu buka PR. Semua kelihatan oke. Tapi ada satu file baru: user-fetcher.ts. Di dalamnya, ada import { match } from 'ts-pattern'. Sekilas fine aja. Cuma satu fungsi kecil. Tapi pas kamu buka bundle analyzer, tiba-tiba ada tambahan 2.8kB gzipped. Dari satu import. Di satu file. Di project yang sebenarnya cuma butuh switch-case biasa.

Rasanya? Sebel banget. Apalagi kalau kamu tech lead atau codebase maintainer yang bertanggung jawab atas performa bundle. Pattern matching library memang memudahkan, tapi setiap dependensi punya harga. Pertanyaannya: kapan harga itu worth it, dan kapan kamu bisa ganti dengan native feature yang udah ada di TypeScript?

Artikel ini bakal kasih kamu diff-by-diff migration path dari ts-pattern ke native pattern matching TypeScript. Plus daftar jujur tentang apa yang native bisa dan belum bisa lakukan. Bukan fanboying, bukan juga hating. Cuma fakta buat keputusan arsitektur yang lebih baik.

TypeScript native pattern matching vs ts-pattern code comparison side by side
Native pattern matching TypeScript vs library ts-pattern, perbandingan kode

TS-Pattern vs Switch + Discriminated Union: Head-to-Head Diff

Mari kita langsung lihat kode. Ini contoh umum pattern matching untuk response API:

// SEBELUM: ts-pattern
import { match, P } from 'ts-pattern';

type ApiResponse =
  | { status: 'ok'; data: User[] }
  | { status: 'error'; code: number; message: string }
  | { status: 'loading' };

const render = (res: ApiResponse) =>
  match(res)
    .with({ status: 'ok' }, ({ data }) => renderUsers(data))
    .with({ status: 'error' }, ({ code, message }) => renderError(code, message))
    .with({ status: 'loading' }, () => renderSpinner())
    .exhaustive();
// SESUDAH: native switch + discriminated union
const render = (res: ApiResponse) => {
  switch (res.status) {
    case 'ok':
      return renderUsers(res.data);
    case 'error':
      return renderError(res.code, res.message);
    case 'loading':
      return renderSpinner();
    default: {
      const _exhaustive: never = res;
      throw new Error(`Unhandled: ${JSON.stringify(_exhaustive)}`);
    }
  }
};

Dua blok kode di atas menghasilkan output yang sama. Tapi versi native tidak butuh import, tidak nambah 2.8kB ke bundle-mu, dan tetap dapat exhaustiveness checking lewat never type di branch default. Kalau ada orang yang nambah varian baru ke ApiResponse tapi lupa update render, TypeScript akan menolak compile. Bukan crash di production. Tidur nyenyak.

Dimana Native Excel: Compile-Time Safety yang Nggak Bisa Ditawar

Ini nilai jual terbesar native pattern matching. Exhaustiveness checking di level type system. Ketika kamu pakai never fallback, TypeScript secara struktural memverifikasi bahwa semua anggota union sudah ditangani. Bukan dengan runtime assertion, bukan dengan unit test. Tapi langsung di compiler. Zero runtime cost.

Hal yang sering bikin salah paham justru di sini: banyak developer pilih ts-pattern karena syntax .exhaustive()-nya kelihatan lebih “safe”. Padahal .exhaustive() di ts-pattern itu runtime check. Dia throw error kalau ada varian yang terlewat. Artinya, kamu baru tahu bug-nya ketika kode sudah jalan di production. Sementara native never fallback memberi kamu error di CI pipeline. Sebelum sempat deploy.

Perhatikan juga soal narrowing otomatis. Di setiap case dalam switch, TypeScript otomatis mempersempit tipe res ke varian yang sesuai. Kamu tidak perlu annotation tambahan. Tidak perlu P.select(). Discriminant literal (res.status) sudah cukup buat TypeScript tahu persis tipe apa yang sedang kamu tangani.

TypeScript switch statement exhaustive checking discriminated union code
Exhaustiveness checking native TypeScript dengan never fallback

Reality Check: Dimana Native Masih Kalah dari TS-Pattern

Sekarang jujur. Native pattern matching TypeScript bukan silver bullet. Ada beberapa situasi di mana kamu bakal kangen fitur-fitur ts-pattern. Dan ini penting buat kamu pertimbangkan sebelum migrasi total.

1. Guard Clause: Kondisi Kompleks di Dalam Pola

TS-Pattern mendukung .when() yang memungkinkan guard clause di dalam pattern match. Contoh: kamu mau match status: 'error' tapi cuma kalau code > 500. Dengan ts-pattern, ini satu baris. Dengan native switch, kamu harus tambah if di dalam case. Tidak separah yang dibayangkan, tapi jadi kurang bersih kalau guard-nya banyak.

// TS-PATTERN: guard clause
match(res)
  .with({ status: 'error' }, (r) => P.when(r.code > 500), () => renderServerError())
  .otherwise(() => renderDefault());

// NATIVE: harus split logic
case 'error':
  if (res.code > 500) return renderServerError();
  return renderDefault();

2. Chaining dan Composable Pattern

TS-Pattern unggul di composability. Kamu bisa define pola yang reusable, misalnya P.union atau P.array, lalu chain di banyak tempat. Native switch tidak punya mekanisme compose pola. Kamu harus ulangi logika pemisahan di setiap fungsi. Ini friction nyata untuk codebase dengan pola kompleks yang berulang.

3. Nested Pattern + Destructuring

TS-Pattern bisa match nested object dua level sekaligus: .with({ user: { role: 'admin' } }, ...). Di native switch, kamu harus narik dulu discriminant level atas, baru if di dalamnya. Ini tidak fatal, tapi verbositas meningkat signifikan untuk data structure yang deeply nested.

Peta Migrasi 3 Langkah: Dari TS-Pattern ke Native

Migrasi tidak harus “big bang rewrite.” Kamu bisa lakukan bertahap. Ini strategi yang udah terbukti di beberapa codebase production:

  1. Audit seluruh pemakaian ts-pattern. Grep import { match } from 'ts-pattern'. Kategorikan: (a) simple union match yang bisa langsung jadi switch, (b) match dengan guard clause, (c) match dengan nested pattern. Yang kategori (a) bisa langsung migrasi sekarang.
  2. Ganti bertahap, satu file per PR. Jangan satu PR gede. Mulai dari file utility paling kecil. Tambahkan never fallback di setiap switch. Pastikan TypeScript strict mode nyala. Kalau TypeScript tidak berteriak saat kamu komen salah satu case, exhaustiveness checking-mu belum benar. Referensi: TypeScript 5.5 release notes.
  3. Tetap pertahankan ts-pattern untuk complex case. Guard clause yang banyak, nested pattern yang dalam, atau composable pattern yang dipakai ulang di banyak tempat. Ini mungkin worth keeping ts-pattern. Tapi batasi dengan ESLint rule: ts-pattern hanya boleh di file khusus (misalnya *.pattern.ts), supaya tidak menyebar lagi. Baca soal penanganan kompleksitas serupa di artikel kami tentang exhaustiveness checking TypeScript 5.5.
TypeScript bundle size comparison ts-pattern library vs native matchers
Perbandingan bundle size: ts-pattern vs native pattern matching

Tabel Cepat: Kapan Pakai Yang Mana

SkenarioNative SwitchTS-Pattern
Union 3-5 varian, discriminant literal✅ Cocok⚠️ Overkill
Guard clause (>2 kondisi)⚠️ Verbose✅ Cocok
Nested pattern 2+ level⚠️ Verbose✅ Cocok
Butuh exhaustiveness guarantee compile-time✅ Built-in⚠️ Runtime only
Zero dependency requirement✅ Native❌ +2.8kB
Pattern reusability/composability❌ Tidak adaP.union, dll

Kesimpulan: Jangan Bawa Parasut ke Tangga

TS-Pattern adalah library yang sangat baik. Tapi sebagian besar pemakaiannya di real-world codebase sebenarnya bisa diganti dengan switch statement dan discriminated union yang sudah native. Kamu cukup tambahkan never fallback untuk menjaga exhaustiveness. Hasilnya: bundle lebih kecil, zero dependency, dan compile-time safety yang lebih ketat.

Untuk complex use case yang tersisa, simpan ts-pattern secara selektif. Tapi jangan biarkan satu import menyebar ke seluruh codebase hanya karena “lebih enak dibaca.” Readability itu subjektif. Bundle size itu objektif. Dan user-mu yang loading aplikasi di 3G tidak peduli betapa elegan-nya syntax .with().

Mau insight performa dan arsitektur TypeScript langsung ke inbox? Subscribe newsletter Hadezuka. Tanpa spam. Cuma konten teknis yang beneran berguna buat developer kayak kamu.

FAQ

Apa itu ts-pattern dan kenapa developer pakai?

ts-pattern adalah library pattern matching untuk TypeScript yang menyediakan API deklaratif (match().with().exhaustive()) untuk mencocokkan pola pada union type, object, dan tuple. Developer memakainya karena sintaksnya ekspresif dan mendukung guard clause, nested pattern, serta composable pattern. Tapi harga yang dibayar adalah tambahan ~2.8kB gzipped ke bundle.

Apa beda .exhaustive() di ts-pattern dengan never fallback di native switch?

.exhaustive() di ts-pattern adalah runtime check: kalau ada varian yang terlewat, dia akan throw error saat kode dijalankan. Sementara never fallback di native switch adalah compile-time check: TypeScript akan menolak kode kamu sebelum sempat di-build. Jadi native approach menangkap bug lebih awal di pipeline CI/CD.

Kapan sebaiknya tetap pakai ts-pattern walau native udah cukup?

Pertahankan ts-pattern jika: (1) kamu punya lebih dari 2 guard clause per pattern, (2) pattern matching sangat nested (2+ level), (3) pola yang sama dipakai ulang di banyak tempat via P.union atau composable pattern, (4) tim tidak familiar dengan never type fallback. Untuk use case lain, native switch sudah cukup.

Apakah ada ESLint rule untuk mencegah ts-pattern menyebar di codebase?

Ada. Kamu bisa pakai ESLint rule no-restricted-imports untuk membatasi import ts-pattern hanya di file yang kamu izinkan (misalnya *.pattern.ts). Konfigurasinya: {‘paths': [{‘name': ‘ts-pattern', ‘message': ‘Gunakan native switch untuk pattern matching sederhana. Lihat panduan migrasi.'}]}. Ini mencegah developer otomatis import ts-pattern tanpa pertimbangan.

About the Author

Dzul Qurnain

Suka nonton Anime, ngoding dan bagi-bagi tips kalau tahu.. Oh iya, suka baca ( tapi yang menarik menurutku aja)... Praktisi WordPress, web development, SEO, dan server administration yang membagikan tutorial teknis dan catatan implementasi nyata.

View All Articles