TypeScriptの型定義ファイルdtsを自作する使い方 破綻しない設計の基本
ライブラリを配布する際、型定義ファイル(.d.ts)の設計ミスで利用者の環境を壊してしまった経験はないでしょうか。この記事では、TypeScriptの型定義ファイルを自作する際の基本から、配布しても破綻しない設計のコツまでを実務経験をもとに解説します。declare、augmentation、モジュール宣言といった基本構文を整理しつつ、型安全を保ちながらメンテナンス可能な.d.tsファイルを作成する判断基準を提示します。tsconfig.jsonとの連携や、グローバル汚染を避ける使い方についても具体例を交えて説明するため、初めて型定義ファイルを作る方から、既存ライブラリの型定義を改善したい実務者まで、判断材料として活用できる内容です。
検証環境
- OS: macOS 15.1 (Sequoia)
- Node.js: 24.12.0 LTS
- TypeScript: 5.9.3
- 主要パッケージ:
- @types/node: 22.10.5
- 検証日: 2025 年 12 月 29 日
背景
なぜ型定義ファイルが必要になるのか
TypeScriptは型安全なコードを書くための仕組みを提供していますが、JavaScriptで書かれた既存ライブラリや、外部APIとのやり取りでは型情報が失われます。このギャップを埋めるのが型定義ファイル(.d.ts)です。
実務では以下のようなケースで型定義ファイルを自作する必要が生じます。
- 社内共通ライブラリに型を付ける
- 既存のJavaScriptコードをTypeScript化する過程で段階的に型を追加する
- サードパーティライブラリの型定義が不完全、または存在しない
- グローバル変数やプロセス環境変数に型を付けたい
型定義ファイルを正しく設計できれば、コード補完やコンパイル時のエラー検出が効くようになり、開発効率と品質が大きく向上します。一方で、設計を誤るとグローバルスコープの汚染や型の競合が発生し、利用者の環境を壊してしまうリスクがあります。
mermaidflowchart LR
jsLib["JavaScriptライブラリ"] --> noType["型情報なし"]
noType --> problem["補完なし<br/>実行時エラー"]
dts["型定義ファイル<br/>.d.ts"] --> typed["型安全"]
typed --> benefit["補完あり<br/>コンパイル時検出"]
型定義ファイルがあることで、JavaScriptライブラリでもTypeScriptの恩恵を受けられるようになります。
課題
型定義ファイル設計で実際に起きた問題
実務で型定義ファイルを作成・配布する際、以下のような問題に直面しました。
グローバル汚染による型競合
社内ライブラリに型定義を追加した際、グローバルスコープにdeclareで型を宣言したところ、利用者のプロジェクト内で同名の型が定義されていたため、型エラーが大量に発生しました。tsconfig.jsonのtypes設定を正しく行わず、意図しない型定義が読み込まれていたことが原因でした。
モジュール宣言の不備によるインポートエラー
declare moduleを使って外部ライブラリの型を拡張しようとした際、モジュール解決が正しく行われず、Cannot find moduleエラーが発生しました。moduleResolutionやpathsの設定と、型定義ファイルの配置場所の関係を理解していなかったことが原因です。
augmentationの範囲制御ミス
既存の型に追加のプロパティを定義する際、declare globalとnamespaceの使い分けを誤り、意図しない範囲まで型が拡張されてしまいました。特に、ライブラリ配布時には利用者の環境に影響を与えないよう、慎重な設計が必要でした。
これらの問題を放置すると、以下のリスクがあります。
- 利用者のプロジェクトで予期しない型エラーが発生
- ビルドが通らなくなる
- 型推論が正しく働かず、型安全性が損なわれる
- メンテナンスコストが増大し、型定義の更新が困難になる
解決策と判断
破綻しない型定義ファイル設計の基本方針
実務経験から、以下の設計方針を採用することで、配布しても壊れにくい型定義ファイルを作成できるようになりました。
方針1: モジュール宣言を優先し、グローバル宣言は最小限にする
グローバルスコープに型を追加すると、利用者の環境と競合するリスクが高まります。可能な限りdeclare moduleを使い、モジュールとしてエクスポートする形式を採用します。
どうしてもグローバルに宣言が必要な場合(例: windowオブジェクトの拡張、プロセス環境変数の型付け)は、declare globalで明示的に範囲を限定します。
方針2: augmentationは必要最小限の範囲で行う
既存の型を拡張する際、namespaceやinterfaceのマージ機能を使いますが、拡張範囲を明確にすることが重要です。特に、サードパーティライブラリの型を拡張する場合は、利用者がオプトインできる設計にします。
方針3: tsconfig.jsonとの連携を意識する
型定義ファイルは、tsconfig.jsonのtypes、typeRoots、include、exclude設定と密接に関係します。これらの設定を理解し、意図しない型定義が読み込まれないようにします。
実際に採用した設計例を、次章で具体的に説明します。
採用しなかった案とその理由
案1: すべての型をグローバルに宣言する
初期検討では、使いやすさを優先してすべての型をdeclareでグローバルに宣言することも検討しました。しかし、以下の理由で採用を見送りました。
- 利用者のプロジェクトで型名が競合するリスクが高い
- どこで型が定義されているか追跡しづらく、メンテナンス性が低い
- TypeScriptのモジュールシステムの恩恵を受けられない
案2: 型定義をインラインで書く
型定義ファイルを作らず、実装ファイル内に型定義を含める方法も検討しました。これは小規模プロジェクトでは有効ですが、以下の理由で大規模には向きませんでした。
- 型定義と実装が混在し、可読性が下がる
- JavaScriptライブラリに後から型を追加する場合には使えない
- 型定義のみを配布・共有することができない
具体例
この章では、実際に動作確認済みのコード例を通じて、型定義ファイルの使い方と設計パターンを解説します。
declare による基本的な型宣言
グローバル変数への型付け
ブラウザ環境でwindowオブジェクトに独自のプロパティを追加する場合、以下のように型を定義します。
typescript// global.d.ts
declare global {
interface Window {
myAppConfig: {
apiEndpoint: string;
version: string;
};
}
}
export {};
ポイント:
declare globalでグローバルスコープへの追加を明示- ファイル末尾の
export {}は、このファイルをモジュールとして扱うために必要 WindowインターフェースはTypeScript標準の型定義に存在するため、マージされる
つまずきポイント:
export {}を忘れると、ファイル全体がグローバルスクリプトとして扱われ、他のモジュール宣言と競合する可能性があります。
Node.js環境変数への型付け
プロセス環境変数に型を付ける場合、以下のように定義します。
typescript// env.d.ts
declare global {
namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
API_KEY: string;
NODE_ENV: "development" | "production" | "test";
}
}
}
export {};
これにより、process.env.DATABASE_URLにアクセスする際に型補完が効き、存在しない環境変数にアクセスするとコンパイルエラーになります。
declare module によるモジュール型定義
外部ライブラリへの型付け
型定義が存在しないJavaScriptライブラリに型を付ける場合、以下のようにdeclare moduleを使います。
typescript// types/my-library.d.ts
declare module "my-library" {
export interface Config {
timeout: number;
retries: number;
}
export function initialize(config: Config): void;
export function getData(id: string): Promise<unknown>;
}
つまずきポイント:
- モジュール名は、
package.jsonのnameフィールドまたは実際のインポートパスと完全に一致させる必要があります。
ワイルドカードモジュール宣言
CSSモジュールや画像ファイルをインポートする際の型定義は、ワイルドカードを使います。
typescript// types/assets.d.ts
declare module "*.module.css" {
const classes: { [key: string]: string };
export default classes;
}
declare module "*.png" {
const src: string;
export default src;
}
これにより、import styles from './Button.module.css'のようなインポートが型エラーなく使えます。
augmentation による型拡張
既存インターフェースの拡張
サードパーティライブラリの型を拡張する場合、以下のようにインターフェースのマージ機能を使います。
typescript// types/express-augmentation.d.ts
import { User } from "./models/User";
declare module "express-serve-static-core" {
interface Request {
currentUser?: User;
}
}
実際に試したところ、この方法でExpressのRequestオブジェクトに独自プロパティを追加でき、ミドルウェアでreq.currentUserに型安全にアクセスできるようになりました。
mermaidflowchart TB
original["元の型定義<br/>interface Request"]
augment["拡張定義<br/>interface Request"]
merged["マージ後<br/>currentUser追加"]
original --> merged
augment --> merged
TypeScriptのインターフェースマージ機能により、同名のインターフェースは自動的に統合されます。
つまずきポイント:
- 拡張する型定義ファイルは、必ず元のモジュールをインポートしてから
declare moduleを使う必要があります。インポートを忘れると、型のマージが正しく行われません。
tsconfig.json との連携設計
型定義ファイルが意図した通りに読み込まれるよう、tsconfig.jsonの設定を適切に行います。
json{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": false,
"typeRoots": ["./node_modules/@types", "./types"],
"types": ["node"]
},
"include": ["src/**/*", "types/**/*"],
"exclude": ["node_modules", "dist"]
}
設定のポイント:
typeRoots: 型定義ファイルを探索するディレクトリを指定(デフォルトはnode_modules/@typesのみ)types: 明示的に読み込む型定義パッケージを指定(空配列にするとすべての@typesを除外)include: プロジェクトに含めるファイルパターン(型定義ディレクトリも含める)skipLibCheck: falseにすることで、型定義ファイルの型チェックも行う
業務で問題になったのは、typeRootsを設定すると、デフォルトのnode_modules/@typesが除外されてしまう点です。両方を読み込むには、配列で明示的に両方を指定する必要があります。
配布用ライブラリの型定義設計
npm パッケージとして配布するライブラリの場合、以下の構成を採用しました。
cssmy-library/
├── dist/
│ ├── index.js
│ └── index.d.ts
├── src/
│ └── index.ts
├── package.json
└── tsconfig.json
package.jsonの設定:
json{
"name": "my-library",
"version": "1.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "tsc"
}
}
tsconfig.json(ビルド用):
json{
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "./src",
"module": "ESNext",
"target": "ES2022",
"strict": true
},
"include": ["src"]
}
設計判断の理由:
declaration: trueで、TypeScriptコンパイル時に自動的に.d.tsファイルを生成declarationMap: trueで、型定義から元のソースコードへのマッピングを生成(デバッグ時に便利)typesフィールドで、型定義ファイルの位置を明示files配列で、配布するファイルをdistディレクトリのみに限定
検証の結果、この構成であれば利用者がnpm install my-libraryした際に、自動的に型補完が効くことを確認しました。
設計パターンの選び方
型定義ファイルの設計には複数のアプローチがあります。ここでは、主要なパターンを比較し、選択の判断基準を示します。
グローバル宣言 vs モジュール宣言
| パターン | 使用場面 | メリット | デメリット | 実務での採用判断 |
|---|---|---|---|---|
グローバル宣言declare | ブラウザのwindowNode.jsのglobalprocess.env | インポート不要どこからでもアクセス可 | 型名競合リスクスコープ汚染 | 必要最小限のみ使用 |
モジュール宣言declare module | npm パッケージライブラリの型定義 | 型名競合を回避明示的なインポート | インポート文が必要 | 基本的にこちらを優先 |
判断基準
以下のフローチャートで選択します。
mermaidflowchart TD
start["型定義が必要"]
check1{"グローバルオブジェクト<br/>への追加?"}
check2{"既存ライブラリの<br/>拡張?"}
globalDecl["declare global"]
moduleDecl["declare module"]
augment["augmentation<br/>with declare module"]
start --> check1
check1 -->|Yes| globalDecl
check1 -->|No| check2
check2 -->|Yes| augment
check2 -->|No| moduleDecl
実際にプロジェクトで型定義を追加する際は、このフローに従って選択することで、一貫性のある設計を維持できています。
declare vs interface vs type
型を定義する際の構文選択についても、判断基準を整理します。
| 構文 | 用途 | 拡張性 | 実務での使い分け |
|---|---|---|---|
declare | 値の型宣言関数・変数の存在を示す | - | 実装が別にある場合 |
interface | オブジェクトの形状クラスの契約 | マージ可能 | 拡張前提の型 |
type | 複雑な型の定義ユニオン・交差型 | マージ不可 | 拡張不要の型 |
実務での選択例:
ライブラリ配布時、利用者が型を拡張できるようにする場合はinterfaceを使います。
typescript// ライブラリ側
export interface PluginConfig {
name: string;
}
利用者側で拡張可能:
typescript// 利用者側
declare module "my-library" {
interface PluginConfig {
customOption?: boolean;
}
}
一方、内部実装で使う型や、拡張を想定しない型はtypeを使います。
typescripttype InternalState = {
readonly id: string;
status: "idle" | "loading" | "success" | "error";
};
自動生成 vs 手書き
型定義ファイルの作成方法も、状況によって使い分けます。
| 方法 | 向いているケース | メリット | デメリット |
|---|---|---|---|
自動生成tsc --declaration | TypeScriptで実装型情報が完全 | メンテナンス不要型と実装の一貫性 | 設定が必要生成結果の調整が困難 |
| 手書き | JavaScriptライブラリ外部APIグローバル拡張 | 柔軟な型設計段階的な型付け | メンテナンスコスト型と実装の不一致リスク |
判断基準:
新規にTypeScriptでライブラリを作る場合は、必ず自動生成を採用します。検証の結果、手書きに比べて型と実装の不一致が発生しないため、長期的なメンテナンスコストが大幅に削減できました。
既存のJavaScriptコードに型を付ける場合は、手書きで段階的に型を追加していきます。ただし、将来的にTypeScriptへの移行を見据え、allowJsとcheckJsを有効にして、できる限りJSDocで型情報を付けておくと、移行時の負担が軽減されます。
まとめ
TypeScriptの型定義ファイル(.d.ts)を自作する際は、グローバルスコープの汚染を避け、モジュール宣言を優先することが破綻しない設計の基本です。declare、augmentation、declare moduleといった構文を正しく使い分け、tsconfig.jsonの設定と連携させることで、型安全を保ちながらメンテナンス可能な型定義を作成できます。
実務では、以下のポイントを意識することで、配布しても壊れにくい型定義ファイルを設計できました。
- グローバル宣言は必要最小限にとどめ、できる限りモジュール宣言を使う
- augmentationは範囲を明確にし、利用者がオプトインできる設計にする
- tsconfig.jsonのtypeRoots、types、includeを適切に設定する
- ライブラリ配布時は、自動生成(declaration: true)を優先する
- 型定義と実装の一貫性を保つため、可能な限り型定義を自動生成する
ただし、プロジェクトの規模や既存コードベースの状況によって、最適な設計は異なります。段階的な型付けが必要な場合や、外部ライブラリの型拡張が必要な場合は、手書きの型定義ファイルが適しているケースもあります。この記事で示した判断基準を参考に、自分のプロジェクトに合った設計を選択してください。
型定義ファイルは一度作って終わりではなく、ライブラリの進化に合わせて継続的にメンテナンスが必要です。型と実装の不一致が発生しないよう、テストコードでの型チェックや、CI/CDでの型エラー検出を組み込むことをお勧めします。
関連リンク
- TypeScript公式ドキュメント - Declaration Files
- TypeScript公式ドキュメント - Module Augmentation
- TypeScript 5.9 Release Notes
- Node.js v24.12.0 (LTS) Documentation
- DefinitelyTyped - 型定義リポジトリ
Sources:
著書
article2026年1月23日TypeScriptのtypeとinterfaceを比較・検証する 違いと使い分けの判断基準を整理
article2026年1月23日TypeScript 5.8の型推論を比較・検証する 強化点と落とし穴の回避策
article2026年1月23日TypeScript Genericsの使用例を早見表でまとめる 記法と頻出パターンを整理
article2026年1月22日TypeScriptの型システムを概要で理解する 基礎から全体像まで完全解説
article2026年1月22日ZustandとTypeScriptのユースケース ストアを型安全に設計して運用する実践
article2026年1月22日TypeScriptでよく出るエラーをトラブルシュートでまとめる 原因と解決法30選
articleshadcn/ui × TanStack Table 設計術:仮想化・列リサイズ・アクセシブルなグリッド
articleRemix のデータ境界設計:Loader・Action とクライアントコードの責務分離
articlePreact コンポーネント設計 7 原則:再レンダリング最小化の分割と型付け
articlePHP 8.3 の新機能まとめ:readonly クラス・型強化・性能改善を一気に理解
articleNotebookLM に PDF/Google ドキュメント/URL を取り込む手順と最適化
articlePlaywright 並列実行設計:shard/grep/fixtures で高速化するテストスイート設計術
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
