TypeScript で開発していると「Could not find a declaration file for module 'xxx'」というエラーに遭遇することがあります。これは型定義ファイル(.d.ts)が存在しないために起こるエラーです。
型定義ファイル(.d.ts)は、JavaScript のコードに型情報を付加するためのファイルです。実装コードは含まず、型の「宣言」のみを持ちます。@types/xxx パッケージ・declare module・declare global など、本記事では型定義ファイルに関わる技術を体系的に解説します。
.d.tsファイルの役割と基本構造declareキーワードの各種用法(変数・関数・クラス・モジュール・namespace)@typesパッケージの仕組みとtsconfig.jsonでの制御- 型定義のない JS ライブラリに
declare moduleで型を付ける方法 declare globalでグローバル変数・環境変数の型を宣言する方法- 既存ライブラリの型定義を拡張する Module Augmentation
- 実践パターン3本・FAQ6問
1. 型定義ファイル(.d.ts)とは
.d.ts(Declaration ファイル)は型情報だけを持つファイルで、コンパイル後の JavaScript には含まれません。TypeScript コンパイラが型チェックをするときに参照します。
| ファイル種別 | 内容 | コンパイル後 |
|---|---|---|
.ts |
実装コード + 型情報 | .js が生成される |
.d.ts |
型情報のみ(declare) | 生成されない(型チェック専用) |
.tsx |
JSX を含む実装コード | .js が生成される |
.d.ts ファイルが必要になる主な場面は次の3つです。
// 1. JavaScript で書かれたライブラリに型を付けたい // → node_modules/@types/xxx/index.d.ts または自作 .d.ts // 2. グローバル変数(window.MyApp, process.env.API_KEY など)の型を定義したい // → global.d.ts で declare // 3. 既存ライブラリの型定義を拡張したい(型を追加したい) // → Module Augmentation で declare module を使う
TypeScript で書いたコードを
tsc でビルドするとき、tsconfig.json で "declaration": true を設定すると.js と一緒に .d.ts が自動生成されます。npm に公開する TypeScript パッケージはこの方法で型定義を同梱します。2. declare キーワードの基本
declare は「この名前の実体はすでに存在している(JS で定義済み)。TypeScript に型情報だけ教える」という宣言です。実装コードは書かず、型シグネチャのみを記述します。
2-1. declare var / let / const
// グローバルに存在する変数を TypeScript に教える
declare var __DEV__: boolean;
declare const APP_VERSION: string;
declare let currentUser: { id: number; name: string } | null;
// 使用例(実体は HTML <script> や webpack.DefinePlugin で定義済み)
if (__DEV__) {
console.log(`App v${APP_VERSION}`);
}
2-2. declare function / declare class
// グローバル関数の型宣言
declare function fetch(url: string, init?: RequestInit): Promise<Response>;
declare function alert(message: string): void;
// オーバーロードも書ける
declare function require(module: string): any;
declare function require<T>(module: string): T;
// クラスの型宣言
declare class EventEmitter {
on(event: string, listener: (...args: any[]) => void): this;
off(event: string, listener: (...args: any[]) => void): this;
emit(event: string, ...args: any[]): boolean;
}
2-3. declare namespace
declare namespace はグローバルオブジェクトのプロパティをまとめて宣言するときに使います。古い JavaScript ライブラリ(jQuery の $ など)の型定義によく使われます。namespace 名はそのままグローバル変数名になります。
// jQuery の型定義イメージ(グローバルスコープに jQuery オブジェクトが存在する場合)
declare namespace jQuery {
interface AjaxSettings {
method?: "GET" | "POST" | "PUT" | "DELETE";
data?: unknown;
timeout?: number;
}
interface jqXHR {
done(handler: (data: unknown) => void): this;
fail(handler: (error: unknown) => void): this;
}
function ajax(url: string, settings?: AjaxSettings): jqXHR;
function get(url: string): jqXHR;
function post(url: string, data?: unknown): jqXHR;
}
// 短縮名 $ も別途 declare(namespace とは独立した変数宣言)
// jQuery namespace と同じ形状を持つ関数・オブジェクトとして宣言
declare const $: typeof jQuery & ((selector: string) => { length: number });
// 使用例(window.jQuery / window.$ として存在する)
jQuery.ajax("/api/users").done(data => console.log(data));
$.get("/api/status");
3. @types パッケージの仕組み
@types/xxx は DefinitelyTyped プロジェクトが管理する型定義パッケージです。型定義を持たない npm パッケージに型情報を追加インストールできます。
# よく使う @types のインストール npm install --save-dev @types/node # Node.js API の型 npm install --save-dev @types/react # React の型 npm install --save-dev @types/lodash # lodash の型 npm install --save-dev @types/jest # Jest テストの型 npm install --save-dev @types/express # Express の型
インストールすると node_modules/@types/node/index.d.ts のような型定義ファイルが追加され、TypeScript が自動的に読み込みます。
3-1. tsconfig.json での @types 制御
// tsconfig.json
{
"compilerOptions": {
// typeRoots: 型定義を探すディレクトリ(デフォルトは node_modules/@types)
"typeRoots": ["./node_modules/@types", "./src/types"],
// types: 自動読み込みする @types を限定する
// 指定した場合、ここにないパッケージは自動読み込みされない
"types": ["node", "jest"],
// declaration: .d.ts を自動生成する
"declaration": true,
// declarationDir: .d.ts の出力先ディレクトリ
"declarationDir": "./dist/types"
}
}
tsconfig.json の "types": ["node"] のように指定すると、それ以外の @types は自動的に読み込まれなくなります。例えば @types/lodash をインストールしても "types" に含めなければ型が効きません。意図せず型が消えた場合は types の設定を確認してください。通常は types を省略して全 @types を自動読み込みするのが無難です。4. declare module:型のない JS ライブラリに型を付ける
@types が存在しない JS ライブラリを使うとき、declare module で自分で型定義を書くことができます。
4-1. 最小限の型定義(any で黙らせる)
// src/types/legacy-lib.d.ts
// 型定義のないライブラリを any で黙らせる(最小限の対処)
declare module "legacy-analytics" {
const analytics: any;
export default analytics;
}
// ワイルドカードで特定パターンのモジュールを一括対処
declare module "*.svg" {
const content: string;
export default content;
}
declare module "*.png" {
const content: string;
export default content;
}
// CSS Modules
declare module "*.module.css" {
const classes: Record<string, string>;
export default classes;
}
4-2. 本格的な型定義を書く
// src/types/my-chart-lib.d.ts
// 架空の npm パッケージ "my-chart-lib" に型定義を追加する例
declare module "my-chart-lib" {
// 公開する型・インターフェース
export interface ChartOptions {
type: "line" | "bar" | "pie" | "scatter";
data: ChartData;
width?: number;
height?: number;
theme?: "light" | "dark";
animation?: boolean;
}
export interface ChartData {
labels: string[];
datasets: Dataset[];
}
export interface Dataset {
label: string;
data: number[];
backgroundColor?: string | string[];
borderColor?: string;
}
// クラス宣言
export class Chart {
constructor(canvas: HTMLCanvasElement, options: ChartOptions);
update(data: Partial<ChartData>): void;
destroy(): void;
toBase64(): string;
}
// 名前付きエクスポート関数
export function createChart(canvas: HTMLCanvasElement, options: ChartOptions): Chart;
export function registerPlugin(name: string, plugin: object): void;
// デフォルトエクスポート
const ChartLib: {
Chart: typeof Chart;
createChart: typeof createChart;
version: string;
};
export default ChartLib;
}
5. declare global:グローバル変数・環境変数の型宣言
ブラウザや Node.js のグローバルスコープに存在する変数、または webpack・Vite などのビルドツールが注入する変数に型を付けるには declare global を使います。
declare global { ... } を含むファイルに import や export が一つもないと、TypeScript はそのファイルを「スクリプト」として扱い declare global が効きません。ファイル末尾に export {}; を書くことで「モジュール」として扱われ、正しく declare global が適用されます。5-1. 環境変数の型定義
// src/types/env.d.ts
// Node.js の process.env に型を追加(declare global 内で interface を拡張)
declare global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: "development" | "production" | "test";
API_BASE_URL: string;
DATABASE_URL: string;
JWT_SECRET: string;
PORT?: string; // 省略可能な環境変数は ? を付ける
MAX_WORKERS?: string;
}
}
}
// これにより process.env に型補完が効く
const port = process.env.PORT ?? "3000";
const url = process.env.API_BASE_URL; // string(undefined にならない)
// process.env.UNKNOWN_KEY; // Error: ProcessEnv にないキー
// ※ モジュール内で declare global を使う場合は export {} が必要
export {}; // このファイルをモジュールとして扱うために必要
5-2. ウィンドウオブジェクトとカスタムプロパティ
// src/types/global.d.ts
// window に独自プロパティを追加
declare global {
interface Window {
// Google Analytics
gtag: (
command: "event" | "config" | "set",
targetId: string,
params?: Record<string, unknown>
) => void;
dataLayer: unknown[];
// アプリ固有のグローバル
__APP_CONFIG__: {
apiUrl: string;
version: string;
features: Record<string, boolean>;
};
}
// webpack.DefinePlugin / Vite define で注入されるグローバル定数
const __DEV__: boolean;
const __VERSION__: string;
const __BUILD_TIME__: string;
}
export {};
// 使用例
window.gtag("event", "page_view", { page_path: "/home" });
const config = window.__APP_CONFIG__;
if (__DEV__) { console.log("Debug mode"); }
6. Module Augmentation:既存ライブラリの型を拡張する
Module Augmentation は既存のモジュールの型定義に新しいプロパティやメソッドを追加する技術です。ライブラリの型定義を変更せずに自分のプロジェクト固有の型を追加できます。
6-1. Express の Request 型を拡張する
Express の req.user などを追加するには、"express" ではなく内部モジュール "express-serve-static-core" を拡張します。Request インターフェースはこちらで定義されているためです。
// src/types/express.d.ts
import { User } from "../models/user"; // アプリの User 型
// express モジュールの型定義を拡張
declare module "express-serve-static-core" {
interface Request {
user?: User; // 認証済みユーザー(認証ミドルウェアで設定)
requestId: string; // リクエスト追跡 ID
startTime: number; // パフォーマンス計測用
}
}
export {};
// これにより Express のハンドラ内で req.user が型安全に使える
// router.get("/profile", (req, res) => {
// if (!req.user) return res.status(401).json({ error: "Unauthorized" });
// res.json({ name: req.user.name }); // req.user は User 型
// });
6-2. Vue / React のグローバルプロパティを拡張する
// Vue 3 のグローバルプロパティに型を追加
// src/types/vue.d.ts
import { ComponentCustomProperties } from "vue";
import type { Router } from "vue-router";
import type { Store } from "vuex";
declare module "@vue/runtime-core" {
interface ComponentCustomProperties {
$router: Router;
$store: Store<AppState>;
$http: typeof axios;
$toast: ToastService;
}
}
export {};
// ────────────────────────────────────────────────
// React 用: react-i18next の useTranslation に型を付ける
// src/types/i18next.d.ts
import "i18next";
import type { Resources } from "../locales/ja";
declare module "i18next" {
interface CustomTypeOptions {
defaultNS: "translation";
resources: Resources;
}
}
// これにより t("user.name") が型チェックされる
// t("user.name"); // OK: 存在するキー
// t("user.unknown"); // Error: 存在しないキー
できること:既存インターフェースへのプロパティ追加・関数のオーバーロード追加・namespace へのメンバー追加。できないこと:既存プロパティの型変更・プロパティの削除・クラスのメソッド実装変更。Module Augmentation はあくまで「追加」のみで、既存の型を上書きすることはできません。
7. 実践パターン3本
実践例1:npm パッケージの型定義を自作して @types に貢献する準備
@types が存在しない小さなユーティリティパッケージの型定義を作り、プロジェクトで使いながら DefinitelyTyped への Pull Request 準備をするパターンです。
// 対象パッケージ: npm の "query-string" v6 系(架空の例として)
// src/types/query-string.d.ts
declare module "query-string" {
export type ParseOptions = {
arrayFormat?: "bracket" | "index" | "comma" | "none";
parseNumbers?: boolean;
parseBooleans?: boolean;
sort?: ((a: string, b: string) => number) | false;
};
export type StringifyOptions = {
arrayFormat?: "bracket" | "index" | "comma" | "none";
encode?: boolean;
skipNull?: boolean;
skipEmptyString?: boolean;
sort?: ((a: string, b: string) => number) | false;
};
export type ParsedQuery<T = string> = {
[key: string]: T | T[] | null;
};
export function parse(query: string, options?: ParseOptions): ParsedQuery;
export function stringify(object: Record<string, unknown>, options?: StringifyOptions): string;
export function parseUrl(url: string, options?: ParseOptions): { url: string; query: ParsedQuery };
export function extract(url: string): string;
}
// 使用例
import qs from "query-string";
const parsed = qs.parse("?foo=1&bar=2&tags=a&tags=b");
// ParsedQuery 型: { foo: string|string[]|null; bar: ...; tags: ... }
const str = qs.stringify({ page: 1, limit: 20, status: "active" });
// "limit=20&page=1&status=active"
実践例2:モノレポでの共有型定義パッケージ
フロントエンド・バックエンドが共存するモノレポで、API のリクエスト/レスポンス型を.d.ts パッケージとして共有するパターンです。
// packages/api-types/src/index.d.ts
// フロントとバックエンドで共有する API 型定義
// ── ユーザー関連 ──
export interface User {
id: number;
name: string;
email: string;
role: "admin" | "editor" | "viewer";
createdAt: string; // ISO 8601
}
// ── API レスポンス共通ラッパー ──
export interface ApiSuccess<T> {
success: true;
data: T;
meta?: { total: number; page: number; perPage: number };
}
export interface ApiError {
success: false;
error: { code: string; message: string; details?: unknown };
}
export type ApiResponse<T> = ApiSuccess<T> | ApiError;
// ── エンドポイント別リクエスト型 ──
export interface CreateUserRequest {
name: string;
email: string;
password: string;
role?: User["role"];
}
export interface UpdateUserRequest {
name?: string;
email?: string;
role?: User["role"];
}
// packages/api-types/package.json
// { "name": "@myapp/api-types", "types": "./src/index.d.ts" }
// フロントエンドとバックエンドで同じ型を使う
// import type { User, ApiResponse } from "@myapp/api-types";
実践例3:実行時型チェックと .d.ts の連携(zod + 型定義)
外部 API のレスポンスに対して zod でスキーマ検証を行い、検証済みの型を .d.ts で公開するパターンです。
// src/schemas/user.ts
import { z } from "zod";
// zod スキーマ(実行時バリデーション)
export const UserSchema = z.object({
id: z.number(),
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(["admin", "editor", "viewer"]),
createdAt: z.string().datetime(),
});
// スキーマから型を自動生成
export type User = z.infer<typeof UserSchema>;
// { id: number; name: string; email: string; role: "admin"|"editor"|"viewer"; createdAt: string }
// src/api/fetchUser.ts
import { UserSchema, User } from "../schemas/user";
export async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
const json = await res.json();
// 実行時バリデーション(API が型通りのデータを返すか確認)
const parsed = UserSchema.safeParse(json);
if (!parsed.success) {
throw new Error(`Invalid user data: ${parsed.error.message}`);
}
return parsed.data; // ここから先は User 型として型安全
}
// ライブラリとして公開する場合: tsc --declaration で .d.ts を自動生成
// dist/api/fetchUser.d.ts が生成され型定義が同梱される
モジュールの import/export の詳細は モジュール完全ガイド、tsconfig.json の詳細設定は tsconfig.json 完全ガイド を参照してください。
8. まとめ:場面別の .d.ts 活用チートシート
| 場面 | 使う技術 | ファイル名の例 |
|---|---|---|
| 型のない JS ライブラリに型を付ける | declare module "xxx" |
src/types/xxx.d.ts |
| 画像・CSS Modules を import する | declare module "*.png" |
src/types/assets.d.ts |
| グローバル変数・定数に型を付ける | declare global { const __DEV__: boolean } |
src/types/global.d.ts |
| process.env の型を強化する | declare global { namespace NodeJS { interface ProcessEnv } } |
src/types/env.d.ts |
| window に独自プロパティを追加 | declare global { interface Window { ... } } |
src/types/window.d.ts |
| Express Request を拡張する | declare module "express-serve-static-core" |
src/types/express.d.ts |
| TS ライブラリの型を公開する | tsc --declaration で自動生成 |
dist/index.d.ts |
型定義ファイルを使いこなすことで、JavaScript のエコシステム全体を型安全な環境で扱えるようになります。まずは process.env の型強化や画像ファイルのインポート型定義など、手軽なところから試してみましょう。 TypeScript の基本的な型については 型の基礎完全ガイド も参照してください。
FAQ
Q.d.ts ファイルをどこに置けばよいですか?
Aプロジェクトの規模によりますが、一般的には src/types/ または types/ ディレクトリを作成して管理します。tsconfig.json の "include" や "typeRoots" にパスを含めることでTypeScript が読み込みます。ファイル名は内容を表す名前(global.d.ts・env.d.ts)が推奨です。
Qdeclare module と declare namespace の違いは何ですか?
Adeclare module "xxx" は ES モジュール形式の外部パッケージに型を付けます。import xxx from "xxx" で使うライブラリが対象です。declare namespace xxx はグローバルスコープにオブジェクト(window.jQuery など)として存在する旧来の JavaScript ライブラリに使います。現代のプロジェクトでは declare module を使う機会が多いです。
Qexport {} はなぜ必要ですか?
A.d.ts ファイルに import や export が一つもないと、TypeScript はスクリプト(グローバルスコープ)として扱います。declare global を含むファイルで declare module や import を使う場合は、ファイルをモジュールとして扱わせるために export {} を末尾に書く必要があります。書かないと declare global が二重に適用されるなどのエラーが発生することがあります。
Q@types パッケージとライブラリ同梱の型定義(.d.ts)はどちらが優先されますか?
Aライブラリの package.json に "types" または "typings" フィールドがある場合、そのパッケージ同梱の型定義が優先されます。@types/xxx はライブラリに型定義が同梱されていない場合のフォールバックです。両方が存在する場合は同梱の型定義が使われます(typeRoots の設定によって変わる場合もあります)。
QModule Augmentation で型を追加しても補完が効きません。なぜですか?
A主な原因は①ファイルが TypeScript に読み込まれていない(tsconfig.json の include に含まれているか確認)、②export {} が必要なのに書いていない(スクリプトとして扱われている)、③拡張先の正確なモジュール名が間違っている(例: Express は "express" ではなく "express-serve-static-core" を拡張する必要がある)、の3つです。エラーメッセージと tsconfig.json を確認してください。
Q自作した型定義ファイルを npm に公開するにはどうすればよいですか?
A方法は2つあります。①パッケージに同梱:tsconfig.json で "declaration": true にしてtsc でビルドし、package.json の "types" フィールドに .d.ts のパスを指定します。②DefinitelyTyped への貢献:@types/xxx の形で公開したい場合はDefinitelyTyped リポジトリに Pull Request を送ります。型定義のテストファイル(xxx-tests.ts)も必要です。
型定義ファイルは TypeScript エコシステムの根幹を支える重要な技術です。最初は @types をインストールするだけで済む場面がほとんどですが、自作が必要になったときにこの記事を参照してください。
