Islands in the byte stream

Technical notes by a software engineer

TypeScriptの型情報を利用したCustom Transformerの現状確認

TypeScriptのコンパイラ・プラグインとして振る舞いASTの操作を行えるcustom transformer (AST preprocessor) が実装されたのは TypeScript 2.4 (2017年) でした。

そのときの様子は次のエントリに非常によくまとまっています。

[TypeScript 2.4] custom transformer を利用して実行時に型情報を参照可能にする - Qiita

さて、現在のTypeScript v3.3 でのTransformer APIの状況はというと、TypeScript compiler本体としては依然としてドキュメントに乗ってない水準の扱いです。特に、コンパイラAPI そのままではtransformerは型情報を利用できません。ts.TransformerFactory に渡される ts.TransformationContextts.Programts.TypeChecker が存在しないためです。これは、TypeScriptコンパイラ開発チームによれば意図的な設計ということです。

Include TypeChecker in TransformationContext so that custom transformers can use type information · Issue #25147 · Microsoft/TypeScript

(あると便利だから再検討してほしい!とコメントはしましたが、望みは薄いでしょう)

つまり、標準的なtransformerができることは、babel pluginとできることに本質的な違いがない、ということで、これではあまり面白くないですね。

しかし先に紹介したエントリではコンパイル時型情報を実行時型情報(=JavaScriptのオブジェクト)に変化しています。コンパイラAPIを駆使すればtransformerが型情報も扱えるということですね。しかしそれはtransformer用にカスタムコンパイラを作ることに等しいため、transformerは開発するのもテストするのも利用するのも難易度が高い状態です。

とはいえ、2019年現在では、有志によって開発されたプラガブルなカスタムコンパイラでtransformerの開発・利用が少しだけ楽になりました。

  • ts-loader
    • getCustomTransformers はtransformerに ts.Program を与えます
    • ts.Program#getTypeChecker()ts.TypeChecker にアクセスできるので、これを通じて型情報を利用できます
  • ttypescript (TypeScript with Transformers)
    • TypeScript compilerのかわりに利用できるカスタムコンパイラで、tsconfig.jsonにtransformerを設定できます
      • 設定できるtransformerは ts.Programts.TypeChecker を受けとるようにできます
    • ts-node--typescript オプションでカスタムコンパイラを設定できるため、 ts-node --typescript=ttypescript とすると ts-node でも型情報を利用するtransformerを利用できます
    • ts-loadertypescript オプションで ttypescript を利用できます

このあたりは先のエントリの作者による ts-transform-keys のREADMEに詳しく載っています。

https://github.com/kimamula/ts-transformer-keys

とはいえ、有志によるカスタムコンパイラに依存しすぎるのもあまり望ましくないので、できれば標準のコンパイラAPIとして型情報へのアクセス方法を解放してほしいところです。

Transformerが型情報にアクセスできると何ができるか

ぱっと考えただけですが、次のようなことができます。

まず、TypeScriptの型情報を React の prop types に変換できるはずです。通常であればTypeScriptの型情報はコンパイル時に失われるので次のような typeToPropTypes<T>() は実装しようがないのですが、transformerがこの関数呼び出しをprop typesに置き換えてしまえばいいというわけです(まったく試していませんが、このアイデアを実装したtransformerはすでにあるようです https://github.com/joelday/ts-proptypes-transformer )。

// これはイメージです
interface Props { /* ... */ }

cass SomeComponent extends React.Component<Props> {
  static redonly propTypes = typeToPropTypes<Props>();
}

次に、TypeScriptの型情報をGraphQLのクエリ(クエリフラグメント)に変換できるのではないかと考えています。

// これはイメージです
interface Tweet {
  author: { id };
  content: string;
  tweetedAt: Date;
}

const GetTweet = gql`
  query {
    tweets {
      edges {
       // { author { id }; content; tweetedAt } に変換する
        ...${typeToQueryFragment<Tweet>()}
      }
    }
  }
`;

というわけで、TypeScriptの型情報を利用したtransformerを安定して開発・利用できるようになるといろいろ夢がありますね。ただし、繰り返しになりますが、現在はその利用のためにTypeScriptのカスタムコンパイラを作る必要があります。プロダクションに入れる際は自己責任でどうぞ。