LoginSignup
24
7

More than 5 years have passed since last update.

TypeScript の FileReader に手を入れた話

Posted at

TypeScript 2.7 で FileReader を使うコードを書くときに型情報が不足しているのが気になったので Microsoft/TSJS-lib-generatorPR #398 を出して TypeScript 2.9 に取り込んでもらった話です。

事の発端は TypeScript 2.7 で書いていたこのようなコードです。

const reader = new FileReader()
reader.onload = (event) => {
  // Property 'result' does not exist on type 'EventTarget'.
  const url = event.target.result
}
reader.readAsFileURL(file)

MDN の FileReader.onload にあるようなシンプルなファイル読み込みなのですが、これを書くと「event.target.result が無いよ」と言われてしまいました。

同じような問題を訴えている TypeScript の issue もあったので、よっしゃ黙らせたるってことで、TypeScript 自体の FileReader に手を入れてみることにしました。

どこに手を入れればいいのか探す

FileReader.onloadnode_modules/typescript/lib/lib.dom.d.ts でこのように定義されています。

node_modules/typescript/lib/lib.dom.d.ts
interface FileReader extends EventTarget {
    // ...
    onload: ((this: FileReader, ev: ProgressEvent) => any) | null;
    // ...
}

この evtarget: EventTarget | null が生えています。その targetresult を生やせれば、event.target.result と書いても何も言われなくなります。イメージとしてはこんな形を望んでいます。

interface FileReader extends EventTarget {
    // ...
    onload: ((this: FileReader, ev: FileReaderProgressEvent) => any) | null;
    // ...
}

interface FileReaderProgressEvent extends ProgressEvent {
    result: FileReader | null;
}

こうするには TypeScript に入っている lib.dom.d.ts に手を入れる必要があります。そうするにはどうすればいいでしょうか。

TypeScript の README にある Contribute という項目からは CONTRIBUTING.md へのリンクが貼られています。ここに書いてあることに従っておけばよさそうです。

この資料の中を lib で検索すると、すぐに Contributing lib.d.ts fixes という項目に引っかかるはずです。そこには

と書いてあります。lib/README.md
によると、src/lib/dom.generated.d.tslib/lib.dom.d.ts を生成するために使うファイルです。

というわけで lib.dom.d.ts を書き換えるには src/lib/dom.generated.d.ts を書き換える必要があり、それを書き換えるためには TSJS-lib-generator に手を入れる必要があるというわけです。ふー、長かった。

TSJS-lib-generator とは

TSJS-lib-generator とは、リポジトリの説明にある通り、DOM に関連した TypeScript と JavaScript ライブラリファイルを生成するツールです。この Contribution Guidelines に、やるべきことが丁寧に書いてあります。短いので、和訳したものを置いておきます。


Contribution Guidelines

TypeScript リポジトリの dom.generated.d.ts, webworker.generated.d.ts, dom.iterable.generated.d.ts ファイルをベースラインとして使います。プルリクエストごとにスクリプトを走らせて、生成されたファイルとベースラインのファイルを比較します。テストを通すためにプルリクエストではベースラインを更新してください。

通常の変更は json ファイルを変更するだけで済みます。型の生成には主に addedTypes.json, overridingTypes.json, removedTyes.json の3つのファイルを使います。comments.json ファイルで型にコメントを足すことができます。最後に knownWorkerTypes.json ではどの型が WebWorker 環境で使えるかを決めます。

それぞれのファイルのフォーマットは内容から察してください。

プルリクエストを送る一般的なステップは:

  1. TypeScript リポジトリ の issue を作るか参照する
  2. 足りない要素を inputfiles/addedTypes.json に追加したり、inputfiles/overridingTypes.json で要素を上書きしたり、inputfiles/removedTypes.json で要素を削除する
  3. ローカルでビルドスクリプトを実行して、新しい dom.generated.d.tswebworker.generated.d.ts を作る
  4. generated フォルダに出力されたファイルを使って baselines フォルダのファイルを更新する (npm run baseline-accept)

