Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

はじめに

どうも、 @Quramy です。
前回の投稿から随分日が経ってしまいましたが、この投稿はある意味で前回投稿の続編的な内容になります。

今日はTypeScript 2.3から導入されるLanguage Service Extensibilityと呼ばれている機能についてまとめてみようと思います1

どのような変更なのか

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

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

すなわち、tsconfig.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が用いられる、といった具合です。

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

Language Serviceのpluginです

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

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

langservice

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が並んでいるのがわかります。

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)というissueとして、Roadmapに記載されていました。
すごーく雑に要約すると「TypeScript + Reactの構成で、JSXの構文チェックやpropsの補完をサポートしているんだから、Angular ComponentのTemplateもサポートしてあげたい!」という内容です(勿論他にも色々な論点があったのですけども)。

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

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

使ってみよう

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

Angular

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

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

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

npm i @angular/language-service -D

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

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

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

app_component_ts_-_ng_and_2___No_Name__-_-_VIM__vim_.png

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

Vue.js

続いては vue-ts-plugin です。
package名からも推測がつくとおり、Vue.js + TypeScript向けのpluginで、Vue.jsのSingle File Components開発を補助してくれます。
インストールすると、.vueファイルに対して次の機能が追加されます:

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

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

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

2__App_vue______workspaces_javascript_ts-language-service-plugins_vue-ls_src__-_VIM__vim_.png

tslint

最後に紹介するのは、tslint-language-service です。
tslintの名が示すとおり、Language Serviceのエラーチェック機能にtslintのチェック機構を追加してくれます。

なお、この記事を書いている時点(2017.04.03)ではnpmへのリリースがされていなかったので、手元で、git clone, tsc, npm linkを実行して利用しています4

2__main_ts____workspaces_javascript_ts-language-service-plugins_tslint-ls_src__-_VIM__vim_.png

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

おわりに

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

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

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


  1. 実はv2.2.1から利用可能なのですが、公式には「2.3で導入される機能」という位置づけのため、本稿でもこれにならっています。 

  2. 2017.04.25追記 [TypeScript 2.3] custom transformer を利用して実行時に型情報を参照可能にするを用いることで、API直叩き限定ではあるものの、transformerの拡張ができるらしいです。ヤバい世界だ... 

  3. .vueにおけるtemplateやstyleセクションの自由度を考えると、TypeScriptのpluginとしてここに何か手を打つのは難しそうな予感。 

  4. https://github.com/angelozerr/tslint-language-service/issues/11 にRelease準備用のissueが立っているので、npm i tslint-language-service でインストールできる日もそう遠くなさそう。 

  5. tslintのAPI自体が、compiler core(ts.Program)を外部からセットできるため、既存のLanguage Server上で動作させるとより軽快、というのも理由の1つです。 

  6. 2017.04.20追記 @Hchan_mgn さんに https://github.com/HerringtonDarkholme/ts-css-plugin を紹介してもらいました。.cssをresolveするpluginです 

Quramy
Front-end web developer. TypeScript, Angular and Vim, weapon of choice.
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away