背景
Chakra UI v3 でダイアログ(モーダル)を実装したところ、TypeScript から次のエラーが出た
型 '{ children: Element; }' には型 'DialogPositionerProps' と共通のプロパティがありません。
このエラーメッセージはそのまま読んでも何が問題なのか分かりにくい
原因をたどると tsconfig.json の moduleResolution の設定値が原因ぽかった
結論
解決策は moduleResolution を "node" から "bundler" に変更すること
以下、エラーの正体・原因・moduleResolution の役割を順に整理する
内容
1. このエラーは「タイプミスを疑っている」エラー
TypeScript のエラーコードでいう TS2559 に該当する。発動条件は次の1点
渡そうとしている props と、受け取り側の型に、共通するキーが1個もない
通常 TypeScript は「足りない」「余計だ」と指摘してくる。一方 TS2559 は「キーが1つも噛み合っていない」状態専用で、「これはタイプミスかコピペミスではないか?」と TypeScript が強めに警告している状態を示す
2. 今回はタイプミスではなく、ライブラリの型情報が壊れていた
JSX で次のように書いたとする。
<Dialog.Positioner>
<Dialog.Content>...</Dialog.Content>
</Dialog.Positioner>
TypeScript は内部でこう変換している。
Dialog.Positioner({
children: <Dialog.Content>...</Dialog.Content>
})
つまり「children というキーを1つだけ持つオブジェクト」を渡している状態。本来 Dialog.Positioner は children を受け取れる型として定義されているはずだが、今回は型情報が壊れて children を受け取れない型になっていた
そのため「渡している側のキー: children のみ」「受け取り側が知っているキー: children を含まない別のもの」となり、共通キーがゼロ → TS2559 が発動するという流れ
3. なぜ型が壊れたか — moduleResolution の話
tsconfig.json とは
TypeScript に「このプロジェクトをどう扱ってほしいか」を伝える設定ファイル。代表的な項目は次のとおり。
| 項目 | 意味 |
|---|---|
target |
どの JavaScript バージョンに変換するか |
strict |
型チェックの厳しさ |
include |
対象とするフォルダ・ファイル |
moduleResolution |
← 今回の要因。モジュール(ライブラリ)の探し方 |
moduleResolution とは
import { x } from 'y' と書いたとき、TypeScript は 'y' という名前だけを頼りに「どのファイルが本体なのか」を解決する必要がある。その解決ルールを決めるのが moduleResolution
主な値の比較
| 値 | 想定環境 | 特徴 |
|---|---|---|
"node" |
古い Node.js | 古いルール。最近のライブラリの最新機能を読めない |
"node16" / "nodenext"
|
新しい Node.js | 新ルール対応。バックエンド向け |
"bundler" |
Vite / Webpack / Next.js などのフロント向けビルドツール | 最新ルール対応 + 柔軟。フロント開発の標準 |
"classic" |
TypeScript 初期の独自方式 | ほぼ使われない(非推奨) |
決定的な差: 「サブパス」を解決できるかどうか
最近のライブラリは package.json の exports フィールドで「入口を細かく分ける」設定を書いている
// 例: あるライブラリの package.json
{
"exports": {
".": "./dist/index.js", // メイン入口
"./dialog": "./dist/dialog.js" // サブ入口(subpath)
}
}
これによって「メインの入口」と「個別パーツ用の入口」を分けて公開できる。
-
"node"→ このexportsを読めない -
"bundler"→ 読める
これが今回の原因
4. 今回起きたことの整理
Chakra UI の Dialog の型は、内部で別のライブラリ(Ark UI)の型をサブパス経由で参照している
// Chakra の内部(イメージ)
import { Dialog as ArkDialog } from "@ark-ui/react/dialog"
// ^^^^^^^^^^^^^^^^^^^^^^
// ↑ サブパス経由で型を参照
プロジェクトの tsconfig.json の moduleResolution が "node" のままになっていた → Ark UI のサブパス型が解決できない → Chakra の型チェーンが途中で切れる → 本来含まれるはずの children が型から消える → TS2559(共通プロパティなし)が出る、という感じ
5. 補足: 関連するエラーコードと設定値
TS2559 と類似エラーの違い
| エラーコード | メッセージ | 発動条件 |
|---|---|---|
| TS2559 | 共通のプロパティがありません | キーが1個も噛み合わない特殊ケース |
| TS2322 | 型 X を型 Y に割り当てれない | 一般的な型不一致 |
| TS2353 | 既知のプロパティしか指定できない | 余計なプロパティがある |
| TS2741 | プロパティ X が不足している | 必須のプロパティがない |
TS2559 だけが「キーが1個も噛み合わないとき専用」という独特の性格を持つ。
moduleResolution 設定値の早見表
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "bundler" // フロントエンド標準
// または "nodenext" // 最新 Node.js
// または "node16" // 特定の Node.js バージョン
// または "node" // ← レガシー、使わない
}
}
まとめ
-
tsconfig.jsonのmoduleResolutionを"Node"→"bundler"に変更することで解決した{ "compilerOptions": { - "moduleResolution": "Node", + "moduleResolution": "bundler", } } -
環境別の選び方の指針
環境 選ぶ値 Vite / Next.js / Webpack(フロントエンド) "bundler"最新の Node.js(バックエンド) "nodenext"Node.js のバージョンを固定したい "node16"古い CommonJS 環境(やむを得ず) "node" -
最近のライブラリ(Chakra v3、Ark UI、Radix、TanStack 系など)を使うなら
"node"は実質使ってはいけない値
感想
- そもそもTS慣れてないと、型系のエラー内容がイマイチ読めない