Babelのpresets設定をNext.jsで理解してみる

## はじめに
フロントのフレームワークを使う上で避けられない、Babelさん。
ですが最近のフレームワークは「インストール手順」にならうだけで、Babelの変換もHMRも当たり前のように動いてくれます。
便利な反面、理解できていないとBabel自体の設定を変更しなければいけない時にハマってしまいます…。
今回は、presetsでハマった私の内容と解決方法を後世の私のために残します。

- Babel   : JavaScriptの新しい記法を現ブラウザで動くように変換してくれる
- Jest    : JavaScriptのシンプルなテストフレームワーク
- Next.js : React.jsのフレームワーク

## 目的と前提
- Next.js(v4)の環境にJestを導入したい
- Jestのバージョンは何でも良い
- テスト導入のためだけに現プロジェクトの大きな変更をしない

```javascript:.babelrc
  "presets": [
    "next/babel"
  ],
```

## 実施したこと、ハマったこと

### 公式サイトを読んで素直にJestをインストール
React.jsが動く環境下だったので、同じFaceBook製のJestを選択しました。
公式のバージョンは`24.9`（2019年10月時点）でした。
手順にならって`jest`と`babel-jest`をインストールしました。
そしてテスト用のプログラムを書いて実行してみると、エラーになりました。

```javascript
Plugin/Preset files are not allowed to export objects, only functions.
```

直訳してみましょう。

```
プラグイン/プリセットファイルは、オブジェクトのみをエクスポートできません。関数のみです。
```

さて、つまり……？　頭の中はハテナマークです。
ネットでエラーを調べてみると、どうやら`babel-preset-env`が足りないなどの理由で失敗しているようです。
なるほど、ではその通りインストールしてみましょう。

```javascript
npm i -D babel-core babel-preset-env
```

さて、ここで問題が起きました。
この`babel-preset-env`はインストールするだけでなく、`.babelrc`の`presets`にセットしなければいけません。
つまりはプロジェクトに多大な影響が…？
とはいえJestの動作確認のため、一旦設定してみましょう。

```javascript:.babelrc
  "presets": [
    ["babel-preset-env",
      {
        "targets": {
          "node": "current"
        }
      }
    ]
  ],
```

こうなります。そして実行すると、

```console
PASS  ./sum.spec.js
  √ adds 1 + 2 to equal 3 (2ms)
```

成功しました。
ちなみにJestの公式サイトでは`v24.9`の場合はbabel7の例が載っていますが、上記のようにbabel6でも動作します。
ですが案の定、プロジェクトではエラーの嵐です。とても押し通せるとは思えません…。

### Next.jsのバージョンを上げてみる
要は`.babelrc`のpresetsを`next/babel`から変更しなければいいんだ、という認識です。
ここを変えずにNext.jsのバージョンを上げてJestが動作するかを検証してみました。
結論から言うと、動作しました。
そもそもNext.js(v9)のcreate-appが公開されていて、それは`next/babel`で動作していました。
ですがこの手段はメリットよりもデメリットが上回りました。はい。

##### 検証結果

| バージョン | プロジェクト画面 | Jest |
|:-----------|:----:|:----:|
| Next4   | ○ | × |
| Next6.1 | △| × |
| Next7   | × | ○ |
| Next8.1 | × | ○ |
| Next9   | × | ○ |

きれいに分かれましたね！
画面側が動くときはJestが動作せず、Jestが動作するときは画面が動かず…。
まるで運命に翻弄される男女のようだね（謎）。

### Jestのバージョンを変更
こちらも`presets`は`next/babel`で固定にして実施しました。
ここまでくると分かっていましたが、動きませんでした。

## ジレンマ
ここで少し整理してみましょう。
`.babelrc`ファイルの`presets`を`next/babel`から`babel-preset-env`に変更するとJestが動作します。
ですが画面側が動かなくなります。
`.babelrc`ファイルの`presets`を`next/babel`のままにするとJestが動作しません。

私はここで詰みを感じていました。
Next.jsのバージョンが低いから動かせないんだ、仕方ないんだ、と。
ですが冷静になった私はここで、自分で勝手にルールを決めていたことに気づきました。

presetsの`next/babel`を変更してはいけない、のではありません。
変更しつつプロジェクトに影響を与えなければいいのです。


## `next/babel`とは
さてここまで単語だけはたくさんでてきた`next/babel`さんですが、実はよく分かっていませんでした。
そもそもpresetsとはなんぞや、の段階でした。
`next/babel`で検索しても`nuxt/babel`さんが邪魔してきますので、理解力の欠如に拍車がかかりますね。

さて、よく公式を読みなさいという言葉がありますが、本当にその通りでした。
この場合は`babel`か`Next`かの問題がありますが、どちらも読むことをおすすめします。

