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"
}
exports
について
package.jsonのここまでたびたび出てきた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
の問題は解決できているようでした.
最後に
滅多にこんなトラブルには当たらないと思いますが、同様の記事もなく解決方法も書いてなかったため記録しました. なるべく依存環境は最新にした方が良いですね...
types@<=5.0
の記述
小噺: React 18以降に@types/reactに書かれた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