【解決済み】Create React AppでTypeScript 3.7の新文法を使う（とハマるので頑張って回避する）

## 本記事の陳腐化のお知らせ

日本時間の2019年12月5日朝に `react-scripts` のv3.3.0が[リリースされました](https://github.com/facebook/create-react-app/releases/tag/v3.3.0)。

待望のOptional ChainingとNullish Coalescingが使えるようになっています。ヤッタネ！

というわけで、本記事は「そんなワークアラウンドを提案してたやつがいたんだなー」ということを思い出してもらうための記念碑として残すものになりました。CRAにおけるBabelとTypeScriptの関係を思い出したくなったときに読み返しにきてください。

ところで余談なんですけど、CRA + TSの初期化コマンドにつけるオプションが `--typescript` から `--template typescrpt` に変わってます（[PR](https://github.com/facebook/create-react-app/pull/8050)）。ドキュメントも全てこちらに書き換わっているので、うっかり実行してしまうのですが、手元にCRA 3.2.x以下がインストールされていると空っぽのプロジェクトができてハマるので、CRA 3.3.0以上にしてから新しいオプションを使いましょう。

## 要約

* 「tscを静的型チェッカーで運用しててコンパイルはBabel」という運用の（Create React Appのような）プロジェクトはTS3.7の新文法を使うときにハマる
* TS側がOptional Chaining等に対応していても、Babel側に入ってない（presetに取り込まれていない場合が多い）
* `@babel/plugin-proposal-optional-chaining` 等を入れておく必要がある
* ところでCreate React Appは `babel.config.js` が使えないのでそのままだと詰む
* react-app-rewired使ったりして何とか頑張ろうな
* 素直にreact-scriptsのアップデートを待ったほうがいいと思う

## はじめに

[TypeScript 3.7がリリースされました](https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/)！

Optional Chainging（`hoge?.fuga`）やNullish Coalescing（`hoge ?? fuga`）といった、便利な文法が取り込まれ、多くの人に祝福されたリリースとなりました。TypeScript 3.7のマイルストーンが付いたことで[Optional Chaining導入検討Issueがお祭りムードになった](https://github.com/microsoft/TypeScript/issues/16#issuecomment-515160784)のは記憶に新しいところです。

## react-scripts 3.2.0時点でのCRAでは新文法が使えない

さて、上げて落とす形になって申し訳ないのですが、残念ながら本日現在、Reactエンジニアが愛するCreate React AppでTS3.7の新文法を利用することはできません。

2019年11月12日現在、`$ create-react-app [プロジェクト名] --typescript` のコマンドで作ったプロジェクトのdependenciesは、次のようになります。

```json:package.json
"dependencies": {
  "@types/jest": "24.0.22",
  "@types/node": "12.12.7",
  "@types/react": "16.9.11",
  "@types/react-dom": "16.9.4",
  "react": "^16.11.0",
  "react-dom": "^16.11.0",
  "react-scripts": "3.2.0",
  "typescript": "3.7.2"
}
```

まあまあ最新の構成になっていて、TypeScriptもv3.7.2が入っていますね。

やったー！　これで僕らも快適生活の仲間入りだ！　というわけで、次のようなコードを書いてみます。

```tsx:App.tsx
import React from 'react';
import logo from './logo.svg';
import './App.css';

const App: React.FC = () => {
  // 中身がオブジェクトかnullかわからないhoge変数
  const hoge: { fuga: string } | null = (() => {
    switch(Math.floor(Math.random() * 10) % 2) { // 0 or 1
      case 0:
        return { fuga: "piyo" };
      default:
        return null;
    }
  })();

  return (
    <div className="App">
      {/*略*/}
        <p>
          {hoge?.fuga /* <= TS3.7 Optional Chaining */}
        </p>
        <p>
          {hoge?.fuga ?? "none" /* <= TS3.7 Nullish Coalescing */}
        </p>
      {/*略*/}
    </div>
  );
}

export default App;
```

TypeScript的には何も問題ないコードなので、エディタでの表示や、tscコマンドでの静的型チェックの結果は、特に問題が起こりません。

問題が起こるのは、ビルドが絡んだときです。

```shell-session
$ yarn build
yarn run v1.19.0
$ react-scripts build
Creating an optimized production build...
Failed to compile.

./src/App.tsx
SyntaxError: /my/project/path/src/App.tsx: Support for the experimental syntax 'optionalChaining' isn't currently enabled (23:16):

  21 |         </p>
  22 |         <p>
> 23 |           {hoge?.fuga}
     |                ^
  24 |         </p>
  25 |         <p>
  26 |           {hoge ?? "none"}

Add @babel/plugin-proposal-optional-chaining (https://git.io/vb4Sk) to the 'plugins' section of your Babel config to enable transformation.


error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

Optional Chainingは有効になっていないよ、と怒られてしまいます。ちなみに上から順にエラーを出していくだけなので、順序を変えればNullish Coalescingでも同じことを言われます。

```shell-session
$ yarn build
yarn run v1.19.0
$ react-scripts build
Creating an optimized production build...
Failed to compile.

./src/App.tsx
SyntaxError: /my/project/path/src/localhost/ts37app/src/App.tsx: Support for the experimental syntax 'nullishCoalescingOperator' isn't currently enabled (23:17):

  21 |         </p>
  22 |         <p>
> 23 |           {hoge ?? "none"}
     |                 ^
  24 |         </p>
  25 |         <p>
  26 |           {hoge?.fuga}

Add @babel/plugin-proposal-nullish-coalescing-operator (https://git.io/vb4Se) to the 'plugins' section of your Babel config to enable transformation.


error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

これは困りました。

### CRAでの文法解決はBabelの責務

[以前の記事でも言及したことがあった](https://qiita.com/Nkzn/items/cf1516136d2db981fbb9#%E5%89%8D%E6%8F%90%E6%9D%A1%E4%BB%B6babel-7%E3%81%AEtypescript%E5%AF%BE%E5%BF%9C%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF)のですが、Create React AppのTypeScriptサポートは、Babelの仕組みに乗っかったものです。

TypeScriptを実行可能なコードに変換するスタイルは、いくつかあります。

1. `tsc` コマンドで.ts(x)ファイルを.jsファイルにコンパイルする（ts-nodeもこれのはず）
2. `@babel/preset-typescript` で「TypeScript独自文法」を剥がしてECMAScript化してから、通常のBabelコンパイルを行う
3. [Static TypeScript](https://www.infoq.com/jp/news/2019/11/static-typescript-msft-paper/)のように直接バイナリにコンパイルする

これらのうち、Create React Appが採用しているのは、2番めの方式です。`noEmit`オプションがついた`tsc`コマンドを実行することで、静的型チェックを行うことはありますが、あくまでもチェックのみで、コンパイルやトランスパイルといったことは`tsc`の責務に含まれません。

この方式を取っている場合、文法を最終的に実行可能な形式に落とし込むのは、Babel側の責務です。**Babelが処理できない文法は扱うことができない**のです。今回のケースでは、Babel側が全く知らないというわけではなく、「薄々知ってはいるけど、まだStage 3でexperimental扱いなので、デフォルトでは有効ではない」ということをエラーメッセージで知らせてくれている形でした。

### 素のCRAでは使えない

エラーメッセージにもあるとおり、 `@babel/plugin-proposal-optional-chaining` や `@babel/plugin-proposal-nullish-coalescing-operator` のプラグインをインストールし、`babel.config.js`等の設定ファイルへ適切に適用すれば、新しい文法が有効になります。

さて、ここで残念なお知らせがあるのですが、Create React Appには `babel.config.js` や `.babelrc` を置くことができません。また、 `react-scripts` のv3.2.0にはこれらのプラグインが含まれていません。

というわけで、詰みました。 `react-scripts` のアップデートをお待ちください。幸い、既に[プルリクエストがマージされて](https://github.com/facebook/create-react-app/pull/7438)います。じきにリリースされるでしょう。

新しい文法さえ使わなければビルドはできるので、もう少し新文法を使うのは我慢しましょう。

## react-app-rewiredでがんばる

ここで終わってしまうとタイトルが回収できないので、「いやだいいやだい！　僕はすぐに新文法を使いたいんだい！！」という皆さん（主に私）はどうすればいいのかという話をします。

まあCRAからの派生で選べる選択肢というのはさほど多くはなく、

* ejectする
* [react-app-rewired](https://github.com/timarney/react-app-rewired)を使う

のどちらかになります。今回はrewiredを使いましょう。

普通のセットアップ方法（`config-overrides.js`を作るところまで）の解説は[公式README](https://github.com/timarney/react-app-rewired/blob/master/README.md)に譲るとして、その先の話をします。逆に、セットアップができない人がこの先の操作をするのは危険なので、この記事を閉じたほうがよさそうです。

まずは必要なパッケージをインストールしておきましょう（npmの人はいい感じに読み替えてください）。

```shell-session
$ yarn add babel-loader @babel/plugin-proposal-optional-chaining @babel/plugin-proposal-nullish-coalescing-operator
```

次に、`config-overrides.js`には次のような設定を行います。

```js:config-overrides.js
module.exports = function override(config, env) {
  config.module.rules.push({
    test: /\.tsx?$/,
    use: {
      loader: 'babel-loader',
      options: {
        // 設定ファイルは使わない
        babelrc: false,
        configFile: false,

        // 元の動きを再現する（つもり）
        presets: [["react-app", { flow: false, typescript: true }]],
        // 今回追加するプラグイン
        plugins: [
          '@babel/plugin-proposal-optional-chaining',
          '@babel/plugin-proposal-nullish-coalescing-operator',
        ],
      },
    },
  });

  return config;
};
```

なけなしのwebpack筋で書いたので、間違っているところもありそうという気持ちはあるのですが、ひとまず私はこれで急場を凌いでいます。

それではビルドしてみましょう。

```shell-session
$ yarn build
yarn run v1.19.0
$ react-app-rewired build
Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

  39.84 KB  build/static/js/2.7713c0fe.chunk.js
  773 B     build/static/js/runtime-main.42fdd2c2.js
  709 B     build/static/js/main.bce5e502.chunk.js
  418 B     build/static/css/main.dfca195d.chunk.css

The project was built assuming it is hosted at the server root.
You can control this with the homepage field in your package.json.
For example, add this to build it for GitHub Pages:

  "homepage" : "http://myname.github.io/myapp",

The build folder is ready to be deployed.
You may serve it with a static server:

  yarn global add serve
  serve -s build

Find out more about deployment here:

  https://bit.ly/CRA-deploy

✨  Done in 12.92s.
```

うまくいったようです。

## まとめ

過渡期ということで、かなりお手軽にハマれてしまう案件が発生していたため、記事にしてみました。

`react-scripts` がアップデートされたら陳腐化する記事なので、早く陳腐化してほしいなあ。（12/5追記：陳腐化してよかったー）

それはそれとして、webpack筋がある程度ある人に取っては `react-app-rewired` はこういう時の脱出ハッチとしてめちゃくちゃ便利なので、使い方を覚えておくと便利です。濫用すると死ぬけど、折を見て使ってみてくださいね。