Next.js 10.xで@types/reactが認識されずビルドに失敗することがある問題
ちょっと株式会社、フロントエンドエンジニアのn13uです.
Next.js 10.2.3を扱っていた際に特定のバージョンの@types/reactを入れると、next build側から@types/reactの不足が原因でビルドに失敗する問題があり、解決した話と詳しい症状、依存解決の話の一部分を備忘録的にまとめています.
結論
-
@type/reactのpackage.json内部のexportsに書かれた Subpath exportsの記述が不足しており、Next.js 10.2.3系の依存解決の方式に合っていなかった - 依存環境は追加されているのに認識されておらず、環境を追加するような案内が出ていた
- 今回
pnpmを利用してる環境だったため@types/reactのpatchを当てる形で対応した - この問題は2022年7月18日[1]以降にパッケージされた
@types/reactをNext.js 10系で利用すると必ず発生する問題
`@types/react-dom` の場合
@types/react-domの場合でもexportsの記述が一緒だったりしますが、Next.js 10.2.3では@types/react-domをビルド時の必須環境としていないため大きな問題はありません.
問題の概要
以下の環境下において、next build実行時に@types/reactがnode_modules/に含まれているにもかかわらず、以下のようなエラーが発生しビルドに失敗ていました.
- Next.js 10.2.3(以下、「当該バージョン」)
- React 17.0.2
- Node.js 20.x(>=12で再現するはず)
- @types/react 17.0.83(17系の場合>=17.0.48で発生し、@types/reactの16.x/15.xの最新版でも起きうる問題です)
発生しているエラー
It looks like you're trying to use TypeScript but do not have the required package(s) installed.
Please install @types/react by running:
npm install --save-dev @types/react
詳しい症状
起きている症状を順を追って書くと以下の通りになります.
- Next.jsでは
next build実行時に事前にハードコードされた必須の依存環境を1つ1つ解決して、パッケージの導入を確認している. - 当該バージョンでは必須の依存環境の1つとして
@types/react/index.d.tsを指定している. - 同様に当該バージョンでは
require.resolveを利用して依存解決をしている[2]が、本来書かれるべきSubpath Exportsにindex.d.tsの記述がないため、処理系がindex.d.tsがexportされていないと判断してERR_PACKAGE_PATH_NOT_EXPORTEDがThrowされる. - 依存環境の確認を行う
has-necessary-dependencies.tsの内部では、catchしたerrorはconsole.errorなどされず握りつぶされる. (当該バージョンの場合) - その結果ユーザーには
@types/reactが追加されていないようなふうに表示がされてしまう.
この問題は結論にも書いたとおり、2022年7月18日以降にビルドされたパッケージでは起きる問題です. ここを深く追っていくと発見が色々あり、以降はその記録です.
@types/reactに起きた変化
2022年4月~2022年7月にかけてDefinitelyTyped[3]のreact、react-domのパッケージに大きな変更が入りました. exportsの記述の詳細化でした. 2022年3月29日のReact 18リリース時にpackage.jsonにexportsが追加されました. これはTypeScriptにおいて「moduleResolutionにnode16やbundlerを指定した際にTypeScriptはpackage.jsonのexportsを参照するようになりここに記述されていないパスは解決できなくなる」問題を解消するためでした. [4]
TypeScriptの話を出しましたが今回の症状には直接関係がありません.
表題の通りで、今回はNode.jsが@types/reactを認識しない問題であり、TypeScriptが@types/reactを認識するかどうかは直接関係ないので、形としてはとばっちりを食らっているようなものです.
このReact 18の変更に合わせて2022年7月28日に、Reactのv15/v16/v17のpackage.jsonにも同様のexportsが記載されました.[1:1]
@types/react@17のdiff
では具体的にはどんな変更が入ったんでしょうか?
以下のように@17.0.48と@17.0.1の比較をしてみるとexportsの記述が新しく追加されたことがわかります.
{
"name": "@types/react",
- "version": "17.0.1",
+ "version": "17.0.48",
"license": "MIT",
"main": "",
"types": "index.d.ts",
+ "exports": {
+ ".": {
+ "types": {
+ "default": "./index.d.ts"
+ }
+ },
+ "./jsx-runtime": {
+ "types": {
+ "default": "./jsx-runtime.d.ts"
+ }
+ },
+ "./jsx-dev-runtime": {
+ "types": {
+ "default": "./jsx-dev-runtime.d.ts"
+ }
+ },
+ "./package.json": "./package.json"
+ },
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
},
"typeScriptVersion": "3.4"
}
何がダメだったか?
当該バージョンでは@types/reactの存在を「@types/react/index.d.tsという依存環境が解決できるか?」という点で検知しています.
@types/reactのexports内のSubpathには**index.d.tsが記述されていないために、ERR_PACKAGE_PATH_NOT_EXPORTEDというエラー**になるわけです.
(記述されているjsx-runtimeとかは@types/react/jsx-runtimeとして認識できます)
そしてもう一つの問題として当該バージョンでは依存解決にrequire.resolveを利用しています.
require.resolveで依存解決をする時にexportsのConditional Exportsを見にいくわけですが、typesというcondition=条件は実はNode.jsの仕様として定義されていないので結局パスが見つからずにエラーが発生するようになります.
ではどうすればいいのでしょうか?
解決方法
起きている問題は以下の2つです.
-
Subpathが記述されていない -
typesというConditionを認識できない
これらを満たす解決方法は複数ありますが、一番短く書くと以下の形で記述できます.
これでTypeScriptもrequireも認識できるようになるわけです.
実際にはpnpm patchで以下と同じ記述を@types/reactのpackge.jsonに当てています.
pnpm patch についてはこちらの記事をご覧ください.
{
"name": "@types/react",
"version": "17.0.48",
"license": "MIT",
"main": "",
"types": "index.d.ts",
"exports": {
".": {
"types": {
// 一見以下の記述で動くように見えますが、
// globのようなpattern matchingには'*'が必要です
// '.’だけでは@types/reactでしか参照されないという問題があります.
"default": "./index.d.ts"
}
},
"./jsx-runtime": {
"types": {
"default": "./jsx-runtime.d.ts"
}
},
"./jsx-dev-runtime": {
"types": {
"default": "./jsx-dev-runtime.d.ts"
}
},
+ "index.d.ts": "index.d.ts",
"./package.json": "./package.json"
},
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
},
"typeScriptVersion": "3.4"
}
package.jsonのexportsについて
ここまでたびたび出てきたpackage.jsonのexportsについて簡単にまとめます.
簡単に一部の機能も紹介します.
- 元々は
mainにentrypointとなるjsファイルを書くとこれがrequireされるようになっていました(<=Node.js 10) -
mainのよりモダンな代替としてNode.js 12から追加された機能です. [5]
Node.js 12からとは明確に書いてない...
Node.jsのドキュメントにはexportsのみの記法に関しては厳密なバージョンが書枯れていません. [6]後述のSubpath Exportsはバージョンの詳細が書かれています.
- パッケージのサブパスのexportを指定したり(
Subpath Exports)、意図してexportしたくない内部のコードを隠匿したり環境(CJS/ESMの区別やproduction/developmentといった区別)によってパスを出し分けする(Conditional Exports)際に使えます.
参考になる記事
詳しい話は詳しい方にお任せしようと思います...
React 18.xや最新のNext.jsは問題ないのか?
では、最新のNext.js環境やReact 18.xでは同様の問題が起きないのか?が気になるところです.
実際にはNext.jsは少なくとも自分が確認している範囲だと13系以降では依存解決の方式が変わっているため同様の問題は起こらなさそうです.
@types/react@18.0.2が認識されない問題
と言いつつ、過去にNext.jsの12系で@types/react@18.0.2が同じよう認識されない問題がありました. これも同様の症状のようでした.
詳しく調べてみたところ、v12.1.5のリリース[7]に含まれている#36082のPRにおいてパス解決時にpackage.jsonを参照するようになったためSubpathの問題は解決できているようでした.
最後に
滅多にこんなトラブルには当たらないと思いますが、同様の記事もなく解決方法も書いてなかったため記録しました. なるべく依存環境は最新にした方が良いですね...
小噺: React 18以降に@types/reactに書かれたtypes@<=5.0の記述
React 18の最新の@types/reactを見にいくとConditional Exportsの条件にtypes@<=5.0の記述が見られます. これはTypeScript 4.9以降にpackage.json内部でTypeScript 5.x / 4.x向けにd.tsを出し分けたいパッケージ向けの優先度記述が変わったことによるものでした.
-
https://github.com/DefinitelyTyped/DefinitelyTyped/commit/ad4d3aa70d943375606bc36d625dbba216cec7cb ↩︎ ↩︎
-
https://github.com/vercel/next.js/blob/v10.2.3/packages/next/lib/has-necessary-dependencies.ts ↩︎
-
@types/をまとめているリポジトリ、詳細は割愛 ↩︎ -
以下のドキュメントで
moduleResolutionがnode16やbundlerの時にpackage.jsonのexportsを参照しにいくことが書かれています. この設定項目で無効化できますが、他のパッケージにも影響が出るので非推奨です. https://www.typescriptlang.org/tsconfig/#resolvePackageJsonExports ↩︎ -
https://nodejs.org/api/packages.html#main-entry-point-export ↩︎
-
https://nodejs.org/docs/latest-v12.x/api/packages.html#packages_main_entry_point_export ↩︎
ちょっと株式会社(chot-inc.com)のエンジニアブログです。 フロントエンドエンジニア募集中! カジュアル面接申し込みはこちらから chot-inc.com/recruit/iuj62owig
Discussion