TypeScript の使い方
JavaScript はもともと大きなプログラムを記述するのにはあまり向いていない言語です。 そこで、JavaScript に型による強い制限を加えて、メンテナンス性を向上させようとする試みがあります。 動的型付け言語である JavaScript に型による制限を加えるとか、本末転倒じゃないかと思われる方もたくさんいらっしゃるとは思いますが、 型による制限を加えた方がエラーやバグの数が減るというのは、過去の調査から明らかになっています。
TypeScript とは?
TypeScript は、Microsoft 社製の JavaScript のスーパーセット(上位互換)言語です。 JavaScript との違いは、"TypeScript" という名前のとおり "Type"、つまり "型" にあります。 JavaScript はもともと動的型付け言語で、型による制限が緩く実行時にエラーやバグが発生しやすい言語だったのですが、 そこに静的型付け言語のような強い型の制限を加えることで、実行時のエラーやバグを減らすことができるようになります。 同じ動的型付け言語である PHP で言うところの型宣言(タイプヒンティング)と同じようなものだと言えばわかりやすいでしょうか。 型の情報があることで、静的解析などもしやすくなりますし、コーディング中にエラーやバグに気付きやすくもなるのです。
JavaScript | TypeScript |
---|---|
function add(a, b) { return a + b; } |
function add(a: number, b: number): number { return a + b; } |
TypeScript は JavaScript の上位互換なので、 型以外は JavaScript とほぼ同じように使用できます。 型以外の文法などもほぼ同じですし、使える関数なども同じです。 あまり難しく考えずに、単純に「型が使えるようになった JavaScript」ぐらいに思っていただいて構わないと思います。
TypeScript のファイルの拡張子は ".ts" になります。 上位互換なので、ファイルの内容が JavaScript で書かれていたとしても、拡張子が ".ts" であれば TypeScript として問題なく動作させることができます。 とはいえ、TypeScript はブラウザ上で直接動作させることはできません。 TypeScript ファイルを実行させるためには、TypeScript ファイルから JavaScript ファイルに変換(トランスパイル)する必要があります。
TypeScript のインストール
さっそくですがインストールしていきましょう。
npm でインストールする
npm を使ってインストールするには、下記のコマンドを実行するだけです。
基本的には開発環境で使うツールだと思いますので、--save-dev
オプションを付けておきましょう。
npm の使い方がわからないという方は、まずは「Node.js と npm の使い方(その1:概要)」を参考にしてください。
npm install --save-dev typescript
インストールできたか確認する
TypeScript をインストールすると、tsc
コマンドが使えるようになっています。
下記のコマンドを実行してバージョン番号が表示されれば、ちゃんとインストールできています。
ちなみに "tsc" という名前は、"TypeScript Compiler" の略のようです。
なお、コマンドは "./node_modules/.bin" にインストールされます。
コマンドが見つからない場合は、"./node_modules/.bin" に PATH を通しておくか、
相対パスで "./node_modules/.bin/tsc" と指定するか、
もしくは npx
コマンドを使用するなどの対応を行ってください。
この記事では、説明の簡略化のために "./node_modules/.bin" に PATH を通してあることを前提として説明していきます。
tsc --version
ヘルプを表示する
tsc
コマンドの詳しい使い方が知りたい場合は、ヘルプを表示してみましょう。
tsc --help
基本的な使い方
インストールできたので、さっそく使ってみましょう。
tsc
コマンドを使用すると、TypeScript ファイルから JavaScript ファイルに変換(トランスパイル)することができます。
特定のファイルのみ変換する
特定のファイルのみを変換する場合は、tsc
コマンドの後にファイル名を指定します。
なお、この方法を使用すると TypeScript の設定ファイルが無視されるので注意してください。
tsc ./src/hoge.ts
tsc ./src/*.ts ./tests/*.ts
設定ファイルの内容に従って変換する
tsc
コマンドをオプション無しで実行すると、設定ファイルの内容に従って変換処理が実行されます。
設定ファイルが無い場合は、ヘルプが表示されます。
設定ファイルの作り方については後述します。
tsc
ファイルの変更を監視し、自動的に変換する
ファイルを変更するたびに変換処理を実行する必要があるわけですが、毎回毎回ファイルを変更するたびに変換処理を実行するのはとても面倒です。
そこで、ファイルの変更を tsc
が監視し、ファイルに変更があった場合に自動で変換処理を実行してくれるようになるオプション --watch
があります。
このオプションを付けて tsc
コマンドを実行すると、そのファイルに何か変更があった場合、自動で変換処理が実行されるようになります。
監視をやめる場合は、Ctrl + C を押せば終了できます。
tsc --watch
設定ファイル
TypeScript のデフォルトの設定ファイルは「tsconfig.json」になります。
フォーマットは JSON、、、ではなく JSONC(JSON with Comments)になります。
つまり、//
や /*
*/
でコメントが書ける JSON です。
設定ファイルを作成する
TypeScript のデフォルトの設定ファイル「tsconfig.json」は、
手動で作成することもできますが、自動で作成してくれる機能がありますので、まずはこれを利用しましょう。
自動で作成するには、tsc
コマンドに --init
オプションを付けて実行します。
tsc --init
設定ファイルを使用する
設定ファイルを使用する方法は2つあります。
デフォルトの設定ファイルを使用する
1つ目の方法は TypeScript のデフォルトの設定ファイル「tsconfig.json」を使用する方法です。 オプションを指定する必要はありません。 TypeScript は、デフォルトで現在のディレクトリから設定ファイルを探します。 設定ファイルが見つかった場合に、そのファイルが自動的に読み込まれます。
tsc
--project
オプションで指定する
2つ目の方法は、--project
オプションで使用したい設定ファイルを指定することです。
複数の設定ファイルを使用して、設定を切り替えたい場合などには便利かもしれません。
--project
オプション には、エイリアス(別名) -p
オプションがあります。
tsc -p config.json
設定ファイルの設定項目
どんな設定項目があるかは、ここでは記載しません。 大量にありますので、公式のドキュメントを見てもらった方が良いと思います。
ここでは簡単な例だけ記載しておきます。
{ "compilerOptions": { // 動作させたい環境 "target": "es2016", // モジュールの出力形式 "module": "esnext", // 変換元のディレクトリ "rootDir": "./assets/js/", // ソースマップを出力するか "sourceMap": true, // 出力先のディレクトリ "outDir": "./public/js/", // CommonJS と ES Modules で相互利用するか "esModuleInterop": true, // ファイル名の大文字小文字を区別するか "forceConsistentCasingInFileNames": true, // 厳密チェックするか "strict": true, // 型を指定していない場合にエラーにするか "noImplicitAny": true, // 型定義ファイル(".d.ts")のチェックをスキップするか "skipLibCheck": true } }
型定義ファイル(".d.ts")
型定義ファイルというのは、その名の通り、型の定義が書かれたファイルです。
ファイルの拡張子は ".d.ts" になります。
基本的には外部のライブラリなどを使用している場合に必要になると思います。
例えば、jQuery を使用しているとしましょう。
TypeScript から見ると、グローバル変数の $
や jQuery
の定義が見つからないため、それらがエラーになってしまうのです。
また、IDE やテキストエディタ(VS Code など)でも型の定義が見つからないため、コード補完やヘルプを表示したりする機能が効かなくなってしまうのです。
そこで、そうならないように、型の定義だけが書かれたファイル(つまり型定義ファイル)が必要になります。
昔懐かしのC言語で言うところのヘッダーファイル(.h)みたいなものを想像してもらえるとわかりやすいでしょうか。
Cannot find name '$'.
有名なライブラリであれば、既に型定義ファイル(".d.ts")が用意されています。 「@types/~」という名前のパッケージで公開されていますので、それらを使用すると良いでしょう。 インストールするだけで使用することができます。
npm install --save-dev @types/jquery
エラーを無視する
TypeScript で検出されたエラーの中には、あきらかに対応できないものや、対応する必要のないものなどがあったりします。 そういったものが常に検出されていると、作業の邪魔になってしまいます。 そこで、特定のエラーを検出の対象外として、無視させることができます。
エラーを無視するには、コメントの中で、@ts-ignore
もしくは @ts-expect-error
を使用します。
この2つはどちらも、それが書かれたその次の行のエラーを無視します。
違いは、実際にエラーが発生しているか否かになります。
@ts-ignore
の場合は、エラーが発生していない個所でも使用することができます。
これを使用すると、「エラーじゃないのにエラーを無視する」という、矛盾した状態が発生する可能性あります。
例えば、コードをメンテナンスしていて、コメントを消し忘れたりした場合に、それに気付くことができなくなります。
@ts-expect-error
の場合は、その名前の通り、実際にエラーが発生する個所にしか使用できません。
こちらであれば、「エラーじゃないのにエラーを無視する」という、矛盾した状態が発生することを防ぐことができます。
コメントとコードが食い違うことを防ぐことができるので、こちらの方がオススメです。
let val: string; // @ts-ignore val = 'hoge'; // @ts-expect-error val = 1;
webpack と組み合わせる
tsc
コマンドは、出力される JavaScript を圧縮(minify)する機能がありません。
しかし最近では、JavaScript を圧縮(minify)するのは、ほぼ必須の状況かと思います。
また、モジュールを使用している場合は、モジュールのバンドル処理も必要になるでしょう。
つまり、実際には下記のような作業が必要になります。
- コードを ".ts" ファイルに記述する
- その ".ts" ファイルを ".js" に変換する
- その ".js" のモジュールをバンドル&圧縮(minify)
この3番目の作業「".js" のモジュールをバンドル&圧縮(minify)」を行うために、webpack と組み合わせます。 webpack の使い方がわからないという方は、「webpack の使い方」を参考にしてください。
あまり良くない組み合わせ方
パッと思いつく単純な方法は tsc
コマンドを実行し、
そのあとに webpack
コマンドを実行することです。
一見すると特に問題はないように感じてしまいますが、実はこの方法にはいくつか問題があります。
tsc && webpack
上記のように tsc
コマンドと webpack
コマンドを別々に呼んでしまうと、
ソースマップファイルがそれぞれのコマンドで別々に出力されてしまい、
ブラウザが読み込んだソースマップファイルから ".ts" ファイルまで到達しないという問題が発生します。
tsc
コマンドが ".ts" ファイルから ".js"(圧縮前)ファイルへのソースマップを出力し、
webpack
コマンドが ".js"(圧縮前)ファイルから ".js"(圧縮後)ファイルへのソースマップを出力します。
ブラウザは webpack
コマンドが出力したソースマップを読み込むことになりますから、
".js"(圧縮前)ファイルまでしか到達できないのです。
また、".js"(圧縮前)ファイルは作業用の中間ファイルとしてしか使用されませんから、
それらのファイルがゴミとして邪魔になってしまいます(デバッグする際には逆に便利なのかもしれませんが)。
良い組み合わせ方
ではどうすれば良いのかというと、tsc
コマンドを使用せずに、
全部まとめて webpack
コマンドで処理させれば良いのです。
つまり、webpack が ".ts" ファイルを読み込めるようなれば良いのです。
そうすれば、webpack が出力するソースマップが ".ts" ファイルまで到達するようになります。
とはいえ、デフォルトの webpack では ".ts" ファイルを扱えません。
そこで、webpack で ".ts" ファイルを扱えるようにするために、ts-loader を使用します。
ts-loader をインストールする
ts-loader をインストールするには、下記のコマンドを実行します。
npm install --save-dev ts-loader
ts-loader パッケージがインストールできたら、webpack の設定ファイルで、".ts" ファイルを読み込めるように変更します。 下記の例を見てください。 この例は、"./src/index.ts" ファイルを変換し、"./dist/main.js" ファイルに、圧縮(minify)して出力するという設定になります。 変換前のファイル(entry)が、".js" ではなく ".ts" ファイルになっていることに注目してください。
module.exports = { mode: 'production', entry: { main: './src/index.ts' }, output: { path: __dirname + '/dist', filename: '[name].js' }, module: { rules: [ { test: /\.ts$/, loader: 'ts-loader' } ] } };
モジュールの読み込みパスの拡張子の問題
モジュールを TypeScript で作成した場合、そのファイルの拡張子は ".ts" になると思いますが、
そのファイルを import
で読み込む際に、拡張子を ".ts" と指定するとエラーになってしまいます。
// import で拡張子 ".ts" はエラーになる
import hello from '../assets/js/modules/hello.ts';
An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
実はこれはかなり厄介な問題でして、TypeScript 5.0 で allowImportingTsExtensions
オプションが追加されるなどの対応が行われましたが、
ts-loader などはまだ完全には対応できていないようです。
- TypeScript: Documentation - TypeScript 5.0
- ts-loader doesn't work when ts-config noEmit is set to true · Issue #1602 · TypeStrong/ts-loader · GitHub
現状での解決策としては、import
のパスには拡張子を指定せずに、
webpack で拡張子を ".ts" に補完させるのがベターな気がします。
設定するには、webpack の設定ファイルの resolve
項目の extensions
で ".ts" を指定します。
なお、extensions
で '...'
と指定すると、デフォルトの拡張子を指定していることと同じ意味になります。
// import で拡張子は指定しない import hello from '../assets/js/modules/hello';
module.exports = { mode: 'production', entry: { main: './src/index.ts' }, output: { path: __dirname + '/dist', filename: '[name].js' }, resolve: { extensions: ['.ts', '...'] }, module: { rules: [ { test: /\.ts$/, loader: 'ts-loader' } ] } };
ESLint と組み合わせる
まず前提として、ESLint の使い方がわからないという方は、「ESLint の使い方」を参考にしてください。
デフォルトの ESLint では ".ts" ファイルを扱えません。 ESLintで ".ts" ファイルを扱えるようにするためには、typescript-eslint をインストールする必要があります。
typescript-eslint をインストールする
typescript-eslint をインストールするには、下記のコマンドを実行します。 「@typescript-eslint/parser」パッケージと、「@typescript-eslint/eslint-plugin」パッケージの2つが必要になるので注意してください。
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin
typescript-eslint がインストールできたら、ESLint の設定ファイルで、".ts" ファイルを読み込めるよう、下記のように変更します。 やっている内容としては、TypeScript をパースするために「@typescript-eslint/parser」パッケージを使用し、 TypeScript 用のコーディング規約を使用するために「@typescript-eslint/eslint-plugin」パッケージを使用しているといった感じになっています。
module.exports = { extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended' ], parser: '@typescript-eslint/parser', plugins: [ '@typescript-eslint' ] };
Jest と組み合わせる
まず前提として、Jest の使い方がわからないという方は、「Jest の使い方」を参考にしてください。
デフォルトの Jest では ".ts" ファイルを扱えません。 Jest で ".ts" ファイルを扱えるようにするためには、ts-jest をインストールする必要があります。
ts-jest をインストールする
ts-jest をインストールするには、下記のコマンドを実行します。 「ts-jest」パッケージと、「@types/jest」パッケージの2つが必要になるので注意してください。
npm install --save-dev ts-jest @types/jest
ts-jest がインストールできたら、Jest の設定ファイルで、".ts" ファイルを読み込めるように変更します。
module.exports = { preset: "ts-jest" };
モジュールの読み込みパスの変換
Jest では、設定ファイルの moduleNameMapper
項目で、
モジュールを読み込む際のパスを正規表現で変換することができました。
パスに相対パスを使用してしまうと、階層が深くなった場合に "../" だらけになったり、
各ファイルで指定するパスの階層が異なったりしてメンテナンスが大変になります。
// 階層が深いとパスが "../" だらけになる import hello from '../../../../../assets/js/modules/hello';
もし、Jest でこの機能を使用してパスを変換しているのであれば、
TypeScript でも同様にパスの変換をしてあげる必要があります。
例えば、パスの指定が /
から始まる場合に、
そのパスをアプリケーションのルートディレクトリからの絶対パスとして変換したい場合は、
下記のような設定になります。
module.exports = { preset: "ts-jest", moduleNameMapper: { "^/(.*)$": "<rootDir>/$1" } };
{ "baseUrl": "./", "paths": { "/*": ["*"] } }
// 変換後はどの階層からでもこう書ける import hello from '/assets/js/modules/hello';
参考サイト
- 38% of bugs at Airbnb could have been prevented by TypeScript according to postmortem analysis : r/typescript
- TypeScript: JavaScript With Syntax For Types.
- TypeScript: Documentation - TypeScript Tooling in 5 minutes
- TypeScript: Documentation - What is a tsconfig.json
- tsconfig.jsonはJSONじゃないと言う話 - 焼売飯店
- TypeScript: TSConfig リファレンス - すべてのTSConfigのオプションのドキュメント
- @types パッケージ (DefinitelyTyped) - TypeScript Deep Dive 日本語版
- @types - npm search
- 【TypeScript】@ts-ignoreと@ts-expect-errorの違い - Qiita
- GitHub - TypeStrong/ts-loader: TypeScript loader for webpack
- TypeScript: Documentation - TypeScript 5.0
- ts-loader doesn't work when ts-config noEmit is set to true · Issue #1602 · TypeStrong/ts-loader · GitHub
- Resolve | webpack
- typescript-eslint
- Getting Started | typescript-eslint
- A Jest transformer with source map support that lets you use Jest to test projects written in TypeScript. | ts-jest
- Installation | ts-jest
- Paths mapping | ts-jest