Typescript Project Organization Monorepo Tsconfig Inheritance Shared Packages
Selesai membaca dokumentasi ini?
Kembali ke rujukan utama TypeScript untuk melanjutkan topik lainnya.
Kembali ke rujukan utama TypeScript untuk melanjutkan topik lainnya.
Project organization di TypeScript adalah cara memecah source code, config, build output, dan shared package supaya struktur repo tetap kebaca saat project tumbuh. Di tahap awal, satu app dengan satu tsconfig.json masih cukup. Tapi begitu ada beberapa aplikasi, shared package, test package, dan build target berbeda, organisasi project mulai menentukan apakah codebase masih enak dirawat atau justru jadi tumpang tindih.
Monorepo adalah satu repository yang menampung beberapa package atau app sekaligus. TypeScript sering dipakai di monorepo untuk memisahkan concern dengan jelas: aplikasi tetap fokus ke runtime, package shared fokus ke tipe, util, atau domain logic, dan konfigurasi diletakkan di level yang bisa diwariskan.
Monorepo membantu kalau beberapa hal ini sudah mulai terjadi:
Monorepo biasanya tidak terasa worth it kalau:
Intinya: monorepo bukan tujuan, tapi alat untuk mengurangi duplikasi dan menjaga batas yang sehat antarbagian.
Struktur yang umum dipakai:
repo/
├─ apps/
│ ├─ web/
│ └─ admin/
├─ packages/
│ ├─ ui/
│ ├─ types/
│ ├─ utils/
│ └─ config/
├─ tsconfig.base.json
├─ package.json
├─ pnpm-workspace.yaml / turbo.json / nx.json
└─ eslint.config.*Prinsipnya:
apps/ berisi entry point yang dijalankan user.packages/ berisi bagian yang reusable.tsconfig.base.json menampung default yang dipakai bersama.Kalau struktur ini disiplin, kamu bisa baca repo dari atas ke bawah tanpa menebak-nebak package mana yang boleh mengimpor apa.
Di monorepo, setiap package tetap punya batasnya sendiri. Import antarpackage sebaiknya lewat nama package, bukan lewat path relatif panjang yang menembus folder lain.
Contoh pola yang sehat:
apps/web mengimpor @acme/uiapps/web mengimpor @acme/typespackages/ui tidak mengimpor file internal apps/adminKunci utamanya adalah dependency graph yang mengalir satu arah. Kalau package saling silang tanpa aturan, monorepo berubah jadi folder besar yang cuma kebetulan dibagi-bagi.
Diagram di atas menunjukkan pola dependency yang ideal:
TypeScript mendukung inheritance lewat extends. Ini dipakai untuk memisahkan config dasar dari config per-package.
Pola umum:
tsconfig.base.json di root berisi setting bersamapackages/*/tsconfig.json mewarisi base dan menambah override lokalapps/*/tsconfig.json mewarisi base tetapi bisa punya target, path, atau include yang bedaContoh root base config:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noEmit": true,
"baseUrl": ".",
Contoh package-level config:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"composite": true,
"declaration": true,
"declarationMap": true
},
"include"
Prinsip inheritance yang perlu diingat:
extends mewariskan opsi, bukan folder secara otomatis.include, exclude, dan files tetap perlu dipikirkan per package.Shared package biasanya dipakai untuk tiga jenis isi:
Types only package
Domain schema package
Shared utilities package
Pola typing yang umum dan sehat:
export type UserId = string & { readonly __brand: "UserId" };
export interface User {
id: UserId;
name: string;
email: string;
}
Kalau kamu punya shared contract, prioritaskan source of truth tunggal. Misalnya:
Contoh pola yang sering berguna:
type alias untuk union, mapped type, atau utility result typeinterface untuk object shape yang mungkin di-extendKalau package types mulai dipakai untuk runtime logic juga, package itu sering berubah jadi campuran aneh antara contract dan implementation. Akibatnya:
Aturan aman: kalau package memang niatnya type-only, pertahankan sebagai type-only.
Contoh buruk:
apps/web import packages/uipackages/ui import packages/typespackages/types import util dari apps/webBegitu dependency balik masuk, build dan editor mulai sulit memprediksi urutan resolve.
Solusi: pakai arah dependency yang tegas dan hindari shared package yang tergoda mengimpor application code.
Path alias di tsconfig tidak otomatis bekerja di semua tool runtime.
Yang sering terjadi:
Karena itu alias harus diselaraskan dengan bundler, test runner, dan package export map.
Kalau shared package menghasilkan .d.ts, tapi emit dan source-nya tidak konsisten, consumer bisa melihat type yang berbeda dari runtime nyata.
Biasanya muncul saat:
declaration aktif tapi source tidak dirapikanexports packagePattern index.ts yang melempar semua export dari semua file memang nyaman, tapi mudah bikin boundary kabur.
Gunakan barrel export secukupnya:
Kalau packages/types berisi type yang sebenarnya hanya dipakai satu app, package itu akan membengkak tanpa manfaat nyata.
Tanda-tandanya:
Checklist sederhana:
tsconfig.base.json di rootpackage.jsoncomposite dan referensi projectContoh struktur referensi project:
{
"files": [],
"references": [
{ "path": "./packages/types" },
{ "path": "./packages/utils" },
{ "path": "./packages/ui" },
{ "path": "./apps/web" }
]
}Dengan pola ini, TypeScript bisa memahami urutan build dan dependency antarproject secara lebih stabil.
Pilih repo biasa kalau:
Kalau belum ada masalah struktur, jangan memaksa monorepo hanya karena terlihat lebih “scalable”. Struktur yang terlalu dini sering menambah beban tanpa menyelesaikan masalah nyata.
tsconfig.base.json jadi satu sumber setting bersama./dokumentasi/typescript/typescript-tsconfig-module-resolution-path-aliases-declaration-files-build-targets/dokumentasi/typescript/typescript-types-interfaces-generics-narrowing-utility-types/dokumentasi/typescript/typescript-advanced-types-unions-intersections-mapped-conditional-infer-type-guards