(訳注:原文ではステップ 0 から始まるのですが Qiita だと 1 に書き換えられてしまうようです)


以上、和訳でした。というわけで、このステップをたどって FileReader.onload の引数の型を書き換えていきます。

1. TypeScript の issue を作るか参照する

書いてある通りです。この時は FileReader.onLoad/onLoadEnd event argument has no target.result property という issue があったので、これを参照する形にしました。

2. json ファイルを編集する

FileReader.onload の型を変えるために inputfiles/overridingTypes.json を編集します。inputfiles/overridingTypes.json の中を探すと FileReader についてこのように書かれています。

inputfiles/overridingTypes.json
            "FileReader": {
                "name": "FileReader",
                "properties": {
                    "property": {
                        "result": {
                            "name": "result",
                            "override-type": "any"
                        }
                     }
                }
            },

今回は onload(this: FileReader, ev: FileReaderProgressEvent) => any にしたいので、このように書きます。

inputfiles/overridingTypes.json
            "FileReader": {
                "name": "FileReader",
                "properties": {
                    "property": {
                        "onload": {
                            "name": "onload",
                            "override-type": "(this: FileReader, ev: FileReaderProgressEvent) => any"
                        },
                        "result": {
                            "name": "result",
                            "override-type": "any"
                        }
                    }
                }
            }

ここについて今の TSJS-lib-generator を確認したところ、書き方が変わっているようでした。

さらに、今回新しく FileReaderProgressEvent を追加するので inputfiles/addedTypes.json に以下の内容を追加します。

inputfiles/addedTypes.json
{
    "interfaces": {
        "interface": {
            // ...
            "FileReaderProgressEvent": {
                "name": "FileReaderProgressEvent",
                "extends": "ProgressEvent",
                "properties": {
                    "property": {
                        "name": {
                            "name": "target",
                            "read-only": 1,
                            "override-type": "FileReader | null"
                        }
                    }
                },
                "no-interface-object": "1"
            },
            // ...
        }
    }
}

name で追加する型、properties でその型が持つプロパティを指定しているなど、このファイルについてこれといって定義はないのですが、意味は推測できると思います。ひとつ注意したいのは "no-interface-object": "1" です。これは PR を出したあとにレビューで指摘されて追加したのですが、これがないと余計なオブジェクトが宣言されてしまうようです。

3. 新しい dom.generated.d.ts と webworker.generated.d.ts を作る

型を定義する json を書けたので、ビルドスクリプトを実行して dom.generated.d.ts と webworker.generated.d.ts を作ります。ビルドスクリプトというのは README にある通り npm run build で実行できます。

生成した型定義ファイルに問題があれば json を調整します。それらしい型定義ができたら次のステップに進みます。

4. ベースラインを更新する

TSJS-lib-generator では、生成された型定義ファイルをベースラインの型定義と比較することで意図しない変更を検出するようになっています。ここでは型定義ファイルを書き換えたいので npm run baseline-accept を実行します。これで、生成した型定義ファイルがベースラインに反映されます。

PR を出して accept してもらう

ここまでのステップをたどることで TSJS-lib-generator に PR を出せる状態になりました!それらしい内容を書いて PR を出してみます。私が Microsoft/TSJS-lib-generator#398 を出した時はかなりシンプルな内容で出しました。

PR を出すと Microsoft の bot が CLA にサインするように求めてくるので、サインします。

image.png

ここまで済めば、レビュワーの方がなにかしらレビューしてくれるはずです。内容に問題がなければ accept されて、そのうち TypeScript 自体に取り込まれることでしょう。

まとめ

TypeScript の型定義に手を入れる方法を説明してきました。仕組みが分かれば誰でも触れそうだなというのが伝われば幸いです。

オチってわけではないのですが、実はこの FileReader.onload に対する変更は、TypeScript 3.0 でワケあって取り消されています。便利だったと思うんですけどね。

11日目は tsukakei さんです。よろしくお願いします!

24
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
7