tmegos blog

I love school idols / Web developer

Web向けのプロジェクトに混入した@types/nodeをany化する

現象

Web向けのプロジェクトにNode.jsの型が混入し、setTimeoutなどの型が壊れてビルドエラーになる

TS2322: Type 'Timeout' is not assignable to type 'number'.

これはライブラリをインストールした際、そのライブラリの型定義ファイルに@types/nodeが含まれていてこのようなケースになることがあります

TypeScriptのissueに登録されていて、ワークアラウンドは提案されているものの、根本的な解決には至っていないようです

github.com

今回のゴール

  • 開発中、ライブラリの型がわかる
  • ビルドに失敗しない

対応策

tsconfig.jsontypeRoots@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が入っています

create-react-app.dev

"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を使って読み込み先を変える方法を思いつきました

qiita.com