LoginSignup
20

More than 3 years have passed since last update.

Babel@7で構築するReact開発環境

Last updated at Posted at 2018-05-12

(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を採用します。

  1. 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を表示できるようにコンポーネントを作成します。

src/App.tsx
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を作ります。

src/components/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環境を構築していきます。
構築といっても、テストファイルは作らず設定ファイルのみ作成します。

jest.config.js
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に別途記述し、それを読みこむようにします。 

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フォルダ配下のファイルをテストするように設定しています。

終わり

設定は以上になります!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20