Menu

Declare TypeScript: Penjelasan File Deklarasi .d.ts

File .d.ts mendeskripsikan tipe kode JavaScript tanpa berisi kodenya sama sekali, dan kata kunci declare melakukan hal yang sama di dalam file .ts. Pelajari cara file deklarasi dihasilkan, posisi package @types, cara memberi tipe pada modul tanpa tipe, serta bagaimana declare global dan module augmentation memperluas tipe yang sudah ada.

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

File deklarasi (.d.ts) hanya berisi tipe: signature, interface, dan bentuk class untuk JavaScript yang berada di tempat lain. Kata kunci declare melakukan pekerjaan yang sama di dalam file biasa. Kata kunci ini memberi tahu compiler "ini ada saat runtime, percayalah", dan tidak menghasilkan kode apa pun.

Compiler menerima __APP_VERSION__.length karena deklarasinya menyatakan bahwa itu string. Saat runtime, variabel itu tidak ada, jadi membaca .length melempar ReferenceError: __APP_VERSION__ is not defined, exception saat runtime yang sama sekali tidak diduga compiler. Itulah kontrak setiap deklarasi: tipenya hanya sebenar JavaScript di belakangnya.

Apa Isi File .d.ts

Mengompilasi dengan declaration: true menunjukkan idenya dengan paling jelas. Dari source ini:

// price.ts
export interface LineItem {
    name: string;
    price: number;
    qty: number;
}

const TAX = 0.2;

export function total(items: LineItem[]) {
    const sum = items.reduce((acc, item) => acc + item.price * item.qty, 0);
    return Math.round(sum * (1 + TAX) * 100) / 100;
}

export class Cart {
    private items: LineItem[] = [];
    add(item: LineItem) {
        this.items.push(item);
        return this;
    }
}

tsc menulis price.js dan price.d.ts berikut:

export interface LineItem {
    name: string;
    price: number;
    qty: number;
}
export declare function total(items: LineItem[]): number;
export declare class Cart {
    private items;
    add(item: LineItem): this;
}

Badan fungsi hilang, tipe kembalian hasil inferensi ditulis lengkap (number, this), field private tetap punya nama tetapi kehilangan tipenya, dan TAX yang tidak di-export sama sekali tidak ada. Sebuah library memublikasikan .js untuk Node dan .d.ts untuk editor dan compiler-mu. emitDeclarationOnly: true hanya menghasilkan file .d.ts, untuk proyek yang JavaScript-nya dibangun oleh bundler.

Objek bawaan ES2022 yang kamu pakai setiap hari juga berasal dari file deklarasi: lib.es2022.d.ts dan kawan-kawannya ikut terpasang bersama TypeScript dan dipilih berdasarkan target dan lib.

Bentuk-bentuk declare

Setiap pernyataan declare mendeskripsikan sesuatu yang sudah ada. Di file .d.ts tanpa import atau export (file deklarasi global), setiap deklarasi berikut terlihat oleh seluruh proyek:

// globals.d.ts
declare const API_URL: string;                     // a global constant
declare let debugMode: boolean;                    // a global variable
declare function track(event: string, props?: Record<string, string>): void;
declare class Widget {                             // a class from a script tag
    constructor(el: string);
    render(): void;
}
declare namespace Analytics {                      // a global object
    function page(name: string): void;
}
declare module "legacy-charts" {                   // a module you import
    export function draw(data: number[]): void;
}

Di dalam file .d.ts, interface dan type tidak butuh declare; deklarasi top-level lain tanpa declare atau export menghasilkan error TS1046. Begitu sebuah file punya import atau export, deklarasinya bersifat lokal untuk file itu, dan tambahan ke scope global ditaruh di blok declare global (ditunjukkan di bawah). Signature yang dideklarasikan adalah acuan compiler saat memeriksa pemanggilan:

Compiler melaporkan index.ts(5,21): error TS2322: Type 'number' is not assignable to type 'string'. Kirim { items: "3" }, atau ubah deklarasinya kalau fungsi aslinya memang menerima angka.

Package @types

Banyak package npm menyertakan file .d.ts sendiri, yang dirujuk dari field types (atau kondisi types di exports) pada package.json mereka. Untuk package yang hanya berisi JavaScript, proyek DefinitelyTyped yang dikelola komunitas memublikasikan tipe di bawah scope @types:

npm i lodash
npm i -D @types/lodash

Saat kamu meng-import sebuah package, TypeScript otomatis mencari node_modules/@types/{name}. Tipe yang mendeskripsikan global, bukan sesuatu yang di-import, seperti @types/node (process, Buffer) atau describe dan it milik test runner, harus dicantumkan di tsconfig.json:

{
    "compilerOptions": {
        "types": ["node"]
    }
}

Tanpa entri itu, TypeScript 7 tidak memuatnya, bahkan saat package-nya sudah terpasang:

error TS2591: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.

tsc --init menulis "types": [] ke config baru, dengan komentar yang menyarankan ["node"] untuk proyek Node.

Memberi Tipe pada Modul Tanpa Tipe

