Module TypeScript adalah file yang punya setidaknya satu import atau export di level teratas. Semua yang dideklarasikan di dalamnya bersifat privat untuk file itu kecuali kamu meng-export-nya, dan file lain meng-import apa yang mereka butuhkan. Sintaksnya adalah sintaks ES module JavaScript ditambah beberapa bentuk khusus untuk tipe.
Named Export dan Default Export
Sebuah proyek terdiri dari banyak file, jadi sisi yang meng-import terlihat seperti ini. Default export di-import tanpa kurung kurawal dan boleh diberi nama apa saja; named export ditulis di dalam kurung kurawal dan mempertahankan namanya kecuali kamu menggantinya dengan as.
// main.ts
import describe, { distance, ORIGIN, type Point } from "./math.js";
import { distance as dist } from "./math.js"; // renamed on import
import * as math from "./math.js"; // everything, as one object
const p: Point = { x: 6, y: 8 };
console.log(describe(p), distance(ORIGIN, p), dist(p, p), math.ORIGIN);
| Bentuk | Yang di-import |
|---|---|
import { a, b } from "./m.js" | named export a dan b |
import x from "./m.js" | default export, dengan nama x |
import * as m from "./m.js" | namespace object yang berisi setiap export |
import { a as b } from "./m.js" | a, diganti namanya menjadi b di file ini |
import type { T } from "./m.js" | hanya tipe, dihapus dari output |
import "./setup.js" | menjalankan file itu demi efek sampingnya |
export { a } from "./m.js" | meng-export ulang a tanpa meng-import-nya |
export * from "./m.js" | meng-export ulang setiap named export |
Re-export memungkinkan satu file (sering kali index.ts) mengumpulkan API publik sebuah folder. Perilaku module JavaScript biasa, seperti live binding dan cache module, dibahas di ES module.
import type dan export type
Tipe tidak ada saat runtime, jadi import yang hanya dipakai sebagai tipe tidak punya apa pun untuk dimuat. import type menyatakannya secara eksplisit, dan pernyataan itu hilang dari output JavaScript. Modifier type juga bisa dipakai pada satu nama di dalam import biasa.
import type { User } from "./models.js"; // whole statement erased
import { saveUser, type Settings } from "./api.js"; // only saveUser survives
export type { User }; // re-export a type only
export type UserId = User["id"];
Tanpa kata kunci itu, TypeScript tetap membuang nama yang terlihat jelas hanya berupa tipe. Ada dua pengaturan yang membuat kata kunci ini wajib:
verbatimModuleSyntax: truemempertahankan setiap import yang tidak ditandaitype, jadi import tipe yang tidak ditandai menjadi error TS1484:'Point' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.- Menjalankan file
.tslangsung di Node (type stripping) membuang anotasi tipe tetapi tidak melihat file lain.import { Point }yang tidak ditandai tetap ada di kode, dan Node gagal saat runtime denganSyntaxError: The requested module './math.ts' does not provide an export named 'Point'.
Menulis type pada setiap import yang hanya berisi tipe bekerja di kedua kasus, jadi itulah kebiasaan yang perlu dibangun.
Cannot Find Module
Kalau path di sebuah import tidak mengarah ke file atau package yang punya tipe, compiler berhenti dengan error TS2307. Jalankan contoh ini untuk melihatnya:
Output-nya adalah index.ts(2,29): error TS2307: Cannot find module './utils.js' or its corresponding type declarations. Penyebab yang umum:
- Salah ketik di path relatif, atau
./yang hilang (tanpanya, nama itu dicari dinode_modules). - Package JavaScript tanpa tipe bawaan: instal
@types/{package}kalau ada, atau tulis declaration file untuknya. - Subpath yang tidak dicantumkan package di field
exportspadapackage.json-nya. Dengan resolutionnode16,nodenext, danbundler, hanya entry point yang tercantum yang bisa di-import.
Output ES Module atau CommonJS
Kamu selalu menulis import dan export. Opsi module di tsconfig.json menentukan JavaScript seperti apa yang dihasilkan.
// Source, the same in both cases
import { add } from "./math.js";
console.log(add(1, 2));
// module: nodenext, in a package with "type": "module"
import { add } from "./math.js";
console.log(add(1, 2));
// module: nodenext, in a package without "type": "module"
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const math_js_1 = require("./math.js");
console.log((0, math_js_1.add)(1, 2));
Dengan node16, node18, node20, atau nodenext, TypeScript mengikuti aturan Node sendiri untuk setiap file:
| File | Format output |
|---|---|
.ts di package dengan "type": "module" | ES module |
.ts di package tanpa pengaturan itu | CommonJS |
.mts | selalu ES module, dihasilkan sebagai .mjs |
.cts | selalu CommonJS, dihasilkan sebagai .cjs |
Dengan module: "esnext" atau "preserve", output-nya mempertahankan import/export, yang memang diharapkan bundler seperti Vite dan esbuild. Perbedaan kedua sistem module saat runtime ada di CommonJS vs ESM.
Ekstensi File di Import
Dengan module: node16 atau nodenext, file ES module harus menyebut file yang benar-benar akan dimuat Node, yaitu file .js hasil kompilasi. TypeScript memetakan ./math.js kembali ke math.ts untuk pemeriksaan tipe.
import { add } from "./math"; // error TS2835 in an ES module file
import { add } from "./math.js"; // correct: the path as it exists after compiling
Teks error-nya adalah Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './math.js'? File CommonJS dengan pengaturan yang sama boleh menghilangkan ekstensi, karena require mencoba ekstensinya sendiri.
Ada dua konfigurasi lain yang mengubah aturan ini:
moduleResolution: "bundler"(denganmodulebernilaiesnext,preserve, ataucommonjs) menerima./mathtanpa ekstensi, karena bundler yang akan me-resolve-nya.rewriteRelativeImportExtensions: truememungkinkanmu menulis./math.ts, path yang sama yang bekerja saat Node menjalankan file.tssecara langsung, lalu menulis ulang path itu menjadi./math.jsdi output.
Module Resolution dan paths
Untuk nama polos seperti "zod", TypeScript mencari di node_modules, membaca package.json milik package itu (field exports dan types), lalu kembali ke node_modules/@types/zod kalau tidak ketemu. Nama relatif (./, ../) di-resolve dari file yang meng-import. File .json juga bisa di-import; pengaturan yang dibutuhkan ada di halaman JSON.
paths di tsconfig.json menambahkan alias untuk folder-foldermu sendiri:
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"paths": {
"@lib/*": ["./src/lib/*"]
}
}
}
paths hanya memengaruhi pemeriksaan tipe. File yang dihasilkan tetap bertuliskan import { v } from "@lib/util", jadi sesuatu yang lain harus me-resolve-nya saat runtime: bundler yang dikonfigurasi dengan alias yang sama, atau field imports milik Node di package.json (alias yang diawali #, seperti "#lib/*": "./dist/lib/*"), yang bekerja tanpa tool tambahan apa pun. baseUrl sudah dihapus di TypeScript 7 (error TS5102); tulis entri paths relatif terhadap tsconfig.json dengan awalan ./.
Pertanyaan yang sering diajukan
Apa perbedaan import dan import type di TypeScript?
import type { User } from "./user.js" hanya bisa membawa masuk tipe, dan seluruh pernyataannya dihapus dari output JavaScript. import biasa bisa membawa masuk nilai dan tipe; TypeScript membuang nama-nama yang hanya dipakai sebagai tipe, tetapi dengan verbatimModuleSyntax kamu wajib menandainya dengan type agar output-nya persis seperti yang kamu tulis.
Kenapa TypeScript meminta .js di path import?
Dengan module: node16 atau nodenext, file ES module harus meng-import dengan nama file sebenarnya yang akan dimuat Node saat runtime, dan file itu adalah .js hasil kompilasi. TypeScript me-resolve ./math.js ke math.ts saat memeriksa tipe. Menghilangkan ekstensi di file ES module adalah error TS2835.
Sebaiknya pakai export default atau named export di TypeScript?
Keduanya bisa. Banyak tim lebih suka named export: namanya sama di setiap file yang meng-import-nya, editor bisa melakukan auto-import dengan andal, dan mengganti nama menjadi refactor, bukan pencarian manual. Default export membuat setiap file yang meng-import bisa memilih namanya sendiri.
Apakah opsi paths di tsconfig mengubah import di output?
Tidak. paths hanya memberi tahu type checker tempat menemukan sebuah module. JavaScript yang dihasilkan tetap menyimpan "@lib/util" seperti yang ditulis, jadi bundler, atau field imports milik Node di package.json, harus me-resolve-nya saat runtime.
Bagaimana cara memperbaiki "Cannot find module" di TypeScript?
Error TS2307 berarti path-nya tidak menunjuk ke file yang bisa ditemukan TypeScript, atau sebuah package tidak menyertakan tipe. Periksa path relatif dan ekstensinya, instal package @types/... untuk package itu kalau tidak punya tipe bawaan, atau tulis declaration file kecil untuknya.