Reactのcreate-react-appで作られたものを無理やりSSRしてみるテスト

### 前提条件
- 自分用メモ:zzz:
- React v16
- Node.js　v8.2.1

### 本文

まずは公式通りにプロジェクト作成。そして必要なものをインストール。

```shell
$ npm install -g create-react-app # 既にある人は無視して
$ create-react-app easy-ssr
$ cd easy-ssr
$ npm install
$ npm install -S express react-dom node-jsx babel-register babel-preset-es2015
```

さて、今回はただSSRを試すだけなので`src`の中はそのままに、`create-react-app`で生成されたものをそのまま利用します！

ちなみに僕は同時進行でやりながらクライアントとサーバーのソースの書式が違うのが気持ち悪いので[babel-register](https://babeljs.io/docs/usage/babel-register/)と[babel-preset-es2015](https://github.com/babel/babel/tree/master/packages/babel-preset-es2015)を入れておきます。これでサーバーサイドでも心置きなくES2015で書けますね！

そして`.babelrc`を作っておきます。

```shell
$ vi .babelrc
```

```json:.babelrc
{
  "presets": ["es2015", "react"]
}
```

今回はシンプルに[ReactDOMServer](https://reactjs.org/docs/react-dom-server.html#rendertostring)を使います。
Reactのrender結果をサーバー上で作ってくれるやつですね。先ほどのプロジェクト作成でもう既にインストールされています。

続いてサーバー用のファイルを作ります。

```shell
$ touch server.js
```

そいで`package.json`を弄ります。

```shell
$ vi package.json
```
サーバーのスタートコマンドの`server`を`scripts`に足しておきます。

```json:package.json
{
  "name": "easy-ssr",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "babel-preset-es2015": "^6.24.1",
    "babel-register": "^6.26.0",
    "express": "^4.16.2",
    "node-jsx": "^0.13.3",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-scripts": "1.0.17"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject",
    "server": "IS_SERVER=true node -r babel-register ./server.js" #これを追加
  }
}
```
これで`npm run server`でES2015のNode.jsが動きますね！（なにやら環境変数ありますがそれは後ほど登場します）

さて、早速`server.js`を書いていきます。

```javascript:server.js
import fs from 'fs';
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import NodeJsx from 'node-jsx';
NodeJsx.install({harmony: true});

import Html from './src/Html';
import App from './src/App';

import asset from './build/asset-manifest.json';

const initialData = {
    logo: `/build/${asset['static/media/logo.svg']}`
};

const app = express();
const PORT = process.env.PORT || 3000;

app.use('/build', express.static('build'));
app.get('/service-worker.js', (req, res) => {
    res.end(fs.readFileSync('./build/service-worker.js'));
});

app.get('/', (req, res) => {
    res.status(200);
    res.setHeader('Content-Type', 'text/html');
    res.end(
        ReactDOMServer.renderToStaticMarkup(
            <Html asset={asset} initialData={JSON.stringify(initialData)}>
                <App {...initialData} />
            </Html>
        )
    );
});

app.listen(PORT, () => {
    console.log(`Server running at port: ${PORT}`);
});
```

ほんと無理やりですが`./build/asset-manifest.json`からビルド後のファイル名を取得してしまいます。さらには`/service-worker.js`はただプロキシしているだけです。我ながらすごいやり方:innocent:

次に`./src`の中に`Html.js`を追加します。

```javascript:./src/Html.js
import React, { Component } from 'react';

class Html extends Component {
    constructor(props) {
      super(props);
    }
    render() {
        return (
            <html>
                <head>
                    <title>App</title>
                    <link rel="stylesheet" href={`/build/${this.props.asset['main.css']}`} />
                </head>
                <body>
                    <div id="root">{this.props.children}</div>
                    <script id="initial-data" type="text/plain" data-json={this.props.initialData}></script>
                    <script src={`/build/${this.props.asset['main.js']}`}></script>
                </body>
            </html>
        );
    }
};

export default Html;
```

さらに`./src/App.js`を修正します。

```javascript:./src/App.js
import React, { Component } from 'react';
if (process.env.IS_SERVER !== 'true') {
  require('./App.css');
  require('./logo.svg');
}

class App extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={this.props.logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;
```
読み込んでないとbuildディレクトリにビルドしてくれないので`App.css`と`logo.svg`の読み込みは必須です！でも、サーバーで起動するときにこれがあるとエラーにしかならないので環境変数でサーバー起動時には読まないように小細工...なんか多分すごい悪手だ:innocent:

最後に`./src/index.js`も弄ります。

```javascript:./src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

const initialData = JSON.parse(document.getElementById('initial-data').getAttribute('data-json'));

ReactDOM.hydrate(<App {...initialData} />, document.getElementById('root'));

registerServiceWorker();
```

...さて準備が終わったところで、buildファイルを生成します！

```shell
$ npm run build
```

`./build`ディレクトリができましたね！ ではサーバーを起動してみます。

```
$ npm run server

> easy-ssr@0.1.0 server /Users/user/git/study/easy-ssr
> IS_SERVER=true node -r babel-register ./server.js

Server running at port: 3000
```
早速ブラウザでアクセス！

![スクリーンショット 2018-01-06 19.22.50.png](https://qiita-image-store.s3.amazonaws.com/0/45860/9ce9ad14-39d2-a2af-c1cb-0b4b89848a59.png)

しっかり表示されていますね！ソースも確認してみましょう！

<img width="200" src="https://qiita-image-store.s3.amazonaws.com/0/45860/960e166e-0c26-d682-db1d-204f6a7ccba9.png">

![スクリーンショット 2018-01-06 19.36.42.png](https://qiita-image-store.s3.amazonaws.com/0/45860/2203681b-7bc8-5196-7fad-edd9ecc85008.png)


わーいやったぞー！！！SSRされた生なソースが来てますね！生っ！！:beers::blush:

...今回できたゴミは一応下に貼って起きますね。。
https://github.com/kokoyoshi/easy-ssr

### すごく参考になったサイト
- [React v16でのサーバーサイドレンダリング](http://blog.koba04.com/post/2017/10/01/serverside-rendering-in-react-v16/)