September 28, 2026
TypeScript di Proyek Nyata.
Konfigurasi tsconfig yang penting (strict, module, moduleResolution, paths), setup TypeScript untuk React + Vite dan Node.js, serta menjalankan type-checking di CI dengan GitHub Actions.
Menulis tipe hanyalah separuh cerita. Di proyek nyata, perilaku TypeScript sangat ditentukan oleh tsconfig.json: seberapa ketat pengecekan, bagaimana import di-resolve, dan apakah tsc menghasilkan JavaScript atau hanya mengecek. Catatan ini fokus pada opsi yang benar-benar berdampak, plus setup untuk React, Node.js, dan CI.
Catatan versi: sejak TypeScript 6.0, beberapa default berubah —
strictkinitruesecara default,moduledefaultesnext, dantypesdefault[](paket@types/*tidak lagi di-include otomatis). Opsi sepertimoduleResolution: "node"(node10) danbaseUrlsebagai root resolusi sudah deprecated dan direncanakan dihapus di TypeScript 7 (compiler native berbasis Go). Tetap tulis opsi penting secara eksplisit agar konfigurasi jelas di semua versi.
1. Opsi tsconfig yang Paling Penting
{
"compilerOptions": {
"target": "es2022",
"module": "esnext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"skipLibCheck": true,
"noEmit": true
},
"include": ["src"]
}
| Opsi | Fungsi | Rekomendasi |
|---|---|---|
strict | Mengaktifkan keluarga pengecekan ketat (strictNullChecks, noImplicitAny, dll.) | Selalu true |
noUncheckedIndexedAccess | arr[i] & obj[key] bertipe T | undefined | Aktifkan untuk proyek baru |
target | Versi JS output (syntax yang di-downlevel) | es2022+ untuk runtime modern |
module | Format modul output | esnext (bundler) / nodenext (Node) / preserve |
moduleResolution | Cara mencari file dari import | bundler (Vite/webpack) / nodenext (Node) |
verbatimModuleSyntax | Import tipe wajib import type | true — aman untuk tool yang memproses per file |
isolatedModules | Pastikan tiap file bisa di-transpile sendiri | true bila memakai esbuild/SWC/Vite |
skipLibCheck | Lewati cek tipe .d.ts di dependency | true untuk mempercepat build |
noEmit | Hanya cek tipe, tanpa output | true jika bundler yang menghasilkan JS |
paths | Alias import (@/components) | Lihat bagian berikut |
strict itu wajib
Tanpa strictNullChecks, null dan undefined bisa masuk ke tipe apa pun — sumber bug nomor satu di JavaScript. Untuk proyek lama, aktifkan strict lalu perbaiki bertahap; jangan matikan permanen.
noUncheckedIndexedAccess dalam praktik
const warna: Record<string, string> = { merah: "#f00" };
const h = warna["hijau"]; // string | undefined (dengan opsi aktif)
h.toUpperCase(); // ❌ Object is possibly 'undefined'
h?.toUpperCase(); // ✅
2. paths: Alias Import
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
Sekarang import { Button } from "@/components/Button" dikenali editor dan tsc.
Penting: paths hanya memengaruhi pengecekan tipe. tsc tidak menulis ulang path di output, dan runtime tidak membaca tsconfig. Alias yang sama harus dikonfigurasi di tool yang menjalankan kode:
| Runtime / bundler | Cara mendaftarkan alias |
|---|---|
| Vite | resolve.alias di vite.config.ts (atau plugin vite-tsconfig-paths) |
| webpack | resolve.alias (lihat setup webpack
) |
| Node.js murni | Field "imports" di package.json ("#/*": "./src/*") |
| Vitest | Membaca vite.config.ts, jadi ikut resolve.alias |
paths tidak lagi membutuhkan baseUrl — tulis path relatif terhadap lokasi tsconfig.json.
3. TypeScript dengan React (Vite)
npm create vite@latest my-app -- --template react-ts
cd my-app && npm install
Template Vite memakai project references: tsconfig.json merujuk ke tsconfig.app.json (kode browser) dan tsconfig.node.json (file config seperti vite.config.ts). Opsi kuncinya di tsconfig.app.json:
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
"strict": true
}
}
Contoh komponen bertipe:
import type { ReactNode } from "react";
import { useState } from "react";
type ButtonProps = {
variant?: "primary" | "ghost";
onClick?: () => void;
children: ReactNode;
};
export function Button({ variant = "primary", onClick, children }: ButtonProps) {
return (
<button className={`btn btn-${variant}`} onClick={onClick}>
{children}
</button>
);
}
type Todo = { id: number; teks: string; selesai: boolean };
export function TodoList() {
const [todos, setTodos] = useState<Todo[]>([]); // generic eksplisit untuk array kosong
const tambah = (teks: string) =>
setTodos((t) => [...t, { id: Date.now(), teks, selesai: false }]);
return <Button onClick={() => tambah("Baru")}>Tambah ({todos.length})</Button>;
}
Poin penting: tipe props sebagai type/interface, useState<T[]>([]) untuk state awal kosong, dan event handler seperti React.ChangeEvent<HTMLInputElement>. Dasar komponennya ada di catatan Cara Menggunakan React
.
4. TypeScript dengan Node.js
Untuk backend (mis. Express.js ) yang dijalankan langsung oleh Node, gunakan resolusi yang meniru Node:
{
"compilerOptions": {
"target": "es2023",
"module": "nodenext",
"moduleResolution": "nodenext",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"verbatimModuleSyntax": true,
"types": ["node"]
},
"include": ["src"]
}
npm install -D typescript @types/node
Hal yang sering mengejutkan dengan nodenext + "type": "module": import relatif wajib pakai ekstensi .js (bukan .ts), karena itulah nama file setelah dikompilasi.
// src/index.ts
import { hitungTotal } from "./utils/harga.js"; // ✅ walau file aslinya harga.ts
Tiga cara menjalankan:
| Cara | Perintah | Catatan |
|---|---|---|
| Compile lalu jalankan | tsc && node dist/index.js | Paling standar untuk produksi |
| Type stripping Node | node src/index.ts | Node 22.18+/23.6+; tidak cek tipe, tidak mendukung enum/namespace (aktifkan erasableSyntaxOnly agar tsc memperingatkan) |
| Runner pihak ketiga | npx tsx src/index.ts | Nyaman untuk dev & script |
5. Type-checking di CI
Bundler seperti Vite dan esbuild tidak mengecek tipe — mereka hanya membuang anotasi. Artinya build bisa sukses walau ada error tipe. Jadikan tsc langkah wajib:
{
"scripts": {
"typecheck": "tsc --noEmit",
"build": "npm run typecheck && vite build"
}
}
Untuk proyek dengan project references (template Vite), gunakan tsc -b alih-alih tsc --noEmit.
GitHub Actions minimal (.github/workflows/ci.yml):
name: CI
on: [push, pull_request]
jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run typecheck
- run: npm test --if-present
Kombinasikan dengan unit test agar CI menjaga dua hal: tipe benar dan perilaku benar.
Kesalahan Umum
- Mengira
pathsbekerja di runtime. HasilnyaCannot find module '@/utils'saat dijalankan. Daftarkan alias di bundler/package.json#importsjuga. - Salah pasangan
module&moduleResolution.nodenextuntuk kode Node yang dijalankan langsung;bundleruntuk kode yang melewati bundler. Mencampur keduanya membuat import yang lolos cek tapi gagal di runtime. - Lupa ekstensi
.jsdi Node ESM. ErrorERR_MODULE_NOT_FOUNDmeskipuntsctidak protes. - Menganggap
vite buildsukses = bebas error tipe. Selalu jalankantscdi CI. - Mematikan
strictkarena banyak error. Lebih baik aktifkan dan pakai// @ts-expect-errorsementara di titik tertentu daripada kehilangan jaminan di seluruh proyek. skipLibCheck: falsedi proyek besar membuat type-check sangat lambat karena menelusuri semua.d.tsdinode_modules.- Tidak mencantumkan
types. Sejak TS 6,@types/nodetidak otomatis dimuat; tambahkan"types": ["node"]bilaprocess/Buffertidak dikenali.
Ringkasan
strict: trueadalah fondasi; tambahkannoUncheckedIndexedAccessuntuk keamanan ekstra.bundleruntuk proyek Vite/webpack,nodenextuntuk Node — dan ingat aturan ekstensi.js.pathshanya untuk tipe; alias runtime diatur di tool lain.- Bundler tidak mengecek tipe, jadi
tsc --noEmit(atautsc -b) wajib ada di CI.

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.