Pada chapter sebelumnya kita menjalankan tsc dengan menunjuk satu berkas demi satu; cara itu cepat usang ketika proyek berisi banyak berkas dengan kebutuhan konfigurasi yang sama. Proyek TypeScript yang tertata menyimpan seluruh pengaturan compiler di satu berkas tsconfig.json, sehingga satu perintah tsc cukup untuk memeriksa seluruh sumber kode. Pada chapter ini kita akan membangun kerangka proyek tersebut dari nol: memasang TypeScript sebagai devDependency, menyusun tsconfig.json untuk Node.js, dan menyiapkan skrip package.json yang memudahkan pemeriksaan berulang.

Memasang TypeScript pada Proyek #

TypeScript adalah package npm bernama typescript; binary utamanya adalah tsc. Pada proyek nyata, kita memasangnya sebagai devDependency karena compiler hanya dibutuhkan saat mengembangkan, bukan saat aplikasi berjalan:

bash
npm install --save-dev typescript

Setelah terpasang, perintah npx tsc --version menampilkan versi compiler yang terbaca dari node_modules proyek. Lingkungan Course ini tidak memasang typescript di dalam setiap chapter, sehingga kita memanggil compiler dengan cara lain — memakai npx yang mengunduh dan menjalankan versi tertentu tanpa memasangnya permanen:

bash
npx --yes [email protected] --version

Output yang Dihasilkan:

text
Version 7.0.2

Perintah di atas memakai spesifikasi [email protected] sehingga npm menjalankan tepat versi 7.0.2 — versi yang sama dengan yang dipakai seluruh contoh Course ini. Pola ini juga berguna untuk mencoba versi berbeda tanpa menyentuh package.json proyek.

Struktur Folder Proyek #

Kita akan memakai proyek mini bernama toko-sederhana sebagai contoh hidup sepanjang beberapa chapter berikutnya. Kode sumber ditempatkan di folder src/ agar terpisah dari berkas konfigurasi di akar proyek:

text
toko-sederhana/
├── package.json
├── tsconfig.json
└── src/
    ├── barang.ts
    └── utama.ts

Dua berkas di dalam src/ menjalankan peran berbeda: barang.ts adalah module yang mengekspor bentuk data dan function pengolahnya, sedangkan utama.ts adalah titik masuk yang mengimpor lalu memakai module tersebut. Pemisahan ini kelak memudahkan penambahan berkas baru tanpa menyentuh kode yang sudah ada.

Menyusun tsconfig.json #

Berkas tsconfig.json adalah pusat kendali compiler. Isi berikut ini adalah konfigurasi yang kita pakai untuk proyek Node.js modern:

json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "allowImportingTsExtensions": true,
    "noEmit": true,
    "strict": true
  },
  "include": ["src/**/*.ts"]
}

Peran setiap opsi dalam compilerOptions:

OpsiNilai kitaPeran
targetesnextStandar JavaScript hasil kompilasi; Node 24 sudah mendukung sintaks modern sehingga tidak perlu diturunkan
modulenodenextFormat module mengikuti aturan Node.js: berkas di bawah "type": "module" diperlakukan sebagai ESM
moduleResolutionnodenextStrategi pencarian berkas saat menemui import; selaras dengan cara Node.js menyelesaikan path
allowImportingTsExtensionstrueMengizinkan penulisan import ... from "./barang.ts" dengan ekstensi .ts yang eksplisit
noEmittruetsc hanya memeriksa tanpa menulis berkas .js — kita menjalankan .ts langsung lewat Node
stricttrueMengaktifkan keluarga pemeriksaan ketat; dibahas tuntas pada chapter Strict Mode

Dua opsi tambahan akan berperan nanti ketika proyek perlu menghasilkan berkas JavaScript untuk didistribusikan: outDir menentukan folder tempat berkas hasil kompilasi ditulis, dan rootDir menentukan folder sumber yang menjadi akar struktur output. Alur kompilasi tersebut kita praktikkan pada chapter Menjalankan TypeScript di Node.js — untuk sekarang, kombinasi noEmit + Node yang menjalankan .ts langsung sudah mencukupi.

