LoginSignup
6
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-01-06

前提条件

  • 自分用メモ:zzz:
  • React v16
  • Node.js v8.2.1

本文

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

$ 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-registerbabel-preset-es2015を入れておきます。これでサーバーサイドでも心置きなくES2015で書けますね!

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

$ vi .babelrc
.babelrc
{
  "presets": ["es2015", "react"]
}

今回はシンプルにReactDOMServerを使います。
Reactのrender結果をサーバー上で作ってくれるやつですね。先ほどのプロジェクト作成でもう既にインストールされています。

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

$ touch server.js

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

$ vi package.json

サーバーのスタートコマンドのserverscriptsに足しておきます。

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を書いていきます。

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を追加します。

./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を修正します。

./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.csslogo.svgの読み込みは必須です!でも、サーバーで起動するときにこれがあるとエラーにしかならないので環境変数でサーバー起動時には読まないように小細工...なんか多分すごい悪手だ:innocent:

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

./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ファイルを生成します!

$ 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

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

スクリーンショット 2018-01-06 19.36.42.png

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

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

すごく参考になったサイト

6
2
0

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
6
2