EcmaScript の中の TypeScript

ここ最近に発表された、 Babel や ESLint による TypeScript サポートによって、 TypeScript の周辺ツール回りの環境が大きく変わりました。 EcmaScript と TypeScript の境界はどんどん小さくなり、 TypeScript の理念を推し進めています。

そこで、ツールにサポートされた経緯の振り返りと、ツールの設定を再確認していきます。それぞれのツールの詳細は、検索して出てくる記事のほうが正確で詳しいため、そちらを御覧ください。
設定例のプロジェクトはこちらです。

目次

TypeScript の変遷

Types on every desk, in every home, for every JS developer

AltJS の一つとして

TypeScript は所謂 AltJS で、コンパイル・トランスパイルにより JavaScript を作成し、それを動かします。文法は JavaScript(EcmaScript) に型情報を付け加えたものになるよう工夫されています。
TypeScript の柔軟な型を上手く活用して型定義ファイル( .d.ts )を用意すれば、 JavaScript 用に書かれたライブラリにも上手く型がつき、エディタの支援を受けられます。(新しいバージョンが出る度に型の表現力は増していきます)

素の JavaScript が書きにくい問題に対しては、以前からも(同じく AltJS である) CoffeeScriptDart 1.xScala.js のように他言語からコンパイルしてくる方法もあり、乱立していました。

2019 年に入ってからの個人的な感触では、TypeScript はほぼ AltJS 界のトップになった気がします。
大工事が不要(漸進的な型付け)で過去のライブラリとの相性を第一に考慮していること(複雑な型付け)、型定義ファイルが蓄積してきたことで質と量ともに良くなったのが一つの理由だと思います。エディタとの相性も良い影響があったのでしょう。

Babel によるサポート

Babel も素の JavaScript が書きにくい問題に対する方法で、最新・将来の EcmaScript 文法を使って書いたコードをブラウザや Node 等のエンジンがサポートする文法・コードへトランスパイルするツールです。
AltJS っぽく感じますが、「将来の機能」、つまり、いずれ将来のブラウザや Node で動く(であろう)書き方で書いておくという点では少し前提が異なります。

TypeScript は JavaScript の文法の進化上にはいませんが、文法の拡張とみることはできます。(そう設計されているから)
こうした理由かはわかりませんが、半年ほど前に Babel 7 が TypeScript のトランスパイルに対応しました( TypeScript and Babel 7 )。 TypeScript 用の Babel プリセットを導入すれば、 EcmaScript の最新バージョンで書かれたコードも TypeScript のコードも、一部の些細な文法を除いて同じ手順・設定の元でトランスパイルできるようになりました。

ESLint への移行表明

TSLintESLint は、どちらともコンパイラ等では防げないエラーやアンチパターンを検知したり、文法の使い方などをチームで揃えたりするためのツールです。

TSLint と ESLint では、前者はパフォーマンスやメンテ運用に問題があることと、どちらも同じ目的で対象もほぼ同じなツールということで、 TypeScript コードを ESLint でチェックする試みは前からいくつかありました。
その他にも、 ESLint 用に開発したルールを TSLint 用に移植する必要があるとか、 TypeScript を使わない ESLint Plugin 開発者はそもそも TSLint を考慮してくれない問題もあります。

数ヶ月前、公式に @typescript-eslint を開発して徐々に TSLint から ESLint へ注力していくことが発表されました( TSLint in 2019 )。
@typescript-eslint を導入すれば、 EcmaScript の最新バージョンで書かれたコードも TypeScript のコードも同じ手順・設定の元で Lint できるようになりました。

いままでのツール設定

ここでは、 TypeScript 用の独自のツールチェインを利用していた、いままでのツール設定を振り返ります。

コンパイル for Web App

Web Application を Webpack でビルドする場合には、 ts-loader の利用をベースに、 babel-loaderfork-ts-checker-webpack-plugin を適宜利用していました。

TypeScript コード(の出力結果)についても Babel Plugin でコード変形したい場合、 ts-loader の後段に babel-loader を追加しますが、ビルドパイプラインの処理時間が延びる問題がありました。
また、元々のパイプラインの処理時間が長い場合は、 fork-ts-checker-webpack-plugin により型チェックを別プロセスに逃し、 ts-loader は transpileOnly: true でトランスパイルに専念して高速化する形をとります。
両方の手法は組み合わせられますが、その場合は ts-loader と babel-loader の役割・処理がほぼ同じで、 1 段分の処理が無駄に感じられます。

コンパイル for Library

