TypeScriptにpluginがやってくる 使ってみよう編

## はじめに
どうも、 @Quramy です。
[前回の投稿](http://qiita.com/Quramy/items/7502f36a9ee84ef26b85)から随分日が経ってしまいましたが、この投稿はある意味で前回投稿の続編的な内容になります。

今日はTypeScript 2.3から導入される[Language Service Extensibility](https://github.com/Microsoft/TypeScript/wiki/Roadmap#23-may-2017)と呼ばれている機能についてまとめてみようと思います[^1]。

## どのような変更なのか
TypeScript Roadmapのリンクを辿っても、[https://github.com/Microsoft/TypeScript/pull/12231](https://github.com/Microsoft/TypeScript/pull/12231) に行き着くだけで、パッと見は何の機能なのかよく分かりません。
このPRの実装を眺めると、次の機能が見えてきます。

- tsconfig.jsonのcompilerOptionsに"plugins"というキーが追加されている
- pluginsに指定した内容は、TypeScript本体からresolveされる

すなわち、tsconfig.jsonに以下のように記述しておくと、

```json:tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "strict": true,
    "sourceMap": false,
    "plugins": [
      { "name": "hoge-plugin" },
      { "name": "foo-plugin" }
    ]
  }
}
```

TypeScriptがnode_modulesから、hoge-pluginやfoo-pluginをresolveしてrequireしてくれる、という意味です。

これまでのTypeScriptでは一貫して、TypeScript自体をユーザーに拡張させることを許してきませんでした。
このポリシー自体には賛否両論あるとは思いますが、ここにきてその頑なさが和らいだことになります。

## pluginにできること・できないこと
さて、次に疑問に思うことは「pluginって言うけど何ができるようになるの？」ではないでしょうか。

### transformerをイジれる訳じゃないのです
「pluggableな機構を持ったJavaScriptトランスパイラ」という文脈で頭をよぎるのは、やはりBabelではないでしょうか。
まず、誤解を生まないようにはっきりと断っておきますが、今回導入されるTypeScriptのpluginは、Babelにおけるpluginとは全くの別物です。

Babelにおけるpluginは、sourceとなるJavaScriptの抽象構文木(AST)を読み取り、必要に応じてASTを変換できます。
すなわち、トランスパイルそのものがBabel pluginの責務です。

一方、TypeScriptにもsourceに対応したASTを変換する機構としてtransformerが存在しています。
例えばReactの.tsxファイルに記述されたJSXの変換には、[src/compiler/transformers/jsx.ts](https://github.com/Microsoft/TypeScript/blob/master/src/compiler/transformers/jsx.ts)が用いられる、といった具合です。

しかし、今回紹介するTypeScript plugin機構はtransformerを拡張できる訳ではありません。
「pluginを使えばtscが生成するJavaScriptをイジれる！」とか思わないでください。
ソースコードの変換が行ないたければ、webpackのloaderやgulpのpluginを自分で作成するなりして頑張りましょう[^6]。

### Language Serviceのpluginです
トランスパイルをカスタマイズできないのであれば、pluginでは一体何ができるというのでしょうか。
その答えは「Language Serviceの拡張」です。

TypeScriptのアーキテクチャにおけるLanguage Serviceの位置づけは、次の図が一番分かりやすいです。

![langservice](https://raw.githubusercontent.com/wiki/Microsoft/TypeScript/images/architecture.png)

*[https://github.com/Microsoft/TypeScript/wiki/Architectural-Overview#layer-overview](https://github.com/Microsoft/TypeScript/wiki/Architectural-Overview#layer-overview)より*

> The "Language Service" exposes an additional layer around the core compiler pipeline that are best suiting editor-like applications. 

とあるように、主にエディタに対して、種々の機能を提供するための存在です。
詳細は後述しますが、pluginではこのLanguage Serviceを自由に差し替えることができるものの、上図からもわかるとおり、StandaloneなCompiler(いわゆるtscコマンド)や、Core Compiler APIを変更できるわけではありません。

Language ServiceがどのようなAPIを備えているかを覗いてみると、例えば次のようなメソッドが列挙されています。
確かにエディタ向け、といった風合いのAPIが並んでいるのがわかります。

```typescript
interface LanguageService {

  // 補完可能なキーワード候補の取得
  getCompletionsAtPosition(fileName: string, position: number): CompletionInfo;

  // QuickFixの取得
  getQuickInfoAtPosition(fileName: string, position: number): QuickInfo;

  // インデント幅の取得
  getIndentationAtPosition(fileName: string, position: number, options: EditorOptions | EditorSettings): number;
  // etc...
}
```

一部の例外はありますが、TypeScriptに対応したエディタ・IDEはさきほどの図中のtsserverを経由してLanguage Serviceにアクセスし、補完情報やエラー情報を取得するように実装されています。
したがって、Language Serviceのpluginを実装してしまえば、Emacs LispやVim scriptが書けなくても、これらのエディタに機能が追加できるのです。

## 背景
さて、今回のLanguage Service Pluginについて、登場の経緯等を少し書いておきたいと思います。
題して1分で知ったつもりになるLanguage Serviceの歴史。

元々は"TypeScript extensibility"([https://github.com/Microsoft/TypeScript/issues/6508](https://github.com/Microsoft/TypeScript/issues/6508))というissueとして、Roadmapに記載されていました。
すごーく雑に要約すると「TypeScript + Reactの構成で、JSXの構文チェックやpropsの補完をサポートしているんだから、Angular ComponentのTemplateもサポートしてあげたい！」という内容です（勿論他にも色々な論点があったのですけども）。

2016年の頭に作成されたこのissueはしばらくの間塩漬け状態だったのですが、Angular v2とともに整備されたAoT CompilerなどのTemplateの静的解析機構によって状況が動きました。
[vscode-ng-language-service](https://github.com/angular/vscode-ng-language-service)というVSC 向けのpluginが生みだされ、このpluginのrefactoringを行う過程でLanguage Serviceの拡張方法が整理されて冒頭のPRに収束しました。

より詳細な流れは下記あたりを追うと把握できると思います。

- https://github.com/Microsoft/TypeScript/issues/11976
- https://github.com/angular/angular/pull/13716


## 使ってみよう
ここからはpluginを導入すると何ができるかを、実例とともに紹介していきます。

### Angular
まずはAngularのLanguage Service pluginです。 
導入すると下記の機能が追加されます:

- Componentのtemplateで補完が利用可能に
- Componentのtemplateでエラーチェックが利用可能に
- Componentのtemplateでツールチップ(SignatureHelp)が利用可能に

npmで普通にインストールできます。
`@angular`とあるように、Angularチーム謹製です。
Angular本体がJiT/AoT Compileに利用している機構が流用されており、コードを動作させるよりも早くtemplateのerrorに気付けるので開発が捗ります。

```sh
npm i @angular/language-service -D
```

tsconfig.jsonを次のように編集します。

```js:tsconfig.json
{
  "compilerOptions": {
    :
    "plugins": [
      { "name": "@angular/language-service" }
    ]
  }
}
```

実際に動作させるとこんな感じです。

<img alt="app_component_ts_-_ng_and_2___No_Name__-_-_VIM__vim_.png" src="https://qiita-image-store.s3.amazonaws.com/0/8808/cf90f577-971e-b961-fc9f-61ad5d84269d.png">

「Componentには `name` というプロパティは定義されてねーぞ！」と怒られているキャプチャですね。
tsconfigの設定さえしておけば、VSCだろうとvimだろうとこの通りです。

### Vue.js
続いては [vue-ts-plugin](https://github.com/sandersn/vue-ts-plugin) です。
package名からも推測がつくとおり、Vue.js + TypeScript向けのpluginで、Vue.jsの[Single File Components](https://vuejs.org/v2/guide/single-file-components.html)開発を補助してくれます。
インストールすると、.vueファイルに対して次の機能が追加されます:

- .vueファイルを扱えるようになる。言い換えると、`import MyComponent from './my-component.vue'` がresolveされる
- .vue内の`<script>` セクション内のコードをTypeScriptとして扱う。コード中での補完や定義ジャンプが利用可能に

ちなみに、現状では`<template>`セクションや`<style>`セクションに対してのサポートは存在しないようです[^2]。

このプラグインの内部ではvue-template-compilerを利用して、`<script>`セクションのコードを抽出してLanguage Serviceで扱えるようにしています。
先のAngularが、.tsから参照されるtemplateに機能を付与していたのに対して、Vue.jsの場合は、.vueファイルからTypeScript部分を抜き出してエディタと統合しよう、というアプローチですね。
個人的には、TypeScript単体ではinvalidなファイルがあたかもvalidであるかのように見えてしまうのはあまり好きでは無いのですが、そこはフレームワーク毎の考え方もあるのでしょう。

<img alt="2__App_vue______workspaces_javascript_ts-language-service-plugins_vue-ls_src__-_VIM__vim_.png" src="https://qiita-image-store.s3.amazonaws.com/0/8808/6559b691-5baf-8a23-d620-22d66e7d27a2.png">

### tslint
最後に紹介するのは、[tslint-language-service](https://github.com/angelozerr/tslint-language-service) です。
tslintの名が示すとおり、Language Serviceのエラーチェック機能にtslintのチェック機構を追加してくれます。

なお、この記事を書いている時点（2017.04.03）ではnpmへのリリースがされていなかったので、手元で、`git clone`, `tsc`, `npm link`を実行して利用しています[^3]。

<img alt="2__main_ts____workspaces_javascript_ts-language-service-plugins_tslint-ls_src__-_VIM__vim_.png" src="https://qiita-image-store.s3.amazonaws.com/0/8808/41277654-6938-ea72-34a3-46ad4acb5860.png">

余談ですが、vimではsyntasticというプラグインにtslint連携がかれこれ2年以上も前から存在しています。
ですので、わざわざLanguage Service Pluginとして導入せずともvim上でlint結果を確認できたのですが、バッファ保存毎に`system`経由でnodeを起動するような実装であったため、とても実用に耐えるレベルではありませんでした。
一方、Language Service Pluginの場合はtsserver上にnodeプロセスが常駐するため、レスポンスは段違いです[^4]。

## おわりに
このエントリでは、TypeScript の Language Service Pluginについて概要を解説しました。

実際のpluginもいくつか紹介しました。というよりも、今回紹介した3つのpluginしか確認できなかった、というのが実状です[^5]。
Language Service好きとしては、plugin開発が盛り上がってくれるとよいなーと思っています。

このエントリを書き始めた当初は、pluginの自作方法についても記載しようかと考えていたのですが、「使いたい！」と「作りたい！」ではあまりにも想定読者が変わってしまいそうなので、[作り方編](http://qiita.com/Quramy/items/ecf83ecd4093cb7d948e)を別途用意しました。興味がある方はこちらも是非。

[^1]: 実はv2.2.1から利用可能なのですが、公式には「2.3で導入される機能」という位置づけのため、本稿でもこれにならっています。
[^2]: .vueにおけるtemplateやstyleセクションの自由度を考えると、TypeScriptのpluginとしてここに何か手を打つのは難しそうな予感。
[^3]: https://github.com/angelozerr/tslint-language-service/issues/11 にRelease準備用のissueが立っているので、`npm i tslint-language-service` でインストールできる日もそう遠くなさそう。
[^4]: tslintのAPI自体が、compiler core(`ts.Program`)を外部からセットできるため、既存のLanguage Server上で動作させるとより軽快、というのも理由の1つです。
[^5]: *2017.04.20追記* [@Hchan_mgn](https://twitter.com/Hchan_mgn) さんに https://github.com/HerringtonDarkholme/ts-css-plugin を紹介してもらいました。.cssをresolveするpluginです
[^6]: *2017.04.25追記* [[TypeScript 2.3] custom transformer を利用して実行時に型情報を参照可能にする](http://qiita.com/kimamula/items/010d1fa8fd9a9d9ca207)を用いることで、API直叩き限定ではあるものの、transformerの拡張ができるらしいです。ヤバい世界だ...