babelの設定ファイルをTypeScriptで書く

表題の通りです。
`babel` の設定ファイルといえば `.babelrc` `babel.config.js` での記述が一般的だと思います。これを `babel.config.ts` に記述できるようにします。

## 1. 必要モジュールのインストール
とりあえず `webpack` 前提で必要最低限なものだけインストールします。

```
npm i -D webpack webpack-cli typescript ts-node @babel/core @types/babel__core babel-loader @babel/preset-env
```

ほとんど説明するほどのものではないですが、重要なのは `@types/babel__core` です。
ここに設定ファイルで使用する型情報が載っています。

## 2. TypeScriptの設定
`tsconfig.json` を書きます。

```tsconfig.json
{
  "ts-node": {
    "compilerOptions": {
      "module": "commonjs",
      "target": "es5",
    },
  },
}
```

ここでは `ts-node` の設定だけ行います。

## 3. babelの設定

ここからが本題です。 `babel.config.ts` を書きますが、 設定の読み込み方によって書き方も変わってきます。
だいたい次の2通りに分けられるんじゃないでしょうか。

1. プロジェクトルートにある `babel` の設定ファイル（ `.babelrc` `babel.config.js` ）を `babel` が自動的に読み込む
2. `webpack` から実行環境によって手動（設定ファイルのコード上）で呼び出す設定を変える

上記2パターンそれぞれについて説明をしていきます。

### 3.1. 設定ファイルを自動的に読み込む場合

この場合、 `babel.config.ts` は次のように書くことができます。

```babel.config.ts
import { TransformOptions, ConfigAPI } from '@babel/core'

export default function (api: ConfigAPI): TransformOptions {
  return {
    presets: [
      '@babel/preset-env',
    ],
  }
}
```

`TransformOptions` と `ConfigAPI` という見慣れないものが出てきましたが、 `TransformOptions` はお馴染みの [babelの設定ファイルの内容](https://babeljs.io/docs/en/options) 、 `ConfigAPI` は [babelの設定API](https://babeljs.io/docs/en/config-files#config-function-api) のことです。
これを `.ts` ではなく `.js` で書くと次のようになります（ドキュメントにほぼ同じものが載っていますが）。

```babel.config.js
module.exports = function(api) {
  return {
    presets: [
      '@babel/preset-env',
    ],
  };
}
```

ここまでくればあとは `babel.config.ts` を `babel` に読み込ませるだけですが、 `babel` は `webpack` のように `.ts` の設定ファイルをいい感じに解釈してくれません。

```webpack.config.ts
// ...
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          configFile: 'babel.config.ts'
        },
      },
// ...
```

このように書いたとしても `babel` は `bable.config.ts` をJSONとして読み込もうとするので構文エラーになります。
そこで `tsc` コマンドで `.js` ファイルにトランスパイルする必要があります。

```
npx tsc babel.config.ts
```

これで `babel.config.js` が生成されるので設定ファイルを読み込むことができますが、いちいちトランスパイルするのは面倒ですし、 `babel.config.ts` と `babel.config.js` の2ファイルが並ぶのはあまり気持ちのいいものではないですね。

### 3.2. webpackから実行環境によって設定を変える場合

3.1. の方法だとあまりすっきりできないので、自分はこちらの方法を採用しています。
特に `webpack4` では `webpack-merge` を使って呼び出す設定ファイルを切り替えるのがメジャーらしい（？）のでこちらの方が合っている気がします。

```babel.config.ts
import { TransformOptions } from '@babel/core'

// 開発環境用の設定
export const dev: TransformOptions = {
  presets: [
    '@babel/preset-env',
  ],
  sourceMaps: true,
}

// 本番環境用の設定
export const prod: TransformOptions = {
  presets: [
    '@babel/preset-env',
  ],
}
```

```webpack.config.dev.ts
import { Configuration } from 'webpack'
import merge from 'webpack-merge'
import config from './webpack.config.common'
import { dev as devBabelConfig } from './babel.config'

const devConfig: Configuration = merge(config, {
  mode: 'development',
  devtool: 'source-map',
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: devBabelConfig,
      },
  // ...
}

export default devConfig
```

```webpack.config.prod.ts
import { Configuration } from 'webpack'
import merge from 'webpack-merge'
import config from './webpack.config.common'
import { prod as prodBabelConfig } from './babel.config'

const prodConfig: Configuration = merge(config, {
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: prodBabelConfig,
      },
  // ...
}

export default prodConfig
```

## おわりに

以上、 `babel` の設定ファイルを `TypeScript` で書いてみました。
`TypeScript` で記述することでタイポなども減らせていいんじゃないでしょうか。
もっといい方法などあれば教えて頂けるとうれしくなります。