ข้ามไปเนื้อหาหลัก

Category: reference

TypeScript Module Augmentation & Declaration Merging

เพิ่ม types ให้ third-party modules, extend interface ที่มีอยู่, และสร้าง .d.ts files สำหรับ JavaScript libraries

· อ่านประมาณ 3 นาที

สารบัญ

Declaration Merging คืออะไร

TypeScript อนุญาตให้ประกาศ interface หรือ namespace เดียวกันหลายครั้ง แล้วจะ merge เข้าด้วยกันอัตโนมัติ — ทำให้เราเพิ่ม properties ให้ existing types ได้

// ✓ Interface merging — ประกาศ interface ชื่อเดียวกัน 2 ครั้ง
interface User {
  id: number;
  name: string;
}

interface User {
  email: string;  // เพิ่ม property ใหม่
}

// ผลลัพธ์: User มีทั้ง id, name, email
const user: User = { id: 1, name: 'Alice', email: 'alice@example.com' };

Module Augmentation — เพิ่ม Types ให้ Library

// เพิ่ม method ให้ Express Request object
// src/types/express.d.ts

import 'express';  // ต้อง import ก่อนเสมอ (ทำให้ file เป็น module)

declare module 'express' {
  interface Request {
    user?: {
      id: string;
      email: string;
      role: 'admin' | 'user';
    };
    requestId?: string;
  }
}
// ตอนใช้งาน — TypeScript รู้จัก req.user แล้ว
app.get('/profile', (req, res) => {
  if (!req.user) return res.status(401).send('Unauthorized');
  res.json({ email: req.user.email });  // ✓ type-safe
});

Augment Node.js ProcessEnv

// src/types/env.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: 'development' | 'production' | 'test';
    DATABASE_URL: string;
    PORT?: string;
    JWT_SECRET: string;
  }
}
// ตอนใช้งาน
process.env.NODE_ENV  // 'development' | 'production' | 'test'
process.env.DATABASE_URL  // string (ไม่ใช่ string | undefined)
process.env.PORT  // string | undefined (optional)

Augment Window Object

// src/types/window.d.ts
export {};  // ทำให้ file เป็น module

declare global {
  interface Window {
    analytics: {
      track(event: string, properties?: Record<string, unknown>): void;
    };
    __APP_VERSION__: string;
  }
}
// ใช้งาน
window.analytics.track('page_view', { path: '/about' });
window.__APP_VERSION__  // string

สร้าง .d.ts สำหรับ JavaScript Library ที่ไม่มี Types

// types/my-legacy-lib.d.ts
declare module 'my-legacy-lib' {
  export interface Options {
    timeout?: number;
    retries?: number;
  }

  export function connect(url: string, options?: Options): Promise<void>;
  export function disconnect(): void;
  export function query<T>(sql: string, params?: unknown[]): Promise<T[]>;

  // Default export
  const client: {
    connect: typeof connect;
    disconnect: typeof disconnect;
    query: typeof query;
  };
  export default client;
}

Ambient Declarations — declare ไม่ได้ implement

// ประกาศ variable ที่มีอยู่ใน global scope (inject โดย bundler หรือ server)
declare const __DEV__: boolean;
declare const __VERSION__: string;
declare const __BUILD_TIME__: number;

// ใช้งาน
if (__DEV__) {
  console.log('Development mode');
}

// .d.ts สำหรับ CSS modules
declare module '*.module.css' {
  const classes: Record<string, string>;
  export default classes;
}

// .d.ts สำหรับ SVG imports (Vite)
declare module '*.svg' {
  const content: string;
  export default content;
}

// .d.ts สำหรับ image imports
declare module '*.png' {
  const url: string;
  export default url;
}

Namespace Merging

// Original function + namespace ชื่อเดียวกัน
function createStore<T>(initial: T) {
  return { state: initial };
}

// Merge namespace เข้ากับ function
namespace createStore {
  export interface Options {
    persist?: boolean;
    key?: string;
  }

  export function fromJSON<T>(json: string): ReturnType<typeof createStore<T>> {
    return createStore(JSON.parse(json) as T);
  }
}

// ใช้งาน
const store = createStore({ count: 0 });
const opts: createStore.Options = { persist: true };
const restored = createStore.fromJSON<{ count: number }>('{"count":5}');

tsconfig สำหรับ .d.ts files

// tsconfig.json
{
  "compilerOptions": {
    "typeRoots": ["./node_modules/@types", "./src/types"],
    // เพิ่ม src/types/ ให้ TypeScript หา .d.ts อัตโนมัติ
    "paths": {
      // ถ้าใช้ path aliases
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts"]
}

กฎสำคัญ

// ✓ file ที่มี import/export = module
// ต้อง declare global { ... } เมื่อต้องการ extend global
import 'some-module';
declare global {
  interface Window { myProp: string; }
}

// ✓ file ที่ไม่มี import/export = script (global scope)
// extend global ได้โดยตรง
interface Window { myProp: string; }

// ❌ ถ้าไม่ import module ก่อน — augmentation ไม่ทำงาน
declare module 'express' { /* ... */ }  // ไม่มี import 'express' → ไม่ merge

// ✓ ถูกต้อง
import 'express';
declare module 'express' {
  interface Request { user?: User; }
}

ตรวจสอบ Types ที่มีอยู่

# ดู types ที่ติดตั้งแล้ว
ls node_modules/@types/

# ติดตั้ง type definitions
npm install --save-dev @types/node @types/express

# ดู .d.ts ที่ library export (ถ้ามี built-in types)
cat node_modules/zod/lib/index.d.ts | head -30