問題の背景最近、約45,000行のReactコードベースをWebpackからViteに移行しました。移行作業はほぼスムーズに進みましたが、「バレルファイル(barrel files)」に手を加えたところで問題が発生しました。これらの index.ts ファイルはエクスポートを集約するものですが、これによってViteの開発サーバーがエラーを吐き出しました。コンソールにはすぐに次のエラーが表示されました:
Re-exporting a type when the '--isolatedModules' flag is provided requires using 'export type'.
これは、Viteがトランスパイルに esbuild を使用しているために起こります。プロジェクト全体のグラフを分析する標準のTypeScriptコンパイラ(tsc)とは異なり、esbuildは各ファイルを完全に独立して処理します。tsc よりも10倍から100倍高速ですが、弱点があります。エクスポートされた名前がJavaScriptの定数なのか、ビルド時に削除されるべきTypeScriptのインターフェースなのかを判別できないのです。
デバッグプロセス問題の根本は tsconfig.json にあります。モダンなビルドツールを使用している場合、おそらくこの設定が有効になっています:
{
"compilerOptions": {
"isolatedModules": true
}
}
isolatedModules が有効な場合、TypeScriptは単一ファイルトランスパイラでは処理できないパターンの使用を制限します。標準的な構文を使用した型の再エクスポートは、その禁止されたパターンの1つです。src/types/index.ts ファイルにおけるよくある例を見てみましょう:
// src/types/user.ts
export interface User {
id: string;
name: string;
}
// src/types/index.ts
export { User } from './user'; // <-- これがエラーの原因になります
トランスパイラは export { User } を見て混乱します。User が単なる型であるため、ランタイムオブジェクト用のコードを生成すべきか、行全体を削除すべきかが判断できないのです。
解決策### 1. 明示的な 'export type' を使用する最も直接的な修正方法は、コンパイラに何を行っているかを正確に伝えることです。この構文はTypeScript 3.8で導入され、エクスポートが削除対象であることを明示的にマークします。
// 変更前:
export { User } from './user';
// 変更後:
export type { User } from './user';
2. 混合エクスポート (TypeScript 4.5+)コンポーネントとそれに対応する型を同じファイルからエクスポートする必要がある場合、2行に分ける必要はありません。インライン型の修飾子を使うことで、よりすっきりと記述できます。
// src/components/index.ts
export {
Button,
type ButtonProps
} from './Button';
3. インポートとエクスポートを分ける古いバージョンのTypeScriptを使用しているチームや、より冗長なスタイルを好む場合は、操作を分けることができます。最初に型をインポートし、次にそれを個別にエクスポートします。
import { User } from './user';
export type { User };
検証修正されたことを確認するのに、ViteのHMR(Hot Module Replacement)だけに頼らないでください。確実を期すために、私はいつもターミナルで手動の型チェックを実行します:
npx tsc --noEmit
ここでは --noEmit フラグが最適です。.js ファイルを生成せずに完全な型チェックを実行します。これは、Viteやesbuildが実際のバンドル処理を担当している場合にまさに必要なことです。

