TypeScript の使い方

JavaScript はもともと大きなプログラムを記述するのにはあまり向いていない言語です。 そこで、JavaScript に型による強い制限を加えて、メンテナンス性を向上させようとする試みがあります。 動的型付け言語である JavaScript に型による制限を加えるとか、本末転倒じゃないかと思われる方もたくさんいらっしゃるとは思いますが、 型による制限を加えた方がエラーやバグの数が減るというのは、過去の調査から明らかになっています。

TypeScript とは?

typescript_thumb.png

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 の設定ファイルが無視されるので注意してください。

例:"./src/hoge.ts" ファイルを変換する場合
tsc ./src/hoge.ts
例:"./src" ディレクトリと "./tests" ディレクトリの ".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 オプションがあります。

例:設定ファイルとして「config.json」を指定する場合
tsc -p config.json

設定ファイルの設定項目

どんな設定項目があるかは、ここでは記載しません。 大量にありますので、公式のドキュメントを見てもらった方が良いと思います。

ここでは簡単な例だけ記載しておきます。

tsconfig.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/~」という名前のパッケージで公開されていますので、それらを使用すると良いでしょう。 インストールするだけで使用することができます。

例:「@types/jquery」パッケージをインストールする場合
npm install --save-dev @types/jquery

エラーを無視する

TypeScript で検出されたエラーの中には、あきらかに対応できないものや、対応する必要のないものなどがあったりします。 そういったものが常に検出されていると、作業の邪魔になってしまいます。 そこで、特定のエラーを検出の対象外として、無視させることができます。

エラーを無視するには、コメントの中で、@ts-ignore もしくは @ts-expect-error を使用します。 この2つはどちらも、それが書かれたその次の行のエラーを無視します。 違いは、実際にエラーが発生しているか否かになります。

@ts-ignore の場合は、エラーが発生していない個所でも使用することができます。 これを使用すると、「エラーじゃないのにエラーを無視する」という、矛盾した状態が発生する可能性あります。 例えば、コードをメンテナンスしていて、コメントを消し忘れたりした場合に、それに気付くことができなくなります。

@ts-expect-error の場合は、その名前の通り、実際にエラーが発生する個所にしか使用できません。 こちらであれば、「エラーじゃないのにエラーを無視する」という、矛盾した状態が発生することを防ぐことができます。 コメントとコードが食い違うことを防ぐことができるので、こちらの方がオススメです。

TypeScript ファイル
let val: string;

// @ts-ignore
val = 'hoge';

// @ts-expect-error
val = 1;

webpack と組み合わせる

tsc コマンドは、出力される JavaScript を圧縮(minify)する機能がありません。 しかし最近では、JavaScript を圧縮(minify)するのは、ほぼ必須の状況かと思います。 また、モジュールを使用している場合は、モジュールのバンドル処理も必要になるでしょう。 つまり、実際には下記のような作業が必要になります。

  1. コードを ".ts" ファイルに記述する
  2. その ".ts" ファイルを ".js" に変換する
  3. その ".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" ファイルになっていることに注目してください。

webpack.config.js
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" と指定するとエラーになってしまいます。

TypeScript ファイル
// 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 などはまだ完全には対応できていないようです。

現状での解決策としては、import のパスには拡張子を指定せずに、 webpack で拡張子を ".ts" に補完させるのがベターな気がします。 設定するには、webpack の設定ファイルの resolve 項目の extensions で ".ts" を指定します。 なお、extensions'...' と指定すると、デフォルトの拡張子を指定していることと同じ意味になります。

TypeScript ファイル
// import で拡張子は指定しない
import hello from '../assets/js/modules/hello';
webpack.config.js
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」パッケージを使用しているといった感じになっています。

.eslintrc.cjs
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" ファイルを読み込めるように変更します。

jest.config.cjs
module.exports = {
	preset: "ts-jest"
};

モジュールの読み込みパスの変換

Jest では、設定ファイルの moduleNameMapper 項目で、 モジュールを読み込む際のパスを正規表現で変換することができました。 パスに相対パスを使用してしまうと、階層が深くなった場合に "../" だらけになったり、 各ファイルで指定するパスの階層が異なったりしてメンテナンスが大変になります。

TypeScript ファイル
// 階層が深いとパスが "../" だらけになる
import hello from '../../../../../assets/js/modules/hello';

もし、Jest でこの機能を使用してパスを変換しているのであれば、 TypeScript でも同様にパスの変換をしてあげる必要があります。 例えば、パスの指定が / から始まる場合に、 そのパスをアプリケーションのルートディレクトリからの絶対パスとして変換したい場合は、 下記のような設定になります。

jest.config.cjs
module.exports = {
	preset: "ts-jest",
	moduleNameMapper: {
		"^/(.*)$": "<rootDir>/$1"
	}
};
tsconfig.json
{
	"baseUrl": "./",
	"paths": {
		"/*": ["*"]
	}
}
TypeScript ファイル
// 変換後はどの階層からでもこう書ける
import hello from '/assets/js/modules/hello';

参考サイト

関連記事

最近の Web の開発では、webpack を使用することはもはや当たり前のような状況です。そこで改めてその使用方法をまとめてみたいと思います。今更かもしれませんが、誰かの参考になれば幸いです。webpack とは?近年の Web 開発では、サーバーサイド(PHP など)ではなく、クライアントサイド(JavaScrip ...
JavaScript は文法的にかなりフレキシブルな言語であり、コードが汚くなりがちです。キレイで読みやすいコードを保つために、JavaScript にも静的解析ツールを導入しましょう。ESLint とは?ESLint は、JavaScript の静的解析ツールです。JavaScript コードを実行する前に(つまり静 ...
近年の Web 開発では、サーバーサイド(PHP など)ではなく、クライアントサイド(JavaScript)で処理することが多くなりました。JavaScript の重要性が増えるに伴って、そのテストもまた重要になってきます。Jest とは?Jest は、Meta 社(旧:Facebook 社)製の JavaScript ...
最近の Web 界隈では、UX を高めるために、UI を JavaScript でガリガリ実装するというのが普通になってます。フロントエンドエンジニアなんていう職種が生まれるほど、この界隈は活発です。そこでフロントエンド開発でほぼ必須となるであろう Node.js と npm について少しまとめてみます。今更感がすごい ...
JavaScript で非同期処理を作成する際に使用するのが async と await です。ですが、この2つをちゃんと理解するのはかなり難しい気がします。そこで、なんとか自分なりにわかりやすくまとめてみます。誰かのお役に立てれば幸いです。Promise の復習async、await を語る前に、まずは Promis ...

記事検索

最新記事

人気記事

RSSフィード

お知らせ

フィードバック

要望などあれば、お気軽にどーぞ。 不具合やバグを発見した場合も、連絡をいただけると助かります。

匿名でフィードバックする
匿名でフィードバックする

要望などあれば、お気軽にどーぞ。 不具合やバグを発見した場合も、連絡をいただけると助かります。

なお、このフォームから入力された内容について、管理者から返信はできませんので注意してください。 もし、管理者からの返信が必要であれば、X(Twitter) もしくは、お問い合わせより、お願いします。

  • フィードバックの送信が完了しました。