Babel 7でTypeScriptをトランスパイルしつつ型チェックをする 〜webpack 4 + Babel 7 + TypeScript + TypeScript EsLint + Prettierの開発環境を構築する〜

##はじめに

表題の通りですが、Babel 7でTypeScriptをトランスパイルをするための、webpack 4 + Babel 7 + TypeScript + TypeScript ESLint + Prettierの開発環境構築に関しての備忘録です。

記事本文に記載している開発環境はGitHubに置いてあります。

[hira777/webpack-babel7-with-typescript](https://github.com/hira777/webpack-babel7-with-typescript)

webpackやPrettierなどを理解していることを前提とした記事ですので、基礎知識を習得したい方は以下の記事をご覧ください。

- [webpack 4 入門](https://qiita.com/soarflat/items/28bf799f7e0335b68186)
- [Prettier 入門 ～ ESLint との違いを理解して併用する～](https://qiita.com/soarflat/items/06377f3b96964964a65d)
- [Babel 7の主な変更点まとめ](https://qiita.com/soarflat/items/21b8955f992bf7d38581)

### 開発環境の特徴

- Babel（webpackのbabel-loader）でTypeScriptをトランスパイルする。そのため、ts-loaderは利用しない。
- Babelでは型チェックはできないため、型チェックはTypeScript（`tsc`）でする。
- 構文チェックにはTSLintではなく、TypeScript ESLintを利用する。
- TypeScript ESLintをPrettierと併用して、コード整形もできるようにする。

Babel 7からTypeScriptのトランスパイルが可能になりました。少し前にJestがバージョンアップし、TypeScriptをサポートしましたが、それも [BabelでTypeScriptをトランスパイルすることで実現しています](https://jestjs.io/docs/en/getting-started#using-typescript)。

しかし、型チェックはできないため、型チェックのためにTypeScriptが必要になります（Jestも同様で、テスト実行時に型チェックをするためにはts-jestが必要です）。また、[ネームスペースなどの一部の TypeScipt 構文はトランスパイルできないです](https://babeljs.io/docs/en/babel-plugin-transform-typescript#caveats)。

そして、TypeScript ESLintのversion1.x系もリリースされているので、それらを取り入れてみた開発環境になります。

### そもそもBabelでTypeScriptをトランスパイルする意味はあるのか？

以下のような理由でBabelのエコシステム上でTypeScriptのコードを扱いたい状況でなければ、TypeScript単体の利用で良く、無理に併用する必要はないと思っています。

- Babelを利用したビルド環境がすでに存在し、環境を一から構築することはできないがTypeScriptを利用したい。
- 開発、もしくは利用しているライブラリなどが特定のBabelプラグインに依存している。
- 何らかの理由で、TypeScriptでトランスパイルしたJavaScriptをBabelでトランスパイルしており、トランスパイルをBabelだけで済むようにしたい。

また、以下の記事のようにBabelからTypeScriptに移行する手段もあるため、本記事のようなBabelとの併用はあくまで手段の1つとして認識していただければ良いと思います。

- [Babelで書かれたJavaScriptライブラリをTypeScriptへ移行する方法](https://efcl.info/2019/01/09/babel-to-typescript-library/)
- [2019年版: 脱Babel!フロント/JS開発をTypeScriptに移行するための環境整備マニュアル](https://qiita.com/shibukawa/items/0a1aaf689d5183c6e0f1)

## ディレクトリ構成

本記事での開発環境のディレクトリ構成は以下を前提とする（`.babelrc`や`.eslintrc`などの設定ファイルの詳細は後述）。

```shell
.
├── .babelrc
├── .eslintrc
├── package.json
├── public
│   └── bundle.js
├── src
│   ├── app.ts
│   └── modules
│       └── add.ts
├── tsconfig.json
└── webpack.config.js
```

### `package.json`

パッケージをローカルインストールするため、`package.json`は以下のコマンドで生成しておく。

```shell
npm init -y
# もしくは
# yarn init
```

### `app.ts`（エントリーポイント）

```ts:src/app.ts
import add from './modules/add';

console.log(add(10, 5));
```

### `add.ts`

```ts:src/modules/add.ts
export default function add(number1: number, number2: number): number {
  return number1 + number2;
}
```

## 開発環境のセットアップ

- Babelのセットアップ
- webpackのセットアップ
- TypeScriptのセットアップ
- TypeScript ESLintとPrettierのセットアップ

### Babelのセットアップ

ますはBabelを利用するために最低限必要なパッケージをインストールする。

```shell
npm i @babel/core @babel/preset-env -D
# もしくは
# yarn add @babel/core @babel/preset-env -D
```

BabelでTypeScriptをトランスパイルするためには以下のパッケージも必要なのでインストールする。

```shell
npm i @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread @babel/preset-typescript -D
# もしくは
# yarn add @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread @babel/preset-typescript -D
```

#### `.babelrc`の設定

上記でインストールしたパッケージ（プリセットとプラグイン）を利用するため、設定は以下のようになる。

```json:.babelrc
{
  "presets": ["@babel/env", "@babel/typescript"],
  "plugins": ["@babel/proposal-class-properties", "@babel/proposal-object-rest-spread"]
}
```

### webpackのセットアップ

webpackを利用するために必要なパッケージをインストールする。前述の通りts-loaderはインストールしない。

```shell
npm i webpack webpack-cli babel-loader -D
# もしくは
# yarn add webpack webpack-cli babel-loader -D
```

#### `webpack.config.js`の設定

BabelでTypeScriptをトランスパイルするため、以下のように拡張子が`.ts`のファイルに対して`babel-loader`を実行するように設定する。

```js:webpack.config.js
const { resolve } = require('path');

module.exports = {
  // エントリーポイントの設定
  entry: './src/app.ts',
  // 出力の設定
  output: {
    // 出力するファイル名
    filename: 'bundle.js',
    // 出力先のパス（絶対パスを指定する必要がある）
    path: resolve(__dirname, 'public')
  },
  resolve: {
    extensions: ['.ts']
  },
  module: {
    rules: [
      {
        // ローダーの処理対象ファイル
        test: /\.ts$/,
        // 利用するローダー
        use: 'babel-loader',
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/
      }
    ]
  }
};
```

### TypeScriptのセットアップ

型チェックを行うためにTypeScriptをインストールする。

```shell
npm i typescript -D
# もしくは
# yarn add typescript -D
```

#### `tsconfig.json`の設定

`include`は開発環境に応じて変更。

```json:tsconfig.json
{
  "compilerOptions": {
    /* トランスパイル後のECMAScriptのバージョン */
    "target": "esnext",

    /* 相対パスではないモジュールは node_modules 配下を検索する */
    "moduleResolution": "node",

    /* 今回、トランスパイルは Babelが行うので、`tsc`コマンドでJavaScriptファイルを出力しないようにする */
    "noEmit": true,

    /* 厳格な型チェックオプション（noImplicitAny、noImplicitThis、alwaysStrict、
       strictBindCallApply、strictNullChecks、strictFunctionTypes、
       strictPropertyInitialization）を有効化する */
    "strict": true,

    /* 各ファイルを個々のモジュールとしてトランスパイルする。
       Babel では技術的制約で、ネームスペースなどのファイルを跨いだ構文を解釈してトランスパイルできない。
       このオプションを有効にすれば、Babel でトランスパイルできない TypeScriptの構文を検出して警告を出す */
    "isolatedModules": true,

    /* ES modules 形式以外の、CommonJS 形式などのモジュールを default import 形式で読み込める
       例）const module = require('module') -> import module from 'module' */
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}
```

### TypeScript ESLintとPrettierのセットアップ

TypeScript ESLintを利用するために必要なパッケージをインストールする。

```shell
npm i eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
# もしくは
# yarn add @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
```

次に、PrettierとTypeScript ESLintを併用するために必要なパッケージをインストールする。

```shell
npm i prettier eslint-plugin-prettier eslint-config-prettier -D
# もしくは
# yarn add prettier eslint-plugin-prettier eslint-config-prettier -D
```

#### `.eslintrc`の設定

以下のように`prettier/@typescript-eslint`を記述することで、`plugin:@typescript-eslint/recommended`のコードフォーマット関連のルールが無効になり、Prettierとの競合を避けられる。

```json:.eslintrc
{
  "parser": "@typescript-eslint/parser",
  "extends": [
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint"
  ],
  "rules": {
    "prettier/prettier": [
      "error",
      {
        "singleQuote": true
      }
    ]
  }
}
```

## 動作確認

上記のセットアップが完了すれば、

- Babel（webpackのbabel-loader）でTypeScriptのトランスパイル
- TypeScript（`tsc`）で型チェック
- TypeScript ESLintとPrettierを併用した状態でESLintの実行

が可能になるので、それぞれの処理を実行していく。

それぞれの処理を実行するために、`package.json`に以下の記述を追加する。

```json
  "scripts": {
    "check-types": "tsc",
    "dev": "webpack --mode development",
    "lint": "eslint --fix --ext .ts ./src"
  }
```

### Babel（webpackのbabel-loader）でTypeScriptのトランスパイルをする

```shell
npm run dev
# もしくは
# yarn dev

# 以下のような実行結果が出力される。
Hash: 91172258ce54870de710
Version: webpack 4.29.6
Time: 740ms
Built at: 2019-04-08 21:39:45
    Asset      Size  Chunks             Chunk Names
bundle.js  4.83 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./src/app.ts] 173 bytes {main} [built]
[./src/modules/add.ts] 92 bytes {main} [built]
```

Babel（webpackのbabel-loader）でTypeScriptファイル（`app.ts`、`add.ts`）をトランスパイルできた。

### TypeScript（`tsc`）で型チェックをする

前述の通り、Babelは型チェックをしないため、以下のように変更をしてもトランスパイルできてしまう。

```ts
import add from './modules/add';

console.log(add(10, '5'));
```

TypeScript（`tsc`）で型チェックをする。

```shell
npm run check-types
# もしくは
# yarn check-types

# 以下のような実行結果が出力される。
src/app.ts:5:36 - error TS2345: Argument of type '"5"' is not assignable to parameter of type 'number'.

5 console.log(add(10, '5'));
                                     ~~~
Found 1 error.
```

型チェックできた。

監視したい場合は`npm run check-types -- --watch`を実行する。

### TypeScript ESLintとPrettierを併用した状態でESLintを実行する

```shell
npm run lint
# もしくは
# yarn lint
```

![lint.gif](https://qiita-image-store.s3.amazonaws.com/0/69667/0760b882-da5e-5f8c-92d2-ad9137ed04d1.gif)


エラー出力とコード整形をできた。

## 終わり

本記事で記載した通り、BabelでTypeScriptのトランスパイルは簡単にできます。

状況によっては不要ですが、手段の1つとして覚えておくと役に立つかもしれません。

## 参考

- [TypeScript and Babel 7](https://devblogs.microsoft.com/typescript/typescript-and-babel-7/)
- [TypeScript With Babel: A Beautiful Marriage](https://iamturns.com/typescript-babel/)
- [ESLint Plugin TypeScript](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin)

## お知らせ

Udemy で webpack の講座を公開したり、Kindle で技術書を出版しています。

Udemy:
[webpack 最速入門](https://www.udemy.com/course/practical-webpack/?couponCode=B6D5AE3897554CBBDAD7)（**~~10,800 円~~ -> 2,000 円**）

Kindle（Kindle Unlimited だったら無料）:
[React Hooks 入門](https://www.amazon.co.jp/gp/product/B08JTXQ2M6)（500 円）

興味を持ってくださった方はご購入いただけると大変嬉しいです。よろしくお願いいたします。