(2018/10/03 追記)
Babel が正式に v7 リリースされたので、書き直しました。
変更点などは詳しくはこちらで確認してください。
(2020/05/18 追記)
色々古くなったので更新しました。
なお、Facebook社が新しく公開したRecoilについてやReduxについては本記事では触れません。
TypeScriptベースでのReactの開発環境構築の内容になります。
また、TypeScript => JavaScriptの変換にはts-loader、JavaScriptのトランスパイルにはBabelを使うように書き直しました。
概要
2018年8月28日、ついに Babel の v7 がリリースされました!!!!
React/Redux の開発環境構築の記事は多いけれども、Babelv7 を使用したものがあまり無いなと感じたので、ここに(備忘録も兼ねて)書き記します。
2017年からこの3年で React で開発する際の周辺モジュールは
- React : 15.4 => 16.13.1
- webpack : 2.4 => 4.43.0
- Babel : babel-preset-2015 => @babel/preset-env
- Linting : TSLint => ESLint
このようにバージョンが上がり、中には使用するものが変わったものまででてきました。
そして、TypeScriptで記述するのがデファクトスタンダートとなっています。
個人的に Babel 周りのパッケージが @babel/package 化されてたり、されてないものもあって設定するときに混乱するので、つらい。。。
なお、TypeScriptを導入すればBabelは必須ではなくなります。
また、BabelからTypeScriptのコードをJavaScriptにトランスパイル(@babel/preset-typescript)することもできるようにもなっています。
一部変換できないコードもありますが、詳しい説明は他の記事に譲ることとします。
モジュールのインストール
先ずは必要なモジュールをどんどんインストールしていきます。
1.React
yarn add react react-dom
npmを使っている方は、下記のコマンドを実行してください。
npm install react react-dom
ついでにcss-in-jsのモジュールをインストールしましょう。
cssModulesを採用する場合はインストールの必要はありません。
yarn add styled-components
styled-componetsかemotionかはお好みですが、ここではstyled-componentsを採用します。
- TypeScript
yarn add -D typescript
styled-componentsを使う場合、下記モジュールもインストールしましょう。
yarn add -D
このプラグインを使うと、TypeScriptでstyled-componentsの補完とリンティングがきくようになります。
3.webpack
webpackv4からはwebpack-cliを別途インストールする必要があります。
yarn add -D webpack webpack-cli webpack-dev-server
ビルドする際のモジュールもインストールします。
yarn add -D terser-webpack-plugin fork-ts-checker-webpack-plugin babel-loader ts-loader
4.Babel
先にBabelでモノレポ管理されているものをインストールします。
yarn add -D @babel/core @babel/preset-env @babel/preset-react @babel/plugin-proposal-object-rest-spread @babel/plugin-proposal-class-properties @babel/plugin-syntax-dynamic-import
なお、今までbabel-preset-stage-n
という Stage Preset がありましたが、これらはv7からは非推奨となっています(package自体はまだ存在しています)。
@babel/plugin-proposal-object-rest-spread
のように、必要なpluginを各自で別途インストールするように変わりました。
次に、@babel/package でないbabel系のパッケージをインストールします。
yarn add -D babel-plugin-styled-components babel-preset-power-assert
5.ESLint
yarn add -D eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks
次に、ESLintでTypeScriptを静的解析できるように下記のモジュールをインストールします。
yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
6.prettier
構文チェックはESLintに任せ、コードフォーマットはprettierに任せます。ここではESLintの設定にprettierのルールをのせることにします。
yarn add -D prettier eslint-plugin-prettier eslint-config-prettier
7.test関連
yarn add -D jest ts-jest jest-fetch-mock eznyme enzyme-adapter-react-16 power-assert testdouble
Reactのテストにはjestが標準とされているので、jestでセットアップをしていきます。
power-assertとtestdoubleはお好みでインストールしてください(jestが提供しているAPIでも全然問題はありません。)
(2020/05/18 追記)
現在Reactの公式サイトでは、enzymeではなくtesting-libraryを使うように推奨されています。
enzymeはprops/stateをベースにテストをすることを重きに置いており、testing-libraryはユーザストーリーをベースにテストをすることに重きを置いています。
歴史を残す意味でenzymeを使用していますが、testing-libraryを導入することを推奨します。
なお、MaterialUIのようなUI Libraryを作るようであれば、enzymeでのテストをすることを筆者はおすすめします。
何を担保したいかによりますが、testing-libraryだとテストできないようなケースが場合によってはでてきます。
8.型定義ファイル
TypeScriptで補完や型エラーの検出ができるように型定義ファイルをインストールします。
yarn add -D @types/enzyme @types/enzyme-adapter-react-16 @types/jest @types/power-assert @types/react @types/react-dom @types/styled-components
TypeScriptの設定
targetやmoduleは各プロジェクトでターゲットするものを選択してください。
{
"compilerOptions": {
"target": "es5",
"module": "ESNEXT",
"lib": ["es2020", "dom"],
"experimentalDecorators": true,
"jsx": "react",
"declaration": false,
"sourceMap": true,
"noEmitOnError": true,
"strict": true,
"downlevelIteration": true,
"noImplicitThis": true,
"alwaysStrict": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"baseUrl": ".",
"plugins": [
{
"name": "typescript-styled-plugin"
}
],
},
"exclude": [
"jest.config.js",
"test/**/*"
]
}
Babelの設定
Babelの設定ファイルはjson,yaml,javascriptなどで記述できますが、javascriptで記述します。
const presets = [
[
'@babel/preset-env',
{
targets: { ie: 11 },
modules: false,
},
],
'@babel/preset-react',
];
const plugins = [
[
'babel-plugin-styled-components',
{
ssr: false, // ssrする場合はtrue
minify: true,
pure: true,
transpileTemplateLiterals: true,
fileName: true,
},
],
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-syntax-dynamic-import',
];
module.exports = { presets, plugins };
babel-plugin-styled-componentsは、styled-componentsで定義されたコンポーネントに対してdisplayNameを付与したりminifyしたりと詳細な設定を行うことができます。
babel-plugin-styled-components
例えば、styled-componentsで
const Header = styled.header`
width: 100%;
height: 60px;
font-size: 1.5rem;
text-align: center;
background-color: #00BCD4;
color: #FFFFFF;
z-index: 999;
`;
export default Header;
このように定義されたコンポーネントがあったとします。
このHeaderが使用されたコンポーネントをenzymeでテスト際にこのプラグインを使えば
const wrapper = shallow(<Hoge />);
assert(wrapper.find('Header').length === 1);
findメソッドで定義したコンポーネントの名前でそのまま取得できるようになります。
プラグインを噛まさない場合、Headerと入力しても取得することができません。
displayNameがないのでenzymeでは[React.Element]
として定義されていたと思います(多分)。
ESLintの設定
ESLintには厳しめのルールで有名なairbnbを採用します。
また、prettierのルールをESLint側にも書いていますが、eslint-config-prettierの設定をカスタマイズしたい場合はruleプロパティの中に直接書いて変更しましょう。
const OFF = 0;
const WARNING = 1;
const ERROR = 2;
module.exports = {
parser: '@typescript-eslint/parser',
extends: [
'airbnb',
'airbnb/hooks',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'prettier/@typescript-eslint',
'prettier/react',
],
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
project: './tsconfig.json',
tsconfigRootDir: '.',
},
env: {
jest: true,
browser: true,
},
settings: {
react: {
version: 'detect',
},
'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
'import/resolver': {
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
},
rules: {
'prettier/prettier': [ERROR, /* 上書きするprettierの設定 */],
/* 省略 */
},
};
webpackの設定
Reactのルートとなるソースコードがsrc/App.js
にあるとしてエントリーポイントを設定しています。
const TerserPlugin = require('terser-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const path = require('path');
const MODE = process.env.NODE_ENV || 'development';
const PRODUCTION = MODE === 'production';
const PRODUCTION_PLUGIN = [
new TerserPlugin({
parallel: true,
cache: true,
sourceMap: true,
terserOptions: {
output: {
comments: false,
},
},
}),
]
module.exports => {
{
mode: MODE,
devtool: PRODUCTION ? 'hidden-source-map' : 'source-map',
entry: {
bundle: './src/App.tsx',
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'build'),
chunkFilename: '[name].js',
publicPath: '/build/',
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{ loader: 'babel-loader' },
{ loader: 'ts-loader', options: { transpileOnly: true } },
],
},
]
},
plugins: [
new ForkTsCheckerWebpackPlugin({
async: true,
eslint: true,
watch: './src/App.tsx',
tsconfig: './tsconfig.json',
}),
],
optimization: {
minimizer: PRODUCTION ? PRODUCTION_PLUGIN : [],
splitChunks: {
cacheGroups: {
vendor: {
test: /react|react-dom/,
name: 'vendor',
chunks: 'initial',
enforce: true,
},
},
},
},
performance: {
hints: false,
},
};
また、npm scriptnの方にこのように設定しています。
"scripts": {
"start": "webpack-dev-server --mode development --inline --history-api-fallback --no-info --open",
"release": "NODE_ENV=production webpack --mode production"
}
webpack-serveについて
webpack-dev-serverの後継モジュールとしてwebpack-serveがあります。
公式ページよりwebpack-serveを使うようにアナウンスされていましたが、2018年9月18日に再びwebpack-dev-serverで更新が行われるようになりました。
なんだったんだお前は・・・
webpack-serve は現在 DEPRECATED になっています
Reactコンポーネントの作成
ReactでHello Worldを表示できるようにコンポーネントを作成します。
import React from 'react';
import ReactDom from 'react-dom';
import { Root } from './components/Root';
ReactDom.render(
<Root title="Hello World" />,
document.getElementById('content'),
);
このApp.tsxが起点となります。
続いてRoot.tsxを作ります。
import React from 'react';
type Props = {
title: string;
}
export const Root:React.FC<Props> = ({ title }) => (
<div>{title}</div>
);
ここまでの設定が終わりましたら、npm start
で開発サーバを起動すると画面にHello Worldが出力されていると思います。
これで、開発環境の設定は終わりました!
test環境の構築
最後に、test環境を構築していきます。
構築といっても、テストファイルは作らず設定ファイルのみ作成します。
const dir = __dirname;
module.exports = {
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
setupFiles: ['./test/setup.js'],
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
coveragePathIgnorePatterns: [
'<rootDir>/node_modules/',
],
resolver: null,
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};
jestの設定ファイルをルートのパスに作成し、TypeScriptで記述されたファイルをテスト対象にふくめます。
また、enzyme等の設定ファイルはtest/setup.jsに別途記述し、それを読みこむようにします。
const React = require('react');
const fetch = require('jest-fetch-mock');
const Enzyme = require('enzyme');
const Adapter = require('enzyme-adapter-react-16');
Enzyme.configure({ adapter: new Adapter() });
global.fetch = fetch;
// React.memo, React.Suspenseを実行できるようにする
jest.mock('react', () => {
const r = jest.requireActual('react');
const Suspense = ({ children }) => `<div>${children}</div>`;
Suspense.displayName = 'Suspense';
return { ...r, memo: x => x, Suspense };
});
2019年ごろまではenzymeがReact.memo,React.SuspenseなどのAPIに対応しておらず、モックをして強制的に動くようにしています。
Suspenseはただのテキストが返されるなど不完全な対応をしていますが、今だと対応して動くようになっているかもしれません(未検証ですみません、、、)
テストする際のスクリプトは
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
}
testフォルダ配下のファイルをテストするように設定しています。
終わり
設定は以上になります!