Web向けのプロジェクトに混入した@types/nodeをany化する
現象
Web向けのプロジェクトにNode.jsの型が混入し、setTimeoutなどの型が壊れてビルドエラーになる
TS2322: Type 'Timeout' is not assignable to type 'number'.
これはライブラリをインストールした際、そのライブラリの型定義ファイルに@types/nodeが含まれていてこのようなケースになることがあります
TypeScriptのissueに登録されていて、ワークアラウンドは提案されているものの、根本的な解決には至っていないようです
今回のゴール
- 開発中、ライブラリの型がわかる
- ビルドに失敗しない
対応策
tsconfig.jsonのtypeRootsで@types/nodeの読み込み先を変えます
declare module 'node'でNode.jsの型を上書きします
src/typings/node/index.ts
declare module 'node'
tsconfig.json
"typeRoots": ["src/typings", "node_modules/@types"],
このようにすることで、実際の@types/nodeが読み込まれず、型が壊れること無くビルドができます
調査ログ
方針
@types/nodeのany化
環境
- TypeScript: 3.7.4
- あるライブラリXXXの
XXX.d.ts
先頭のトリプルスラッシュディレクティブで@types/nodeが読み込まれる
/// <reference types="node" /> type...
@types/nodeがどのように読み込まれるのかを調べるため、traceResolutionを有効にしてビルドします
単純にライブラリをインストールした後
ビルド失敗
======== Module name 'XXX' was successfully resolved to 'node_modules/XXX/XXX.d.ts' with Package ID 'XXX/XXX.d.ts@x.y.z'. ======== ======== Resolving type reference directive 'node', containing file 'node_modules/XXX/XXX.d.ts', root directory 'node_modules/@types. ======== Resolving with primary search path 'node_modules/@types. Found 'package.json' at 'node_modules/@types/node/package.json'.
ライブラリの型を読み込んだ後、nodeを探して無事に見つかって@types/nodeが読み込まれていますね😇
pathsで@types/nodeの読み込み先を変える
ビルド失敗
tsconfig.json
"paths": { "node": ["typings/node.d.ts"] },
src/typings/node.d.ts
declare module 'node'
======== Module name 'XXX' was successfully resolved to 'node_modules/XXX/XXX.d.ts' with Package ID 'XXX/XXX.d.ts@x.y.z'. ======== ======== Resolving type reference directive 'node', containing file 'node_modules/XXX/XXX.d.ts', root directory 'node_modules/@types. ======== Resolving with primary search path 'node_modules/@types. Found 'package.json' at 'node_modules/@types/node/package.json'.
変わらないですね…
typeRootsに定義
ビルド失敗
tsconfig.json
"typeRoots": ["src/typings", "node_modules/@types"],
======== Module name 'XXX' was successfully resolved to 'node_modules/XXX/XXX.d.ts' with Package ID 'XXX/XXX.d.ts@x.y.z'. ======== ======== Resolving type reference directive 'node', containing file 'node_modules/XXX/XXX.d.ts', root directory 'src/typings, node_modules/@types'. ======== Resolving with primary search path 'src/typings, node_modules/@types'. Found 'package.json' at 'node_modules/@types/node/package.json'.
無事に@types/nodeを見つけてしまいましたね😇
typeRootsからnode_modules/@typesを消すとどうなるでしょうか
Resolving with primary search path 'src/typings'. Looking up in 'node_modules' folder, initial location 'node_modules/XXX'. File 'node_modules/XXX/node_modules/node.d.ts' does not exist. (中略)... ======== Type reference directive 'node' was successfully resolved to 'node_modules/XXX/node_modules/@types/node/ts3.2/index.d.ts' with Package ID '@types/node/ts3.2/index.d.ts@12.12.35', primary: false. ========
src/typingsからは見つからず、ライブラリ内のnode_modulesから見つけましたね😇
typeRootsに指定しているディレクトリの構造をnode_modules/@types以下に揃える
ビルド成功
node_modules/@types以下を見ていると、ライブラリ名ごとにディレクトリが別れていると思いました
なので、typeRootsに指定したディレクトリも同じようにライブラリ名でディレクトリを作ってみます
src/typings/node/index.ts
declare module 'node'
tsconfig.json
"typeRoots": ["src/typings", "node_modules/@types"],
======== Module name 'XXX' was successfully resolved to 'node_modules/XXX/XXX.d.ts' with Package ID 'XXX/XXX.d.ts@x.y.z'. ======== ======== Resolving type reference directive 'node', containing file 'node_modules/XXX/XXX.d.ts', root directory 'src/typings, node_modules/@types'. ======== File 'src/typings/node/package.json' does not exist. File 'src/typings/node/index.d.ts' exist - use it as a name resolution result. Resolving real path for 'src/typings/node/index.d.ts', result 'src/typings/node/index.d.ts'. ======== Type reference directive 'node' was successfully resolved to 'src/typings/node/index.d.ts', primary: true. ========
自分で定義した方の'node'の型を読み込んでくれた!🎉
おまけ create react appで@types/nodeが入った場合どうなるのか
--template typescriptでTypeScriptを入れます
このときのtsconfig.jsonには"skipLibCheck": trueが入っています
"skipLibCheck": trueを消してビルドしてみると…?
$ react-scripts build The following changes are being made to your tsconfig.json file: - compilerOptions.skipLibCheck to be suggested value: true (this can be changed)
自動的にtrueになりました。強い。
参考
以下の記事でtypeRootsがトリプルスラッシュディレクティブにしか効かないことを読んで、
typeRootsを使って読み込み先を変える方法を思いつきました