お疲れ。最近、TypeScriptの導入を検討したものの、ビルドステップの複雑化や「とりあえずプレーンなJSでサクッと書きたい」という要件に阻まれ、モヤモヤしている現場が増えているんじゃないか?
特に、中途半端に育った中規模のJavaScriptプロジェクトでよくあるのが、「どのファイルがどんなオブジェクトの形をしているのか、誰も全容を把握できていない問題」だ。エディタの補完は効かないし、ちょっとしたリファクタリングでコードベースが崩壊の危機に瀕する。あの冷や汗をかく感覚、フロントエンドエンジニアなら誰もが一度は味わっているはずだ。
だが、安心してほしい。TypeScriptを導入せずとも、JSDocと現代のエディタ(VSCodeなど)の型推論エンジンを組み合わせれば、JSのままTypeScript並みの堅牢性と神がかった入力補完を手に入れることができる。
今回は、その切り札であるJSDocの `@import` タグを使った外部型定義のインポートについて、実務の現場でそのまま使えるノウハウを余すところなく伝授しよう。
—
なぜ今、JSDocの `@import` なのか?
かつて、JSDocで他のファイルの型を参照しようとすると、`@typedef` と `@link` や `@param {import(‘./path’).TypeName}` のような、いかにも「後付け感」のある冗長な記述が必要だった。正直、書くのも読むのも苦痛だったはずだ。
しかし、JSDocは日々進化している。最新のTypeScript言語サービス(VSCodeの裏側で動いているやつだ)は、JSDoc内での `@import` 構文を完全にサポートするようになった。これにより、TSファイルや別のJSファイルで定義した型を、まるで本家のTypeScriptのようにスッキリとインポートできるようになったんだ。
ブラウザの裏側、つまりJavaScriptのランタイムにおいて、JSDocは単なるコメントにすぎない。実行時にはすべてパースされずに捨てられるため、トランスパイルのオーバーヘッドはゼロ。純粋なJSの軽快さを保ちながら、開発時のみ最強の型安全を享受できる。これを使わない手はない。
—
現場で即実践!`@import` を使った型共有のハンズオン
百聞は一見にしかずだ。実際のプロジェクト構造をイメージして、コードを見ていこう。
1. 型定義を集中管理するファイルを作る
まずは、アプリ全体で使う共通のデータ構造(例えば、APIから返ってくるユーザー情報)を定義する。ここでは純粋なJavaScriptファイル、あるいは `.d.ts` ファイルを使う。今回は実態の分かりやすいJSファイルでいこう。
// types.js
/
- @typedef {Object} User
- @property {string} id – ユーザーを一意に識別するID
- @property {string} name – ユーザーの表示名
- @property {(‘admin’|’user’|’guest’)} role – 権限ロール
- @property {string} [email] – オプショナルなメールアドレス
/
/
- @typedef {Object} ApiResponse
- @property {boolean} success – リクエストが成功したかどうか
- @property {User} data – レスポンスデータ
- @property {string} [error] – エラーメッセージ
/
// JSとして有効にするため、空のオブジェクトでもエクスポートしておく
export {};
2. 業務ロジックのファイルで `@import` を使う
次に、先ほど定義した型を、実際の処理を行う別のJSファイルにインポートして活用する。ここが今回のハイライトだ。
// user-service.js
/
- @import { User, ApiResponse } from ‘./types.js’
/
/
- 指定されたIDのユーザー情報を取得する(モック)
- @param {string} userId – 取得したいユーザーのID
- @returns {Promise
} ユーザーデータを含むAPIレスポンス
/
export async function fetchUser(userId) {
// 実際のネットワークリクエストのつもり
return {
success: true,
data: {
id: userId,
name: ‘レジェンドエンジニア’,
role: ‘admin’,
// ここでプロパティ名を間違えたり、型違いの値を入れようとすると
// エディタが赤く波線を引いて怒ってくれる
}
};
}
/
- ユーザーの権限に応じたウェルカムメッセージを生成する
- @param {User} user – 対象のユーザーオブジェクト
- @returns {string} ウェルカムメッセージ
/
export function getWelcomeMessage(user) {
// user. と打った瞬間に、id, name, role, email が補完される快感を味わってほしい
if (user.role === ‘admin’) {
return `ようこそ、管理者 ${user.name} さん。`;
}
return `こんにちは、${user.name} さん。`;
}
どうだ? TypeScript特有のコンパイルエラーや型定義ファイルのビルド設定に悩まされることなく、エディタ上では完璧な型補完と静的解析が機能している。
—
シニアが教える、実務でハマる落とし穴とベストプラクティス
この `@import` 手法は強力だが、現場で導入する際にはいくつかおさえておかなければならない「大人の事情」やコツがある。
1. パス解決の罠に気をつけろ
`@import` のパス指定は、TypeScriptのモジュール解決ルールや、IDE(VSCodeなど)のファイルパス補完に依存している。
相対パス(`./types.js` など)を書く際、ファイル移動をした途端にパスが破綻することがよくある。可能であれば、`jsconfig.json` でパスエイリアス(baseUrlやpaths)を設定し、 `@/types` のような絶対パス風のスマートな書き方をできるように環境を整えておくのがプロの技だ。
2. 必ずファイル末尾に `export {}` を書くこと
JSDocで定義した型を他のファイルから参照するためには、そのファイルが「モジュール」として認識されていなければならない。
ES Modulesの構文(`import` や `export`)がファイル内に一つもないと、JavaScriptはそれをグローバルスコープのスクリプトとみなしてしまう。そのため、型定義だけのファイルであっても、最後に `export {};` を書くことを忘れないようにしよう。これ、本当によくあるハマりどころだ。
3. JSDocの型定義は「実行時」には消えることを忘れるな
初心者がやりがちなミスとして、JSDoc内で定義したカスタム型を、実行時の `instanceof` チェック等に使おうとするケースがある。
当然だが、JSDocはコメントだ。実行時のJavaScriptエンジンには一切届かない。あくまで「開発時のエディタ補助(インテリセンス)」と「コードのドキュメント化」のためのものであるという境界線を、チームメンバー全員で共通認識として持っておいてほしい。
—
おわりに
TypeScriptへの移行は素晴らしい試みだが、プロジェクトの規模やチームの習熟度によっては、大きなコストとストレスを伴う。
「型安全は欲しいが、ビルドの複雑さは増やしたくない」「既存のVanilla JS資産を最大限活かしたい」――そんな現場のジレンマを鮮やかに解決してくれるのが、このJSDocの `@import` による外部型定義のインポートだ。
今日から君のプロジェクトでも、`types.js` を一枚仕込んで、快適なエディタ補完ライフをチームに共有してやってくれ。まわりのエンジニアたちの開発効率が跳ね上がるのを、きっと実感できるはずだ。それじゃ、また現場で会おう。

コメント