Help us understand the problem. What is going on with this article?

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

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

日本時間の2019年12月5日朝に react-scripts のv3.3.0がリリースされました

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

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

ところで余談なんですけど、CRA + TSの初期化コマンドにつけるオプションが --typescript から --template typescrpt に変わってます(PR)。ドキュメントも全てこちらに書き換わっているので、うっかり実行してしまうのですが、手元に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がリリースされました

Optional Chainging(hoge?.fuga)やNullish Coalescing(hoge ?? fuga)といった、便利な文法が取り込まれ、多くの人に祝福されたリリースとなりました。TypeScript 3.7のマイルストーンが付いたことでOptional Chaining導入検討Issueがお祭りムードになったのは記憶に新しいところです。

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

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

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

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が入っていますね。

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

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コマンドでの静的型チェックの結果は、特に問題が起こりません。

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

$ 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でも同じことを言われます。

$ 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の責務

以前の記事でも言及したことがあったのですが、Create React AppのTypeScriptサポートは、Babelの仕組みに乗っかったものです。

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

  1. tsc コマンドで.ts(x)ファイルを.jsファイルにコンパイルする(ts-nodeもこれのはず)
  2. @babel/preset-typescript で「TypeScript独自文法」を剥がしてECMAScript化してから、通常のBabelコンパイルを行う
  3. Static TypeScriptのように直接バイナリにコンパイルする

これらのうち、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 のアップデートをお待ちください。幸い、既にプルリクエストがマージされています。じきにリリースされるでしょう。

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

react-app-rewiredでがんばる

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

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

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

普通のセットアップ方法(config-overrides.jsを作るところまで)の解説は公式READMEに譲るとして、その先の話をします。逆に、セットアップができない人がこの先の操作をするのは危険なので、この記事を閉じたほうがよさそうです。

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

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

次に、config-overrides.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筋で書いたので、間違っているところもありそうという気持ちはあるのですが、ひとまず私はこれで急場を凌いでいます。

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

$ 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 はこういう時の脱出ハッチとしてめちゃくちゃ便利なので、使い方を覚えておくと便利です。濫用すると死ぬけど、折を見て使ってみてくださいね。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした