おはようございますわかめです。
TypeScript 1.6でnode_modulesからの依存性解決が入りました。
まだ.d.tsより.tsが優先してlookupされる不具合などがありますが、実質上、型定義ファイルを自然な形でバンドルできるようになったのです。
npm installして使えば型定義が有効になっている。暖かいですね。
過渡期の機能の常として、複数のやり方がありいくつかのワークアラウンドが必要な場合があります。
ですので、ここではわかめが考える現在最も便利な運用について意見を述べていきます。
TL;DR
- ライブラリ作成側は
- 自力で.d.tsをメンテできる場合のみnpmパッケージに.d.tsをバンドルする
- 非TypeScriptユーザかつTypeScriptの型について詳しくなければ今のままDefinitelyTypedに丸投げのほうがよい
- .ts はnpm packageに含めない
- package rootにindex.d.tsを置くか、package.jsonのtypingsにエントリーポイントとなる型定義ファイル名を書く
-
declare module "hoge"
を使わず、1 .jsファイル = 1 .d.tsファイル の形式を利用する- 要するに
tsc --declaration
での出力をそのまま使うと良い
- 要するに
- 自分のライブラリに関する型定義のみ提供する
- 依存するライブラリの型定義の面倒は明示的には見ない
- 自力で.d.tsをメンテできる場合のみnpmパッケージに.d.tsをバンドルする
- ライブラリ利用側は
- npmパッケージに型定義ファイルがバンドルされているか調べる
- index.d.tsの存在チェックかpackage.jsonのtypingsプロパティ
- 不足する型定義ファイルをtsdやdtsmを利用して補う
- 多くの場合孫レベルを含めた面倒を見ないとならない
- 何が不足しているかはコンパイルしてみて、エラーメッセージから判別する(つらい
- npmパッケージに型定義ファイルがバンドルされているか調べる
機能の解説
TypeScript 1.6から、 node_modules
からのライブラリの読み込みが可能になりました。
今まで、TypeScriptは --module commonjs
をサポートしてはいましたがNode.js式のライブラリの解決はできませんでした。
TypeScript 1.6からはそこが改善され、 --module commonjs
を指定した場合か、--moduleResolution node
を指定した場合、 import * as hoge from "hoge";
のようなコードに対して node_modules/hoge
からモジュールを発見してくれるようになります。
これにあわせてnpmパッケージにバンドルされている.d.tsファイルが自動的に検出されるようになったため、dtsmやtsdを使ってDefinitelyTypedのリポジトリから別途型定義ファイルを持ってくる手間が不要になります。
同時に、DefinitelyTypedではライブラリの最新のバージョンに対してのみ型定義ファイルをメンテする方針ですが、npmパッケージにバンドルされることにより利用するバージョンに対応した型定義ファイルが利用できるようになるため、自分のペースで依存関係のバージョンを更新することがより容易になります。
逆にいうと、自分のコードにマッチした型定義ファイルをメンテナンスできないのであれば.d.tsをバンドルするべきではありません。(これはTypeScriptでライブラリを書いている人には無用の心配ですね)
この方法が広く利用されるようになれば、同一ライブラリのメジャーバージョン違いが依存先に混在していた場合にも上手くコンパイルできるようになるでしょう。
モジュール名解決のルール
Issueに書かれた擬似コードによると、モジュール検出のルールは概ね以下のとおりです。
ある、 hoge
や ./hoge
というモジュールの解決について…
- ambientな(
declare module "hoge"
形式な )モジュールの定義が存在する場合それを使う -
./
や../
でモジュール名が始まる場合、同名のファイルやディレクトリがあるかを探す- 今までと違い
./hoge/index.ts
は./hoge
にマッチするようになった。もちろん./hoge.ts
にもマッチする。./hoge.d.ts
や./hoge.tsx
もマッチする。
- 今までと違い
-
hoge/package.json
が存在する場合、package.jsonの情報を参照しようと試みる-
typings
プロパティがあれば、そこに書いてある型定義ファイルを参照する。例えばhoge/package.json
が{"typings": "./lib/hoge.d.ts"}
だった場合、hoge/lib/hoge.d.ts
が参照される。
-
-
hoge/index
が参照される。優先度は.d.tsのみ読み込む場合はindex.d.ts
、それ以外の場合はindex.ts
>index.tsx
>index.d.ts
となる。
制限
-
declare module "hoge"
形式の定義は利用できないerror TS2306: File 'node_modules/fuga/fuga.d.ts' is not a module.
- = DefinitelyTyped上の自分のライブラリの型定義ファイルをコピーしてきてもそのままでは動作しない
- reference comment (
/// <reference path="./fuga.d.ts" />
) は利用できないerror TS2654: Exported external package typings file cannot contain tripleslash references. Please contact the package author to update the package definition.
- コンパイル時にだけ参照したい型定義ファイルが読み込まれるようにする。そのためにtsconfig.jsonを利用する (参考)
- さもなくば、reference commentを集めたコンパイル時entry pointな.tsファイルを用意してもいいかも オススメしないけど
必要なワークアラウンド
- TypeScriptコンパイル時のオプション基本コンボは
--declaration --module commonjs --sourceMap --inlineSources
- npm publishする時に .ts は出荷しない
- 理由:.tsが意図せず利用側で再コンパイルされちゃうから
- .gitignoreで .js と .js.map と .d.ts はignoreする
- .npmignoreで .ts はignore する
-
--sourceMap
を利用する場合、--inlineSources
を併用する- npmで.tsをpublishしないので、.js.map内に.tsのデータ自体を持たないとスタックトレースが見難くなったりする
- リファレンスコメント (
/// <reference path="./typings/jquery/jquery.d.ts" />
) を利用しない- tsconfig.jsonに依存する型定義ファイルの参照を押し込み、tsconfig.json経由でコンパイルする
まだ残ってる問題
- ライブラリが依存しているライブラリ(大本の利用側から見ると孫ライブラリ)が要求する型定義ファイルの解決を大本の利用側で解決して整合性を取ってやらねばならない。
- なので、型定義ファイルを必要とする孫となるライブラリの数はなるべく絞ったほうが良い
- 孫ファイルが.d.tsをバンドルしていて型定義ファイルを明示的に補わなくて良い場合は気にする必要はない
- もしくは、孫となるライブラリがexportしている型をなるべくライブラリ側でexportしないようにする
- 型をexportしなければ利用側では孫ライブラリの型を気にしなくてよくなる
- なので、型定義ファイルを必要とする孫となるライブラリの数はなるべく絞ったほうが良い
利用する側に求められること
- 利用するライブラリが型定義を持っているかを調べる
- index.d.ts か package.json に typings を持っているか
- 持っていない場合、dtsmやtsdを使ったりして型定義ファイルを入手してくる
- ライブラリを利用してコンパイルするなどしてみて、型が不足する旨のエラーが出るかを確かめる
- 不足がある場合、dtsmやtsdを使ったりして型定義ファイルを入手してくる
これだけです。今までは2番だけ行えばOKでしたね。環境構築の時点では手間が若干増えています。
型定義ファイルがバンドルされている場合でも、declare module "hoge"
形式の型定義ファイルが先に読み込まれている場合そちらが利用されてしまうので注意が必要です。
よくある質問
Q. DefinitelyTypedに僕のライブラリの型定義があるんだけど、あれを持ってきても動くの?
A. 動かないよ!ambient module declarationは許可されないよ! error TS2306: File 'node_modules/fuga/index.d.ts' is not a module.
とか言われちゃうよ!
Q. TypeScriptの型定義ファイルって結構Breaking Change入るの?安定してる?
A. ちょいちょいあります。しかし、それは"より強固なチェック"のためのBreaking Changeで、文法の変更によるものではないはずです。
つまり、適切な型定義ファイルを最初から作れていればあまり気にする必要はないでしょう。
Q. tsconfig.json使わないとダメ?
A. 使わなくてもいいけど、IDEが参照してくれたりして楽なので作ったほうがいいと思うよ。 tsc -p ./
でコンパイルできるプロジェクトは健全だと思うよ。
Q. DefinitelyTypedでよくね?バンドルする利点何かあるの?
A. バンドルする場合、インストールしたバージョンに適した型定義ファイルが必ず手に入るという利点があります。
DefinitelyTypedではあるライブラリの最新のバージョンの定義しかメンテナンスを頑張らない方針です。つまり、DefinitelyTyped上の型定義ファイルは新しいバージョンに進んでいってしまうので、古いバージョンの型定義でうまく欲しいものに合致するものを探すのは大変です。
バンドルすればその心配はありません。パッケージについてくる型定義ファイルを常に使えば良いのです。
Q. 何かサンプルある?サンプル!サンプル!
A. commandpostのtsconfig.jsonと.gitignoreと.npmignore見ればいいよ!commandpostの利用例はprhとかがあるよ!
Q. どうやって上手く読み込まれているかテストすればいいの?
A. --listFiles
オプションていうのがある。利用側コードを書いてみて、--listFiles
付きでコンパイルしてみて node_modules/hoge/index.d.ts
みたいな感じで自分がリリースした型定義ファイルがリストに現れればOK。
追加で質問があればコメントかTwitterで教えてね!