Edited at

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

More than 1 year has passed since last update.

(2018/10/03 追記)

Babel が正式に v7 リリースされたので、書き直しました。

変更点などは詳しくはこちらで確認してください。


概要

2018年8月28日、ついに Babel の v7 がリリースされました!!!!


React/Redux の開発環境構築の記事は多いけれども、Babelv7 を使用したものがあまり無いなと感じたので、ここに(備忘録も兼ねて)書き記します。

2017年からこの1年で React で開発する際の周辺モジュールは


  • React : 15.4 => 16.5.2

  • webpack : 2.4 => 4.20.2

  • Babel : babel-preset-2015 => @babel/preset-env

このようにバージョンが上がり、中には使用するものが変わったものまででてきました。

個人的に Babel 周りのパッケージが @babel/package 化されてたり、されてないものもあって設定するときに混乱するので、つらい。。。


モジュールのインストール

先ずは必要なモジュールをどんどんインストールしていきます。

1.React

yarn add react react-dom

また、styled-componentsもインストールしておきます。

yarn add styled-components

2.webpack

webpackv4からはwebpack-cliを別途インストールする必要があります。

yarn add -D webpack webpack-cli webpack-dev-server

ビルドする際のモジュールもインストールします。

yarn add -D css-loader uglifyjs-webpack-plugin

3.Babel

先にBabelでモノレポ管理されているものをインストールします。

yarn add -D @babel/preset-env @babel/preset-react @babel/register  @babel/plugin-proposal-object-rest-spread

なお、今までbabel-preset-stage-nという Stage Preset がありましたが、これらはv7からは非推奨となっています(package自体はまだ存在しています)。

@babel/plugin-proposal-object-rest-spreadのように、必要なpluginを各自で別途インストールするように変わりました。

次に、@babel/package でないbabel系のパッケージをインストールします。

yarn add -D babel-eslint babel-loader babel-plugin-istanbul babel-plugin-styled-components babel-preset-power-assert

4.eslint

yarn add -D eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react

5.prettier

構文チェックはeslintに任せ、コードフォーマットはprettierに任せます。ここではeslintの設定にprettierのルールをのせることにします。

yarn add -D prettier eslint-plugin-prettier eslint-config-prettier

6.test関連

yarn add -D mocha power-assert enzyme enzyme-adapter-react-16 jsdom testdouble

Reactのテストにはjestが標準となっていますが、入れていません。jestで組みたい方は別途インストールしてください!


Babelの設定

{

"presets": [
["@babel/preset-env", {
"targets": {
"browsers": ["last 2 Chrome versions"]
},
"modules": false
}],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-object-rest-spread",
],
"env": {
"test": {
"presets": ["power-assert"],
"plugins": [
["istanbul", {
"exclude": ["test/**/*.spec.js"]
}],
["babel-plugin-styled-components", {
"fileName": false
}]
]
}
}
}

babel-plugin-istanbulはテストのカバレッジを計測するためのもので、

babel-plugin-styled-componentsは、styled-componentsで定義されたコンポーネントに対して自動的にdisplayNameを付与してくれます。


babel-plugin-styled-components

例えば、styled-componentsで

const Header = styled.header`

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の設定

'use strict';

const OFF = 0;
const ERROR = 2;

module.exports = {
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 8,
sourceType: 'module',
ecmaFeatures: { jsx: true },
},
env: {
node: true,
es6: true,
browser: true,
mocha: true,
},
extends: ['airbnb', 'prettier'],
plugins: ['react', 'prettier'],
rules: {
'import/first': OFF,
'prettier/prettier': [
ERROR,
{
singleQuote: true,
printWidth: 100,
trailingComma: 'all',
jsxBracketSameLine: true,
},
],
/* 以下省略 */
},
};


webpackの設定

Reactのルートとなるソースコードがsrc/App.jsにあるとしてエントリーポイントを設定しています。

const path = require('path');

const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

const PRODUCTION = NODE_ENV === 'production';

const PRODUCTION_PLUGINS = [
new UglifyJSPlugin({
uglifyOptions: { compress: { drop_console: true } },
sourceMap: true,
}),
];

module.exports => {
{
entry: ['./src/App.js'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build'),
publicPath: '/build/',
},
devServer: {
port: 3000,
contentBase: './',
},
devtool: PRODUCTION ? 'hidden-source-map' : 'source-map',
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
loaders: ['style-loader', 'css-loader?modules'],
},
]
},
resolve: {
extensions: ['.js', '.jsx'],
},
optimization: {
minimizer: PRODUCTION ? PRODUCTION_PLUGINS : [],
},
};
};

webpackのmodeやdev-serverの設定をあまり書いていませんが、そちらは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.js

import React from 'react';
import ReactDom from 'react-dom';
import Root from './components/Root';

ReactDom.render(
<Root />,
document.getElementById('content'),
);

このApp.jsが起点となります。

続いてRoot.jsを作ります。

// src/components/Root.js

import React from 'react';

const Root = () => (
<div>
Hello World
</div>
);

export default Root

ここまでの設定が終わりましたら、npm startで開発サーバを起動すると画面にHello Worldが出力されていると思います。

これで、開発環境の設定は終わりました!


test環境の構築

最後に、test環境を構築していきます。

構築といっても、テストファイルは作らず設定ファイルのみ作成します。

// test/setup.js

require('@babel/register');

import { JSDOM } from 'jsdom';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

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

const DOCTYPE = '<!doctype html>';
const BODY = '<html><body></body></html>';
const HTML = `${DOCTYPE}${BODY}`

const fakeMatchMedia = {
matches: false,
addListener: () => {},
removeListener: () => {},
};

const jsdom = new JSDOM(
HTML,
{
url: 'http://fake.com',
referrer: 'http://fakeReferrer.com',
userAgent: 'Macintosh',
includeNodeLocations: true,
},
);

const { window } = jsdom;
const { document } = window;

global.document = document;
global.window = window;

global.location = global.window.location;
global.window.matchMedia = fakeMatchMedia;

global.requestAnimationFrame = callback => setTimeout(callback, 0);

global.reconfigure = url => jsdom.reconfigure({ url });
global.reconfigureReset = () => jsdom.reconfigure({ url: 'http://fake.com '});

ブラウザではグローバルオブジェクトはwindowオブジェクトですが、nodeではglobalオブジェクトになります。

window.locationやwindow.navigator.userAgentなどは存在しないので、jsdomでDOMを作ります。

その作ったDOMをglobal.windowとして代入します。

こうすることで、ブラウザのwindowオブジェクトにアクセスすることができるようになります。

なお、matchMediaやlocalStorageなど一部のオブジェクトは含まれていないので注意してください。

含まれていないオブジェクトは


const fakeMatchMedia = {
matches: false,
addListener: () => {},
removeListener: () => {},
};
global.window.matchMedia = fakeMatchMedia;

このように自分で作成することでアクセスできます。

また、jsdomは@9からurlを直接書き換えることができなくなり、jsdom.reconfigureメソッドを介してのみ変更できるようになっています。

テスト時に動的にurlを変更できるようにglobalオブジェクトに関数として定義しています。

テストする際のスクリプトは



"scripts": {
"test": "NODE_ENV=test mocha --recursive test/ -r @babel/register -r test/setup.js",
"test:watch": "NODE_ENV=test npm test -- --watch"
}

testフォルダ配下のファイルをテストするように設定しています。


終わり

設定は以上になります!