Opsi allowImportingTsExtensions layak mendapat perhatian khusus karena berkaitan langsung dengan cara Node.js memuat berkas. Tanpa opsi tersebut, tsc menolak import yang berakhir .ts — aturan lama yang mengasumsikan import ditulis memakai ekstensi .js karena hasil kompilasi nanti berupa berkas .js. Karena strategi kita adalah memeriksa dengan tsc lalu menjalankan berkas .ts apa adanya, ekstensi .ts pada import justru yang kita butuhkan. Opsi ini mensyaratkan noEmit, dan keduanya memang satu paket dalam alur kerja kita.

package.json dan Skrip Proyek #

Berkas package.json proyek kita memuat tiga keputusan: proyek bersifat privat, seluruh berkas .ts adalah ESM, dan ada dua skrip pintasan:

json
{
  "name": "toko-sederhana",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "typecheck": "tsc --noEmit",
    "mulai": "node src/utama.ts"
  }
}

Baris "type": "module" penting karena opsi module: "nodenext" membacanya: tanpa baris tersebut, tsc memperlakukan berkas .ts kita sebagai CommonJS dan hasil perilakunya berubah. Skrip typecheck menjalankan pemeriksaan seluruh proyek, sedangkan skrip mulai mengeksekusi titik masuk lewat Node — pola node src/utama.ts bekerja karena Node 24 menjalankan berkas .ts secara langsung. Kedua skrip mengandaikan typescript sudah terpasang sebagai devDependency sehingga perintah tsc tersedia di node_modules; di lingkungan Course ini padanannya adalah pemanggilan npx --yes [email protected] seperti di atas.

Memeriksa dan Menjalankan Proyek #

Saatnya mengisi src/ dengan kode. Module barang.ts mendefinisikan bentuk data Barang beserta satu function pengolah:

ts
export interface Barang {
  nama: string;
  harga: number;
}

export function formatRupiah(harga: number): string {
  return `Rp${harga.toLocaleString("id-ID")}`;
}

Titik masuk utama.ts mengimpor keduanya — perhatikan penulisan import type untuk bentuk data yang murni tipe:

ts
import type { Barang } from "./barang.ts";
import { formatRupiah } from "./barang.ts";

const pensil: Barang = { nama: "Pensil", harga: 2500 };

console.log(`${pensil.nama}: ${formatRupiah(pensil.harga)}`);

Jalankan titik masuk tersebut dengan node src/utama.ts:

Output yang Dihasilkan:

text
Pensil: Rp2.500

Mari kita telaah isi utama.ts:

  1. Baris 1 (import type { Barang } ...): kata kunci type menandakan yang diimpor murni bentuk data, bukan nilai saat runtime. Node menghapus import ini saat menanggalkan tipe — tanpa type, import justru gagal saat dimuat karena tidak ada nilai Barang untuk diekspor.
  2. Baris 2 (import { formatRupiah } ...): function adalah nilai nyata yang dibutuhkan saat runtime, sehingga diimpor tanpa type.
  3. Baris 4 (const pensil: Barang = ...): anotasi : Barang membuat compiler memeriksa object ini — nama properti dan tipe nilainya harus sesuai interface.
  4. Baris 6 (console.log(...)): template literal merangkai nama barang dan harga terformat id-ID.

Sekarang periksa seluruh proyek dengan satu perintah yang membaca tsconfig.json:

bash
npx --yes [email protected] -p tsconfig.json
echo kode keluar: $?

Output yang Dihasilkan:

text
kode keluar: 0

Tidak ada pesan error dan kode keluar 0 — seluruh berkas pada pola src/**/*.ts lulus pemeriksaan. Pada proyek Anda sendiri, perintah yang setara adalah npm run typecheck setelah typescript terpasang. Coba ubah salah satu tipe di utama.ts — misalnya harga: 2500 diganti harga: "2500" — lalu jalankan pemeriksaan ulang; compiler akan menunjuk barisnya dengan pesan TS2322 sebagaimana kita saksikan pada chapter sebelumnya.

Kerangka proyek kita kini lengkap: package.json menentukan format module dan skrip, tsconfig.json mengatur perilaku compiler, dan folder src/ menampung kode yang lulus pemeriksaan. Dari fondasi ini kita masuk ke materi inti bahasanya — anotasi tipe, union, hingga unknown — pada chapter Tipe Dasar TypeScript.