npm パッケージ等の配布用コードを Rollup でビルドする場合には、 rollup-plugin-typescript2 を使うのが主流のようです。
私自身の利用回数が低いため辛いポイントはわかりませんが、設定などは簡単でした。ただし、 EcmaScript 用のパイプラインを別途設ける必要はありそうです。

テスト時コンパイル

ここでは Jest を使い場合を載せます。その場合は ts-jest を利用することがほとんどでした。

他のテストフレームワークではテスト実行前に tsc や ts-node 等によりビルドしていたと思います。

スタイル等

コードのスタイル等の確認・整形をする場合には、 TSLint と Prettier を併用していました。

TSLint でも EcmaScript をチェックすることができるため( "jsRules" )、コード量が少なければ TSLint と ESLint を併用する必要性はありません。
また、移植が必要と云えども ESLint にある人気のルールを第三者が TSLint で再実装していることもあり、質の違いや移植までの時間差に目を瞑れば困ることは少ないかもしれません。

Prettier は元々 EcmaScript も TypeScript もサポートしているため違いの考慮は不要です。ただし、便利に連携させるために TSLint と Prettier を橋渡しする TSLint の Plugin と Config を使っていました。

これからのツール設定

プロジェクトによって最適な設定は違ってきますが、これから例示するように設定しておけば EcmaScript と TypeScript の違いを型関連の文法以外は意識せずに開発できるはずです。

TypeScript 専用のツールを使わなくなった箇所も、エディタの適切な支援を受けるために tsconfig.json の調整が必要なことがあります。特に、 "isolatedModules": true" とし忘れるとトランスパイルに失敗する可能性があるので注意してください。
また、(当然ながら) Babel7 で @babel/preset-typescript を利用している前提になります。

設定例のプロジェクトを用意しました。

トランスパイル for Web App

Web Application を Webpack でビルドする場合には、 ts-loader ではなく babel-loader を利用し、型チェック用として fork-ts-checker-webpack-plugin を(必ず)利用します。
babel-loader では test: /\.[jt]sx?$/ のようにして TypeScript と EcmaScript ファイルの両方を通過・加工するようにしておき、一方の fork-ts-checker-webpack-plugin では TypeScript ファイルのみの型チェックを行います。

別の Babel Plugin によるコード変形が必要な状況でもトランスパイル処理の重複がなく、効率的になりました。

トランスパイル for Library

npm パッケージや配布用コードを Rollup でビルドする場合には、 rollup-plugin-babel を使う例が見つかります。tsc --noEmittsc --emitDeclarationOnly を前後に挟むなどして、ビルド時の型チェックと型定義ファイル生成を補います。
型チェックと型定義ファイル生成を両方行う場合、現状は Allow emitDeclaration and isolatedModules together · Issue #29490 という理由で tsconfig.json を 2 つ用意する必要があります。

このケースでは複雑になってしまいます…

テスト時トランスパイル

ここでも Jest を使う場合を載せます。その場合は、 Jest 24 から Babel 7 をサポートしたことにより、何も追加で設定することはありません。ただし、型チェックはしてくれなくなったため、実行前後や CI で tsc --noEmit しておくとコードの健康度を保てると思います。

他のテストフレームワークでは Babel でトランスパイルする場合の設定を確認してください。(デフォルトでサポートしているかもしれない)

スタイル等

コードのスタイル等の確認・整形をする場合には、 ESLint + @typescript-eslint と Prettier を併用するようにします。

EcmaScript の対象と同一にできるだけでなく、 React Hooks など ESLint しか公式サポートがないルールを使えたり、さらには Parser を組み合わせて .vue ファイルをも統一的に扱えるのが大きな利点です。
これぞ @typescript-eslint の成果の賜物ですね。

Prettier については、 TSLint のときと同じように、 ESLint と Prettier を橋渡しする Plugin と Config により連携させます。こちらは TSLint のそれらよりも設定しやすくなっています。

まとめ

TypeScript とツール回りの経過の説明と、 TypeScript を独自のツールチェインで利用した場合と Babel ツールチェインを利用した場合の比較を行いました。

これらの成果により TypeScript と EcmaScript とでトランスパイル方式とツールチェインをほぼ統一でき、設定の違いや重複に悩むことを減らせます。(少しの間はバグ等が見つかりそうですが…)
また、統一により EcmaScript ベースのプロジェクトから TypeScript プロジェクトへの移行の障壁が小さくなります。

TypeScript は、文法だけの拡張だったものが、ツールチェインの統一によって本当の意味で “a typed superset of JavaScript” という理念を達成したのかなと思います。

参照記事