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 — strict kini true secara default, module default esnext, dan types default [] (paket @types/* tidak lagi di-include otomatis). Opsi seperti moduleResolution: "node" (node10) dan baseUrl sebagai 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"]
}
OpsiFungsiRekomendasi
strictMengaktifkan keluarga pengecekan ketat (strictNullChecks, noImplicitAny, dll.)Selalu true
noUncheckedIndexedAccessarr[i] & obj[key] bertipe T | undefinedAktifkan untuk proyek baru
targetVersi JS output (syntax yang di-downlevel)es2022+ untuk runtime modern
moduleFormat modul outputesnext (bundler) / nodenext (Node) / preserve
moduleResolutionCara mencari file dari importbundler (Vite/webpack) / nodenext (Node)
verbatimModuleSyntaxImport tipe wajib import typetrue — aman untuk tool yang memproses per file
isolatedModulesPastikan tiap file bisa di-transpile sendiritrue bila memakai esbuild/SWC/Vite
skipLibCheckLewati cek tipe .d.ts di dependencytrue untuk mempercepat build
noEmitHanya cek tipe, tanpa outputtrue jika bundler yang menghasilkan JS
pathsAlias 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 / bundlerCara mendaftarkan alias
Viteresolve.alias di vite.config.ts (atau plugin vite-tsconfig-paths)
webpackresolve.alias (lihat setup webpack )
Node.js murniField "imports" di package.json ("#/*": "./src/*")
VitestMembaca 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:

CaraPerintahCatatan
Compile lalu jalankantsc && node dist/index.jsPaling standar untuk produksi
Type stripping Nodenode src/index.tsNode 22.18+/23.6+; tidak cek tipe, tidak mendukung enum/namespace (aktifkan erasableSyntaxOnly agar tsc memperingatkan)
Runner pihak ketiganpx tsx src/index.tsNyaman 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

  1. Mengira paths bekerja di runtime. Hasilnya Cannot find module '@/utils' saat dijalankan. Daftarkan alias di bundler/package.json#imports juga.
  2. Salah pasangan module & moduleResolution. nodenext untuk kode Node yang dijalankan langsung; bundler untuk kode yang melewati bundler. Mencampur keduanya membuat import yang lolos cek tapi gagal di runtime.
  3. Lupa ekstensi .js di Node ESM. Error ERR_MODULE_NOT_FOUND meskipun tsc tidak protes.
  4. Menganggap vite build sukses = bebas error tipe. Selalu jalankan tsc di CI.
  5. Mematikan strict karena banyak error. Lebih baik aktifkan dan pakai // @ts-expect-error sementara di titik tertentu daripada kehilangan jaminan di seluruh proyek.
  6. skipLibCheck: false di proyek besar membuat type-check sangat lambat karena menelusuri semua .d.ts di node_modules.
  7. Tidak mencantumkan types. Sejak TS 6, @types/node tidak otomatis dimuat; tambahkan "types": ["node"] bila process/Buffer tidak dikenali.

Ringkasan

  • strict: true adalah fondasi; tambahkan noUncheckedIndexedAccess untuk keamanan ekstra.
  • bundler untuk proyek Vite/webpack, nodenext untuk Node — dan ingat aturan ekstensi .js.
  • paths hanya untuk tipe; alias runtime diatur di tool lain.
  • Bundler tidak mengecek tipe, jadi tsc --noEmit (atau tsc -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.

Comments