Menu

Module di TypeScript: Import, Export, dan import type

Setiap file TypeScript yang punya import atau export di level teratas adalah sebuah module. Pelajari named export dan default export, import type dan export type, bagaimana pengaturan module menentukan output ES module atau CommonJS, dan kenapa node16 serta nodenext meminta ekstensi .js di import.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

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);
BentukYang 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: true mempertahankan setiap import yang tidak ditandai type, 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 .ts langsung 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 dengan SyntaxError: 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 di node_modules).
  • Package JavaScript tanpa tipe bawaan: instal @types/{package} kalau ada, atau tulis declaration file untuknya.
  • Subpath yang tidak dicantumkan package di field exports pada package.json-nya. Dengan resolution node16, nodenext, dan bundler, 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:

FileFormat output
.ts di package dengan "type": "module"ES module
.ts di package tanpa pengaturan ituCommonJS
.mtsselalu ES module, dihasilkan sebagai .mjs
.ctsselalu 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" (dengan module bernilai esnext, preserve, atau commonjs) menerima ./math tanpa ekstensi, karena bundler yang akan me-resolve-nya.
  • rewriteRelativeImportExtensions: true memungkinkanmu menulis ./math.ts, path yang sama yang bekerja saat Node menjalankan file .ts secara langsung, lalu menulis ulang path itu menjadi ./math.js di 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.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai