TypeScript + create-react-app で eject せずツールを導入する

create-react-appv2.1.0 で TypeScript を公式サポートしました!

ということで、悪名高き “eject” をせずにどこまで外部ツールを設定できるか挑戦してみました。
npm run eject をせず、また、 react-app-rewired / craco は使わないようにします。

もう一つの動機としては、私は Webpack が好きなので手で設定をしてしまうことが多いですが、他のメンバーが管理できる範囲に収めたほうが喜ばれそう、というのもあります。

サンプルプロジェクトはこちら

TL;DR

create-react-app でプロジェクトを作った後、 Storybook と Linter(for TypeScript & CSS/SCSS) と E2E テストは自分で設定しなければならない。
ただし、 Storybook は v4.1 から設定不要になり、 TypeScript の Linter 設定も今後は不要になるかもしれない。

まえがき

以前 create-react-app (以下、省略して CRA )を使った際は、コミュニティが用意した TypeScript テンプレートはありましたが、ツールを追加しようとすると “eject” が必要なことも多く、積極的に使おうという気分になりませんでした。
後から rewired の存在を知りましたが、 CRA サポート外になることと、結局 Webpack の設定を改変することになるので設定に詳しくなる必要がありそうで、しっくりしませんでした。
CRA 2 からは TypeScript のコンパイルだけでなく SVG や CSS を import するときに必要となる型定義ファイル ‘react-scripts/lib/react-app.d.ts’ も提供されているため、以前の create-react-app で発生したような問題は起きにくくなっていました。これからは本当に設定レスで開発していけそうな未来を感じました。

Node: 10.14.1
create-react-app: 2.1.1
その他は package.json をご参照ください。

create-react-app

肝心な CRA を使うところから。といっても、下のコマンドを実行するだけで終わります。 TypeScript の公式サポートにより、 --typescript というオプションを付けて実行することで TypeScript での開発に必要なものがすべて封入されます。

$ npx create-react-app your-project --typescript

TypeScript でプロジェクトを示す ‘tsconfig.json’ も一緒に生成されます。CRA の標準では TypeScript ファイルは Babel によりトランスパイルされますが、型チェックは別途 TypeScript コンパイラ(?)を使っています。
ここの設定値を大きく変えると Babel でトランスパイルできないコードになる場合もあります。ただし、後から追加するツールのために、 "skipLibCheck": true としておきます。( Webpack の処理が高速になる効果もあります)

Stylesheet

CRA では、普通の CSS も CSS Modules も、デフォルトで対応しています。前者は “.css” を、後者は “.module.css” にすればそのように動きます。

SASS/SCSS を使いたい場合は、加えて

$ npm install node-sass --save-dev

とすることで、普通の SASS/SCSS とそれを利用した CSS Modules に対応できます。設定を弄る必要はありません。前者は “.sass”/“.scss” を、後者は “.module.sass”/“.module.scss” にすればそのように動きます。

一方、 CSS in JS ライブラリを使う場合は自分で設定する必要があります。Babel が必要になるライブラリは厳しいかもしれません…

Linter & Formatter

Prettier

Prettier は下のコマンドでインストールできます。コードスタイルを変更しない限り Prettier 自体に設定を加える必要はありません。

$ npm install prettier --save-dev

TSLint

CRA には ESLint が付属していて JavaScript ファイルに対しては npm start の最中にチェックしてくれますが、 TypeScript ファイルはチェックしてくれません。
この記事ではひとまず TSLint を使うことにします。( Pull Request が出されている模様 Add support for TSLint by ianschmitz · Pull Request #5697 · facebook/create-react-app この記事とはルールは違います)

TSLint と、 Prettier を組み合わせるためのツールも含めてインストールします。

$ npm install tslint tslint-react tslint-config-prettier tslint-plugin-prettier --save-dev

‘tslint.json’ は次のように書けばこれらが有効になります。

1
2
3
4
5
{
"extends": ["tslint:latest", "tslint-react", "tslint-config-prettier"],
"rulesDirectory": ["tslint-plugin-prettier"],
"rules": { "prettier": true }
}

stylelint

CRA では CSS 用の Lint は何も用意されていないため、 stylelint を導入しておきます。
こちらも stylelint と、 Prettier を組み合わせるためのツールも含めてインストールします。

$ npm install stylelint stylelint-config-recommended stylelint-config-prettier stylelint-prettier --save-dev

‘.stylelintrc’ は次のように書けばこれらが有効になります。

.stylelintrc (for normal CSS)
1
2
3
{
"extends": ["stylelint-config-recommended", "stylelint-prettier/recommended"]
}

CSS Modules を使う場合は、さらに下のコマンドを実行し、

$ npm install stylelint-config-css-module --save-dev

‘.stylelintrc’ の extends の真ん中に "stylelint-config-css-module", と加えれば CSS Modules 用に動くはずです。

SCSS で使う場合には、 stylelint のインストールのコマンドの代わりに、

$ npm install stylelint stylelint-scss stylelint-config-recommended-scss stylelint-config-prettier stylelint-prettier --save-dev

として、 ‘.stylelintrc’ を次のように書いて有効化します。

.stylelintrc (for normal SCSS)
1
2
3
{
"extends": ["stylelint-config-recommended-scss", "stylelint-prettier/recommended"]
}

SCSS で CSS Modules を使う場合は、上記の設定に加え、下の設定コードを rules に加えます。 “stylelint-config-recommended-scss” と “stylelint-config-css-module” の設定が競合するため、後者の一部の rules をそのまま持ってきている形です。(微妙…)

.stylelintrc rules (for SCSS + CSS Modules)
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"selector-pseudo-class-no-unknown": [
true,
{
"ignorePseudoClasses": [
"export",
"import",
"global",
"local",
"external"
]
}
],
"selector-type-no-unknown": [
true,
{
"ignoreTypes": ["from"]
}
],
"property-no-unknown": [
true,
{
"ignoreProperties": ["composes", "compose-with"]
}
]
}

最後に “package.json” の scripts"lint": "tslint -p tsconfig.json & stylelint '**/*.{css,scss}'", を加え、 CI 時などに npm test の近くに npm run lint の行を追加するとか commit 時に自動で実行するとかすれば設定はおしまいです。

Storybook

v4.0 まで

(※サンプルプロジェクトでは ‘.storybook/for-v4_0.webpack.config.js’ に設置)

Storybook は下のコマンドを実行するだけで導入できます。(記事を書いた時点では v4.0 が導入されます。)

$ npx -p @storybook/cli sb init

v4.0 では TypeScript 用 CRA の設定ファイルを読み込んでくれないので、ちょっとしたスクリプトを書く必要があります。かなり Hacky な部分もありますが、やっているのは CRA の設定を Storybook へバイパスしつつ “.ejs” ファイルの扱いを変えているという処理です。

.storybook/webpack.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const dev = require("react-scripts/config/webpack.config.dev");

// resolve .ejs error
dev.module.rules[dev.module.rules.length - 1].oneOf.forEach(e => {
if (
e.loader &&
e.loader.includes("file-loader") &&
Array.isArray(e.exclude)
) {
e.exclude.push(/\.ejs$/);
}
});

module.exports = {
module: dev.module,
resolve: dev.resolve
};

あとは ‘src/stories’ にファイルを追加していくのみです。生成される ‘src/stories/index.js’ を “src/stories/index.tsx” にしておくと混乱せずよいです。

次に、型ファイルをインストールします。

$ npm install @types/storybook__react @types/storybook__addon-actions @types/storybook__addon-links --save-dev

ストーリーファイルで TSLint がエラーを出すのを防ぐため、 // tslint:disable:no-implicit-dependencies をファイルの先頭につけておきます。(TSLint の設定で消せたとも思いますがファイル限定ではないので…)
(ちなみに demo 用のストーリーに型はつけられていないので、適当に差し替えておく。)

v4.1 から

まだ alpha なので変わるかもしれませんが、 Storybook が TypeScript 用の CRA 設定を読み込んでくれるようになるため、設定関連ではもう何もしなくて良くなるはずです。
Add TypeScript support for react-scripts by mrmckeb · Pull Request #4824 · storybooks/storybook
(今のコードを見る限りでは、型ファイルのインストールはまだ必要そうですが…)

Unit Test

Jest

CRA では初期状態で Jest が導入されています。 babel-jest も導入されているので、 TypeScript ファイルも設定なしで動きます。
ただし、 React 用のテストツールは入っていないので、必要に応じてどちらかをインストールする必要があります。利用実績という面では Enzyme が良さそうにも見えますが、設定が必要ない、という点では react-testing-library のほうが楽だと思います。

Enzyme

Enzyme を利用する場合は、下のようにパッケージをインストールし、

$ npm install enzyme @types/enzyme enzyme-adapter-react-16 --save-dev

‘src/setupTests.js’ ファイルを作成して下のように記述します。

src/setupTests.js
1
2
3
4
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";

configure({ adapter: new Adapter() });

react-testing-library

react-testing-library の場合は、下のようにパッケージをインストールするだけです。

$ npm install react-testing-library jest-dom --save-dev

E2E Test

この記事では、 E2E 用には Puppeteer を使うことにします。 Selenium でもいいかもしれません。

E2E は npm run build の生成物に対して行えばいいだけなので CRA は直接関わりがありません。単体テスト用の Jest の設定とかぶらないようにしておけば問題は起きないはずです。
Jest 用プリセットとして “jest-puppeteer” を使います。これを使えば Jest + Puppeteer での面倒な設定はほとんど消え去ります。
下のコマンドでインストールします。

$ npm install puppeteer jest-puppeteer expect-puppeteer @types/puppeteer @types/jest-environment-puppeteer @types/expect-puppeteer serve --save-dev

E2E 用 Jest 設定は、次のように書けばうまくいきます。 ‘jest.e2e.config.js’ は Jest そのものの設定で、 ‘jest-puppeteer.config.js’ は jest-puppeteer の設定になります。
一部 CRA の設定から引っ張ってきています。また、 E2E テストコードは ‘e2e’ ディレクトリ以下に、ポート番号は 8888 番と決めてあります。もちろん自由に変えることができますが、単体テストのファイル名や開発サーバのポートと被らないように気をつけてください。

jest.e2e.config.js
1
2
3
4
5
6
7
8
9
10
11
const moduleFileExtensions = require("react-scripts/config/paths")
.moduleFileExtensions;

module.exports = {
preset: "jest-puppeteer",
testMatch: ["<rootDir>/e2e/**/*.{js,jsx,ts,tsx}"],
transform: {
"^.+\\.(js|jsx|ts|tsx)$": "react-scripts/config/jest/babelTransform.js"
},
moduleFileExtensions
};
jest-puppeteer.config.js
1
2
3
4
5
6
module.exports = {
server: {
command: "npx serve -s build -p 8888",
port: 8888
}
};

最後に “package.json” の scripts"e2e-test": "jest --config=jest.e2e.config.js", を加え、 CI 時などで npm run build の後に npm run e2e-test の行を追加するなどすれば設定はおしまいです。

まとめ

CRA 2 でのプロジェクト作成から様々なツールを導入していくところを追っていきました。

“eject” せずにツールを追加できたものの、 Storybook v4.0 での設定は Hacky で、 Lint と E2E は全部自分で設定するなど、まだまだ面倒な部分がありました。
(E2E テストに関しては想定していないんでしょうけれども…)

Storybook v4.1 の正式版リリース後や CRA のバージョンアップが進めば、もっと設定が楽になると思います。