概要
TypeScriptの開発において、型定義ファイルが用意されてないnpmライブラリを使うのは厄介なものです。
無理やり require("...") でJSのまま読み込ませてもいいですが、ちゃんとした型定義を使用したくなるときもあります。
作成した型定義ファイルは元のnpmライブラリに取り込んでもらい package.json の types で指定してもらう方法 1 と、 DefinitelyTyped に取り込んでもらい @types/*** パッケージとしてnpm installする方法がありますが、どちらも取り込んでもらうためにはPull Requestを投げたり、ライブラリのメンテナに新しいバージョンとしてリリースしてもらったりで時間がかかってしまいます。
Pull Requestは投げつつ、それが取り込まれてリリースされるまでの間、自分のプロジェクトで作った型定義ファイルを利用できる方法はないものかと思って調べてたんですが、なかなかちょっと面倒くさかったのでここにまとめておこうと思います。
実行環境
- Node.js 10.5.0
- TypeScript 2.9.2
やってみる
今回はDefinitelyTypedのREADMEでもいい型定義ファイルの例としてなぜか推奨されている base64-js を利用します。 2
For a good example package, see base64-js.
https://github.com/DefinitelyTyped/DefinitelyTyped#create-a-new-package
base64-js の型定義
base64-jsは公開されている関数が3つしかないため、型定義ファイル も次のようなシンプルなものです。
// Type definitions for base64-js 1.2
// Project: https://github.com/beatgammit/base64-js
// Definitions by: Peter Safranek <https://github.com/pe8ter>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
export function byteLength(encoded: string): number;
export function toByteArray(encoded: string): Uint8Array;
export function fromByteArray(bytes: Uint8Array): string;
今回はこのライブラリを利用した簡単なソースコードで実験を行います。
import { toByteArray } from "base64-js";
import { TextDecoder } from "util";
const encoded = "44GT44KT44Gr44Gh44Gv"; // "こんにちは" をBase64エンコードしたもの
console.log(`encoded: ${encoded}`);
const decoded = new TextDecoder().decode(toByteArray(encoded));
console.log(`decoded: ${decoded}`);
設定方法
今回は独自の型定義ファイルを配置する方法を確認するため、npmの @types/base64-js は利用せず、この型定義ファイルをプロジェクト内に配置することにします。
プロジェクト直下に types ディレクトリを作成し、/types/base64-js/index.d.ts に先ほどの型定義を配置しましたが、このままではコンパイルすることはできません。
tsc が型定義ファイルの位置を知らないためです。
$ npm run build
> original-definition-in-typescript-project-sample@1.0.0 build /Users/mtgto/work/js/original-definition-in-typescript-project-sample
> tsc --project tsconfig.json
src/index.ts:1:29 - error TS7016: Could not find a declaration file for module 'base64-js'. '/Users/mtgto/work/js/original-definition-in-typescript-project-sample/node_modules/base64-js/index.js' implicitly has an 'any' type.
Try `npm install @types/base64-js` if it exists or add a new declaration (.d.ts) file containing `declare module 'base64-js';`
1 import { toByteArray } from "base64-js";
~~~~~~~~~~~
これをコンパイラに教えてあげるため、 tsconfig.json の typeRoots, baseUrl, paths を次のように変更します( typeRoots のデフォルトは [] です)。
{
"compilerOptions": {
"baseUrl": "./",
"paths": {"base64-js": ["types/base64-js"]},
"typeRoots": ["types", "node_modules/@types"],
}
}
paths によって import * as base64 from "base64-js" のように書くことが許されるようになります。
baseUrl は paths を使う際に設定する必要があります。ソース内で相対パスではなくimportしたいときなんかは src を指定したりすることがあるようです。 3
また paths は "paths": {"*": ["types/*"]}, のように書くことで複数の独自型定義をまとめて設定することもできます。
typeRoots 4 は実は今回のケースでは書かなくてもコンパイルできます。もうすでに独自の typeRoots を指定しており、新たに今回の /types も使用したい場合に追加する必要があります。
すでに型定義ファイルをもっているライブラリを上書きする形でしたいときに独自型定義を使いたいときに設定するのかなとおもって @types/base64-js をインストールした上で typeRoots から types ディレクトリを消してみたりしたのですが、ちゃんと types/base64-js/index.d.ts にしかない関数を呼んだコードをコンパイルすることができました。なのでライブラリの型定義ファイルが古くてちょっとだけ自分のプロジェクト内だけでも型定義を書き換えたいときに使ったりできそうです。
各項目について詳しくは公式のtsconfig.jsonの説明 も参考にしてください。
実行してみる
無事に独自型定義でコンパイルが通ったので実行してみます。
$ node .
encoded: 44GT44KT44Gr44Gh44Gv
decoded: こんにちは
動きました。以上です。
サンプルコード
今回の記事で紹介したコードを含むプロジェクトを↓においています。
補足
試行錯誤してたときにだめだったことを書いておきます。
よくない例: 独自型定義ファイルを *.ts から相対パスで参照する
コンパイルは通りますが、JSが参照するライブラリが相対パス表記になってしまい、実行時に「ライブラリが見つからない」とnodeに怒られました。
コンパイル後に .js ファイル内の相対パスを書き換える強引な手もありますが、SourceMapとの関連がずれることもあるでしょうし、やはりおすすめできません。
おすすめしない例: 独自型定義ファイルを node_modules/@types に配置する
.gitignore で node_modules 以下を管理していることは多いのでおすすめできません。
まとめ
- TypeScriptの独自型定義を置くときには
tsconfig.jsonのbaseUrl,pathsに絶対パスで指定できるように設定をしましょう-
typeRootsを使っているときはtypeRootsにも追加しましょう
-
- もしよければライブラリかDefinitelyTypedにPRを送り、他の人に貢献しましょう!
-
https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#including-declarations-in-your-npm-package ↩
-
型定義を新たに作ってみようと思うときに参考にしようとしてもbase64-jsはシンプルすぎて役に立たないことのほうが多いと思います。関数しかないライブラリなら参考になりますけど。 ↩
-
https://www.typescriptlang.org/docs/handbook/tsconfig-json.html#types-typeroots-and-types ↩