本記事の陳腐化のお知らせ
日本時間の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のアップデートを待ったほうがいいと思う
はじめに
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は、次のようになります。
"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が入っていますね。
やったー! これで僕らも快適生活の仲間入りだ! というわけで、次のようなコードを書いてみます。
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を実行可能なコードに変換するスタイルは、いくつかあります。
-
tsc
コマンドで.ts(x)ファイルを.jsファイルにコンパイルする(ts-nodeもこれのはず) -
@babel/preset-typescript
で「TypeScript独自文法」を剥がしてECMAScript化してから、通常のBabelコンパイルを行う - 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からの派生で選べる選択肢というのはさほど多くはなく、
- ejectする
- react-app-rewiredを使う
のどちらかになります。今回はrewiredを使いましょう。
普通のセットアップ方法(config-overrides.js
を作るところまで)の解説は公式READMEに譲るとして、その先の話をします。逆に、セットアップができない人がこの先の操作をするのは危険なので、この記事を閉じたほうがよさそうです。
まずは必要なパッケージをインストールしておきましょう(npmの人はいい感じに読み替えてください)。
$ yarn add babel-loader @babel/plugin-proposal-optional-chaining @babel/plugin-proposal-nullish-coalescing-operator
次に、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
はこういう時の脱出ハッチとしてめちゃくちゃ便利なので、使い方を覚えておくと便利です。濫用すると死ぬけど、折を見て使ってみてくださいね。