[Next.js #customizing-babel-config](https://nextjs.org/docs#customizing-babel-config)

上記リンクはNext.jsの公式サイトです。
こちらを読んでみると、`next/babel`とはどうやら複数の機能を持った１つの集合名のようです。
バーベキュー満喫セットとか、入学式おめでとうセットみたいなもので、そこに必要なものが一式揃えられています。

> アプリケーションを動作させるのに必要なものがすべて入っています。
> - preset-env
> - preset-react
> - preset-typescript
> - plugin-proposal-class-properties
> - plugin-proposal-object-rest-spread
> - plugin-transform-runtime
> - styled-jsx

なるほど…？
つまり`presets:[]`と`plugins:[]`に上記を記述するのと、`presets[]`に`next/babel`だけを記述するのは同じ意味だということですね。
そう考えると記述の短縮にもなるし、なんだか`next/babel`に親しみを持てるようになってきましたね。
各プラグインの意味合いは、また気になるようでしたら調べてみてください。

さて、ここまで理解した私は１つのひらめきを頼りに調べ物をしました。

## Next.jsとbabelの依存を確認
普段は何気なくインストールしている`node_modules`ですが、中を覗くと10000以上のパッケージがあります。
それを１つ１つ読み解いている時間はないので、いつの間にか「読む」という手段そのものを忘れていました。

今回は`node_modules/next`フォルダを見ました。
中には`package.json`があり、babelの記述もあります。
そうです。
Next.jsのバージョンと各パッケージのバージョンは依存しています。
そのため、Next.jsのバージョンアップを行うと`next/babel`でも動作するようになったわけですね。

## Next.js(v4)と同じバージョンのpresetとpluginをインストール

```
npm i -D babel-core@v6.26.0 babel-generator@6.26.0 babel-plugin-module-resolver@2.7.1 babel-plugin-react-require@3.0.0 babel-plugin-syntax-dynamic-import@6.18.0 babel-plugin-transform-class-properties@6.24.1 babel-plugin-transform-es2015-modules-commonjs@6.26.0 babel-plugin-transform-object-rest-spread@6.26.0 babel-plugin-transform-react-jsx-source@6.22.0 babel-plugin-transform-react-remove-prop-types@0.4.8 babel-plugin-transform-runtime@6.23.0 babel-preset-env@1.6.0 babel-preset-react@6.24.1 babel-runtime@6.26.0 babel-template@6.26.0
```

```javascript:.babelrc
{
  "presets": [
    "babel-preset-env",
    "babel-preset-react"
  ],
  "plugins": [
    "babel-plugin-react-require",
    "babel-plugin-syntax-dynamic-import",
    "babel-plugin-transform-class-properties",
    "babel-plugin-transform-es2015-modules-commonjs",
    "babel-plugin-transform-object-rest-spread",
    "babel-plugin-transform-react-jsx-source",
    "babel-plugin-transform-react-remove-prop-types",
    "babel-plugin-transform-runtime"
  ]
}
```

実際のプロジェクトでは`module-resolver`の設定などもすると思いますが、こんな感じ。
実行するとJestはうまく動作してくれました。

ちなみにjestの`v24.9`では動作せずに`v22.4.4`にバージョンダウンすることで動作しました。
（この辺りは調べきれていないのでご存知の方はコメントして下さると嬉しいです）

画面の方も内部的には同じものを読み込んでいるので、エラーは起きませんでした。
結論はNext.jsのpackageの一部を無視するという暴挙に落ち着いたわけですね…。


## 余談
Babelの`presets`や`plugin`には省略記法があるみたいです。
この辺りはプロジェクトの方々と相談すると良さそうですね。

```javascript:.babelrc
{
  "presets": ["env","react"],
  "plugins": [
    "react-require",
    "syntax-dynamic-import",
    "transform-class-properties",
    "transform-es2015-modules-commonjs",
    "transform-object-rest-spread",
    "transform-react-jsx-source",
    "transform-react-remove-prop-types",
    "transform-runtime"
  ]
}
```

## 少し長いあとがき
ここまでお付き合いをいただきましてありがとうございます。
さて、察しのいい方なら読んでいて「ん？」となったところがあるかもしれません。

`next/babel`を指定するのと`next/package.json`の中身をすべて記述するのは、同じ意味です。
では結局、`next/babel`を指定するのと同じでjestは失敗するのでは？ということですね。
そもそも`next/babel`には`babel-preset-env`も含まれていたので、失敗する意味がますます分かりません。

### やり直し
私は１つの予想を立てました。
Next(v4)の`next/babel`には足りないプラグインがあり、それを補えばJestは上手く動作するのでは？　と。

まずはjestをインストールします。
Next(v4)の`next/babel`で使っているbabelは6なので、そちらに合わせてjestもv22にしてみましょう。

```
npm i -D jest@v22 babel-jest@v22
```

`jest@v22`の時はpackage.jsonに下記を追加する必要があります。

```javascript
  "jest": {
    "testURL": "http://localhost"
  },
```

そして実行すると、`jest@v24`の時とは違うエラーが出てきました。

```javascript
import { sum } from './sum'
       ^
```

```
SyntaxError: Unexpected token {
```

これはES6の構文解釈ができていない、というエラーですね。
これを見る限り、やはりjestのバージョンはNext.jsと合わせた方が良さそうですね。
（`jest@v24`のときは理由がよく分からなかったです…）

では不足しているプラグインを足していきましょう。
やはり１番怪しそうなのは、`babel-plugin-transform-es2015-modules-commonjs`でしょうか？

```javascript:.babelrc
{
  "presets": [
    "next/babel"
  ],
  "plugins": [
    "transform-es2015-modules-commonjs",
  ]
}
```

↓ 結果

```console
 PASS  .\sum.spec.js
  √ adds 1 + 2 to equal 3 (3ms)
```

はい、動きました～。ビンゴですね！
これでNext.jsのpackageを無視する暴挙もすることなく、スマートにjestを動かすことができました。

あとは画面側に影響を与えないように、jestだけに設定したら完了です！

```javascript:.babelrc
{
  "presets": [
    "next/babel"
  ],
  "env": {
    "test": {
      "plugins": ["transform-es2015-modules-commonjs"]
    }
  }
}
```

ではでは、良いプログラムライフを！