React開発環境構築2018

#この記事について

この記事では実際にReactの開発環境をセットアップする方法を説明します。ReactなどのSPAでネックになる速度を改善するための手法としてサーバーサイドレンダリングも行います。

- **[出来る限り短く説明するReact.js入門](https://qiita.com/rgbkids/items/8ec309d1bf5e203d2b19)** | Reactとは？を解説してくれている良記事

フロントエンド開発についてまだよく知らないという人は下の記事を読んでおきましょう。

- **[今時のフロントエンド開発2017 (1. 愚痴編)](https://qiita.com/hiroiku/items/783a972bace33002455c)**
- **[2018年の最先端フロントエンドエンジニアになろう](https://qiita.com/rana_kualu/items/5986ceab8fd4e353de1b)**

フロントエンドとはいえ、今回は静的サイトではなく動的サイト（サーバーサイドレンダリングを行うため）を作成するのでサーバー側の話がメインとなります。

- [**ユーザー体験を向上させるサーバーサイドレンダリングJavaScript — 歴史と利点**](https://medium.com/@sundaycrafts/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E4%BD%93%E9%A8%93%E3%82%92%E5%90%91%E4%B8%8A%E3%81%95%E3%81%9B%E3%82%8B%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%82%B5%E3%82%A4%E3%83%89%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0javascript-%E6%AD%B4%E5%8F%B2%E3%81%A8%E5%88%A9%E7%82%B9-df68cd7cd991)

[**実際のところ「ブラウザを立ち上げてページが表示されるまで」には何が起きるのか**](https://qiita.com/tsin1rou/items/d4c781a2f25e2b92fa5e)ではブラウザがどのようにページを描画するのかなどについて書いたのですが、ローカルでjsを実行するのはかなり時間がかかる上、**jsの実行中はページの描画をストップ**させてしまいます。

![](https://cdn-images-1.medium.com/max/1600/1*AdJ3FwIW9psBE3hOFa2Y9g.jpeg)
例えば170KBほどのjsファイルはパース・コンパイル・実行で3.5秒程かかることもあります。そこでサーバーサイドでHTMLを生成しておくことでページの描画を圧倒的に早くするという技術がサーバーサイドレンダリングです。

Reactを使うなら一緒に押さえておくべきだと思います。

ちなみに[Googleの調査](https://www.thinkwithgoogle.com/marketing-resources/data-measurement/mobile-page-speed-new-industry-benchmarks/)ではページのロード時間が1秒から3秒に伸びると直帰率が32%増加するそうです。
![](https://www.thinkwithgoogle.com/_qs/images/3SvF7JqQKUu4KYHHV15fvEa6nKA=/189/width-2000/mobile-page-speed-new-industry-benchmarks-01-21.png)

#必要なソフトをインストールする
##エディタ
まずは[VSCode](https://code.visualstudio.com/)をインストールします。別に他のエディタでも良いのですが、特に信念がないのであればVSCodeを使いましょう。

- [Visual Studio Codeの使い方、基本の「キ」 (1/6)](http://www.atmarkit.co.jp/ait/articles/1507/10/news028.html)

VSCodeをインストールしたら起動して「ワークスペースフォルダー」を追加して開いておきます。ついでにターミナルで開いておくと後で便利です。

<img width="279" alt="スクリーンショット 2018-09-15 15.53.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/278434/567bef4d-07e6-0477-f48b-738d1863cbdd.png">

それと左の拡張機能タブから「Bracket Pair Colorizer」と「ESLint」をインストールしておきます。

**(重要)さらにメニューから設定を開きeditor.insertSpacesにチェックを入れ、editor.tabSizeを2にします。**2タブは正義です。
設定画面上の検索窓で検索すれば出てきます。

## Node.js
[Node.js](https://nodejs.org/ja/)はローカルでjavascriptを実行できるようにしてくれるすごいやつです。一緒について来るnpmというパッケージマネージャーが超有能で、今のフロントエンド開発はこのnpmが支えています。

## Git
まあ無くても良いっちゃ良いんですが。macなら最初から入っていると思います。

[公式サイト](https://git-scm.com/)

## yarn
さっきはnpmが凄いって書いたんですがyarnの方がパッケージのインストールが早いのでyarnも使います。さっき開いたターミナルでnpmを使ってインストールします。

```bash
npm i -g yarn
```
ただし[公式のドキュメント](https://yarnpkg.com/ja/docs/install#mac-stable)ではhomebrewを使うように指示されているそうです。silverskyvictoさん指摘ありがとうございます。

パッケージ管理ができるようにpackage.jsonを設定しておきます。

```bash
yarn init -y
```

## ESLint
それと構文解析ツールのESLintもインストールしておきます。サーバーにデプロイしてからエラーを発見するよりも、書いた瞬間エラーを表示してくれた方が何かと便利です。

```bash
yarn add eslint eslint-plugin-react -D
```

-DはdevDependenciesにインストールせよという命令で、開発中だけ使用し、本番稼働では使わないようなライブラリはこっちにインストールします。まあ間違えても大差ないですが。

`.eslintrc.json`を作成してeslintの設定をします。

```:フォルダ構造
qiita/
 ├─node_modules/ ←yarnでインストールしたライブラリが保存されています。
 │  └─...
 ├─.eslintrc.json ←new!
 ├─package.json
 └─yarn.lock ←パッケージのバージョン情報などが保存されています。
```

よく分からないと思うのでreact用の設定を用意しておきました。コピーして使いましょう。

```json:.eslintrc.json
{
    "env": {
        "browser": true,
        "es6": true,
        "node": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended"
    ],
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "plugins": [
        "react"
    ],
    "rules": {
        "no-console":"warn",
        "indent": [
            "error",
            2
        ],
        "linebreak-style": [
            "error",
            "unix"
        ],
        "quotes": [
            "error",
            "single"
        ],
        "semi": [
            "error",
            "always"
        ]
    }
}
```

#サーバーを書く
サーバーサイドはnode.jsで書いていきます。

##Expressの設定
続いてサーバーを超簡単にセットアップしてくれるライブラリ「express」をインストール。

```bash
yarn add express
```

serverフォルダを作りserver.jsを作成します。

```:フォルダ構造
qiita/
...
 ├─server/ ←new!
 │  └─server.js ←new!
...
```
```javascript:server.js
import express from 'express';

const app = express();

//GETリクエストでルートにアクセスが会った時の動作
app.get('/', (req, res)=>{
  res.send('Hello express');
});

//3000番ポートを使ってサーバーを立ち上げ
app.listen(3000, ()=>{
  console.log('app listening on 3000');
});
```

##babel-nodeの設定

nodeがes6を読めるようにbabelの設定をしてあげます。

```bash
yarn add babel-core babel-preset-env
```
```:フォルダ構造
qiita/
...
 ├─.babelrc ←new!
...
```
```json:.babelrc
{
  "presets": [
    "env"
  ]
}
```

nodeに渡す前にbabelがコンパイルしてくれる便利なツール`babel-node`をインストールします。

```bash
yarn add -D babel-cli
```
↑ `babel-cli` に `babel-node` が含まれています。

```js:package.json
...
   "main": "index.js",
+  "scripts": {
+    "dev": "babel-node server/server.js"
+  },
   "license": "MIT",
...
```
合わせてpackage.jsonを書き換えます。
+の行は追記した行で-は削除された行です。git diffで生成しています。

- [バージョン管理ツール「Git」は一体どういうものなのか？](https://gigazine.net/news/20180904-git-version-control-system/)
- [Gitの基礎](https://qiita.com/satoshi1335/items/ead109412430a078feaa)
- [忘れやすい人のための git diff チートシート](https://qiita.com/shibukk/items/8c9362a5bd399b9c56be)

```bash:サーバーを実行
yarn run dev

# ↓
# app listening on 3000
```

これで[http://localhost:3000/](http://localhost:3000/)にアクセスすればHello expressと表示されるはずです。

##index.html

publicフォルダを用意してindex.htmlを作成します。

```:フォルダ構造
qiita/
...
 ├─public/ ←new!
 │  └─index.html ←new!
...
```
```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
```
ちなみに今時のhtmlの書き方は[普通のHTMLの書き方](https://hail2u.net/documents/html-best-practices.html)が参考になります。

htmlを配信できるようにserver.jsを書き換えます。

```javascript:server.js
 import express from 'express';
+import fs from 'fs';

 const app = express();

 app.get('/', (req, res)=>{
-  res.send('Hello express');
+  const index = fs.readFileSync('./public/index.html', 'utf-8');
+  res.send(index);
 });
...
```

もう一回`yarn run dev`すればindex.htmlが配信されているとわかるかと思います。真っ白で分からないという場合は「⌘+⌥+i」で出現する検証モードを見ましょう。

# 初めてのReact

```bash
yarn add react react-dom
```
```:フォルダ構造
qiita/
...
 ├─src/ ←new!
 │  └─index.jsx ←new!
...
```
```jsx:index.jsx
import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
  <h1>Hello React</h1>,
  document.getElementById('root')
);
```

`ReactDOM.render()`は第一引数のjsxを第二引数のエレメントの子要素に加える関数です。

# Webpackの設定

ファイルをまとめたりminifyしてくれたりする便利なやつ。

- [webpack 4 入門](https://qiita.com/soarflat/items/28bf799f7e0335b68186)

```bash
yarn add webpack -D
```
```:フォルダ構造
qiita/
...
 ├─webpack.config.dev.babel.js ←new!
...
```
`webpack.config` webpackの設定ファイルである事を示しています。
`dev` 開発用（本番用では無い）という意味。
`babel` 設定ファイルを読む前にこの設定ファイル自体をbabelで変換しろという意味になります。
`js` jsファイルなので。

```js:webpack.config.dev.babel.js
import path from 'path';

export default {
  mode: 'development',
  entry:[
    path.resolve(__dirname, 'src/')
  ],
  output:{
    path: path.resolve(__dirname, 'public'),
    publicPath: '/',
    filename: 'bundle.js'
  },
  resolve:{
    extensions: ['.js','.json','.jsx']
  },
  module:{
    rules:[
      {
        test: /\.jsx?$/,
        use:{
          loader: 'babel-loader'
        },
        include: path.resolve(__dirname, 'src')
      }
    ]
  }
};
```

webpackでファイルをまとめて一つのbundle.jsにするんですが、ES6で書かれたファイルとかjsxとかを読めるようにするためにbabel-loaderというローダーを使用します。

```bash
yarn add babel-loader -D
```

## サーバーにWebpackを組み込む
サーバーでコンパイルして配信させるようにします。この設定は開発中のみ使用し、本番ではあらかじめビルドしたものを配信することになります。

```bash
yarn add webpack-dev-middleware -D
```

```javascript:server.js
 import express from 'express';
 import fs from 'fs';
+import webpack from 'webpack';
+import webpackConfig from '../webpack.config.dev.babel';
+import webpackMiddleware from 'webpack-dev-middleware';

 const app = express();

+const compiler = webpack(webpackConfig);
+app.use(webpackMiddleware(compiler));
+
 app.get('/', (req, res)=>{
   const index = fs.readFileSync('./public/index.html', 'utf-8');
...
```
```html:public/index.html
...
   <body>
     <div id="root"></div>
+    <script src="/bundle.js"></script>
   </body>
 </html>
```

いざ実行！

```bash
yarn run dev
```

```bash
ERROR in ./src/index.jsx
Module build failed (from ./node_modules/babel-loader/lib/index.js):
Error: Cannot find module '@babel/core'
 babel-loader@8 requires Babel 7.x (the package '@babel/core'). If you'd like to use Babel 6.x ('babel-core'), you should install 'babel-loader@7'.
```
🙃＜ふぁっきゅ
babelを7.x系にアップグレードするかbabel-loaderを7.x系にダウングレードするか選べば良いようです。パッチノートを読むとbabel7.0.0が出たのが19日前となっていてあまり気乗りはしなかったのですが、せっかくの機会なのでbabel7.x系を使ってみます。

```bash:以前のバージョンをアンインストール
yarn remove babel-core babel-preset-env babel-cli
```
```bash:新しいバージョンをインストール
yarn add @babel/core @babel/preset-env @babel/preset-react
```
ついでにbabelがjsxを理解できるようにpreset-reactをインストールしておきました。

```bash
yarn add -D @babel/node
```
`babel-node`を使用するために`@babel/node`をインストールしておきます。

```js:.babelrc
 {
   "presets": [
-    "env"
+    "@babel/env",
+    "@babel/preset-react"
   ]
 }
```

`yarn run dev`でサーバーを実行してページを開くと"Hello React"と表示されているはずです。

#HMR（Hot Module Replacement）

- [モジュールをHMRに対応するための実装について](https://qiita.com/haradakunihiko/items/9840f1ea2ffa0ee0874d)

> HMR（Hot Module Replacement）はWebpackが提供する、ブラウザのリロードをすること無くアプリケーションのJSを更新する開発ツールです。

この機能を使用するためにサーバーを書きました。まあサーバーを書いておくと後々Server Side Rendering（SSR）出来たりもしますし。

早速実装していきます。

```bash
yarn add webpack-hot-middleware -D
```

```javascript:webpack.config.dev.babel.js
+import webpack from 'webpack';
 import path from 'path';

 export default {
   mode: 'development',
   entry:[
+    'webpack-hot-middleware/client',
     path.resolve(__dirname, 'src/')
   ],
   output:{
...
     publicPath: '/',
     filename: 'bundle.js'
   },
+  plugins:[
+    new webpack.HotModuleReplacementPlugin()
+  ],
   resolve:{
     extensions: ['.js','.json','.jsx']
   },
...
```

```javascript:server/server.js
...
 import webpackConfig from '../webpack.config.dev.babel';
 import webpackMiddleware from 'webpack-dev-middleware';
+import HMR from 'webpack-hot-middleware';

 const app = express();

 const compiler = webpack(webpackConfig);
 app.use(webpackMiddleware(compiler));
+app.use(HMR(compiler));

 app.get('/', (req, res)=>{
...
```

```:フォルダ構造
qiita/
...
 ├─src/
 │  └─App.jsx ←new!
...
```
```jsx:src/App.jsx
import React from 'react';

const App = ()=>(
  <h1>Hello React</h1>
);

export default App;
```

```jsx:src/index.jsx
 import React from 'react';
 import ReactDOM from 'react-dom';

-ReactDOM.render(
-  <h1>Hello React</h1>,
-  document.getElementById('root')
-);
+import App from './App';
+
+const render = (_App) =>{
+  ReactDOM.render(
+    <_App />,
+    document.getElementById('root')
+  );
+};
+
+if(module.hot){
+  module.hot.accept('./App', ()=>{
+    const NextApp = require('./App').default;
+    render(NextApp);
+  });
+}
+
+render(App);

```

```bash
yarn run dev
```
![hmr.gif](https://qiita-image-store.s3.amazonaws.com/0/278434/69e3b550-8ad8-3953-2ae1-121efaf7812e.gif)

あとは気の赴くままにReactを書いていきましょう。

# サーバーサイドレンダリング（SSR）
Reactでサーバーサイドレンダリングを行うのは非常に簡単です。

```html:public/index.html
...
   </head>
   <body>
-    <div id="root"></div>
+    <div id="root"><%= preloadedApplication %></div>
     <script src="/bundle.js"></script>
   </body>
...
```
```jsx:server/server.js
...
 import fs from 'fs';
 import webpack from 'webpack';
+import React from 'react';
+import { renderToString } from 'react-dom/server';
 import webpackConfig from '../webpack.config.dev.babel';
 import webpackMiddleware from 'webpack-dev-middleware';
 import HMR from 'webpack-hot-middleware';

+import App from '../src/App';
+
 const app = express();
...
 app.use(HMR(compiler));

 app.get('/', (req, res)=>{
-  const index = fs.readFileSync('./public/index.html', 'utf-8');
+  let index = fs.readFileSync('./public/index.html', 'utf-8');
+
+  const appRendered = renderToString(
+    <App />
+  );
+  index = index.replace('<%= preloadedApplication %>', appRendered);
   res.send(index);
 });
...
```
```jsx:src/index.jsx
...
     const NextApp = require('./App').default;
     render(NextApp);
   });
 }

-render(App);
+ReactDOM.hydrate(
+  <App />,
+  document.getElementById('root')
+);

```

何か分かりにくいところやエラーなどあればコメントで教えてください。
次回があれば本番用のビルド＆デプロイの設定かReduxの実装とかですかね。