Meng-import package JavaScript yang tidak punya tipe, baik bawaan maupun di @types, menghasilkan error TS7016:

error TS7016: Could not find a declaration file for module 'fakelib'. '/project/node_modules/fakelib/index.js' implicitly has an 'any' type.

Perbaiki dengan file .d.ts di mana pun dalam proyekmu (folder types/ adalah pilihan umum; folder itu hanya perlu tercakup oleh include) yang tidak punya import atau export di top level. Deskripsikan bagian yang kamu pakai:

// types/fakelib.d.ts
declare module "fakelib" {
    export function hi(name: string): string;
    export const version: string;
}

// Non-code files a bundler lets you import
declare module "*.svg" {
    const url: string;
    export default url;
}

Versi terpendeknya, declare module "fakelib"; dalam satu baris, membuat setiap import dari package itu bertipe any. Cara ini menghilangkan error-nya sekaligus semua pemeriksaannya, jadi anggap sebagai langkah sementara.

declare global

Kode yang menambahkan sesuatu ke scope global, seperti polyfill, method baru pada objek bawaan, atau global yang diatur oleh tag script, butuh tipe yang sesuai. declare global menambahkannya. Blok ini harus berada di dalam modul (file dengan import atau export), itulah sebabnya export {} ada di bagian atas:

interface Array<T> digabung dengan interface Array bawaan, bukan menggantikannya, dan var (bukan let atau const) adalah yang menambahkan property ke globalThis. Memperluas prototype objek bawaan berisiko di kode yang dipakai bersama; teknik declare global yang sama adalah cara proyek menambahkan variabel mereka sendiri ke process.env di interface NodeJS.ProcessEnv milik @types/node.

Module Augmentation

Untuk menambah tipe sebuah package yang kamu import, deklarasikan ulang nama modulnya dan buka kembali interface-nya. File itu sendiri harus berupa modul (import sudah membuatnya begitu; export {} juga bisa). Tanpa import atau export, blok yang sama justru mendeklarasikan modul config-lib yang benar-benar baru dan menyembunyikan tipe asli package itu:

// types/config-lib.d.ts
import "config-lib";

declare module "config-lib" {
    interface Settings {
        beta: boolean; // merged into the package's own Settings interface
    }
}

Setelah ini, load().beta dari config-lib bertipe boolean di mana-mana. Interface bisa digabung; type alias tidak, jadi sebuah library harus meng-export interface agar cara ini berhasil. Beginilah plugin menambahkan field ke objek request atau config milik sebuah framework.

skipLibCheck

skipLibCheck: true menghentikan compiler dari memeriksa tipe file .d.ts, termasuk yang ada di node_modules. Kodemu sendiri tetap diperiksa terhadap file-file itu. Opsi ini menghemat waktu dan menghindari error dari dua package yang deklarasinya saling bertentangan, itulah sebabnya tsc --init menyalakannya. Konsekuensinya, kesalahan di dalam file .d.ts milikmu sendiri juga tidak dilaporkan.

Pertanyaan yang sering diajukan

Apa itu file .d.ts di TypeScript?

File deklarasi: isinya hanya tipe (signature fungsi, interface, bentuk class) tanpa implementasi. File ini mendeskripsikan JavaScript yang ada di tempat lain, seperti library yang sudah dikompilasi atau API bawaan browser, sehingga TypeScript bisa memeriksa kode yang memakainya. tsc tidak pernah menghasilkan JavaScript untuk file ini.

Apa fungsi kata kunci declare di TypeScript?

Kata kunci ini memberi tahu compiler bahwa sebuah nilai ada saat runtime tanpa membuatnya. declare const VERSION: string; membuatmu bisa memakai VERSION sebagai string, dan baris itu hilang dari output. Kalau tidak ada yang benar-benar mendefinisikan VERSION, program gagal saat runtime dengan ReferenceError.

Bagaimana cara memperbaiki "Could not find a declaration file for module"?

Error TS7016 berarti package itu tidak punya tipe. Pasang tipenya kalau tersedia (npm i -D @types/package-name), atau tambahkan file .d.ts berisi declare module "package-name" { ... } yang mendeskripsikan bagian yang kamu pakai. declare module "package-name"; saja sudah membungkam error itu dengan memberi tipe any pada seluruh modul.

Bagaimana cara menghasilkan file .d.ts dari TypeScript?

Atur "declaration": true di tsconfig.json (atau kirim --declaration). Setiap file .ts lalu menghasilkan .d.ts di samping .js-nya. Tambahkan emitDeclarationOnly kalau ada alat lain yang membangun JavaScript-nya, dan declarationMap supaya editor bisa melompat dari tipe ke source-mu.

Apa bedanya declare global dan declare module?

declare global { ... } menambahkan sesuatu ke scope global, misalnya property baru pada Array atau variabel global. declare module "name" { ... } mendeskripsikan modul yang kamu import dengan nama itu. declare global harus berada di dalam modul (file dengan import atau export); declare module mendeklarasikan modul baru di file tanpa import atau export, dan meng-augment modul yang sudah ada kalau berada di dalam modul.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai