Typescript + Express + React + Webpack でSSR(サーバサイドレンダリング)を検証

# 以前書いた記事は間違いでした
[公式ドキュメントに沿ってTypescript + Express + React + Webpackを検証 - Qiita](https://qiita.com/adibozu/items/a01cc79efc7e8c543aa1)
公式ドキュメントはSSRしていませんでした…orz
嘘書いててすみませんでした、修正しました。ページのソースを表示でHTML吐き出されているかちゃんと確認しないといけませんでした。

# そもそもReactでSSRって？
サーバサイドレンダリングとは、JSによるHTMLレンダリング処理を、サーバサイドで行うことです。
テンプレートエンジン（JSではないもの）で、サーバサイドで動的にHTMLを生成することも、サーバサイドレンダリングと呼ぶようです。

react-domというライブラリを使うことで、ReactでレンダリングしたHTMLを文字列として出力することができるため、

サーバサイドでReactを使ってHTMLレンダリング　→　クライアントに返す

というReactのサーバサイドでの利用が可能になります。

# 今度こそSSRする
下記のドキュメントを参考にしました。
[Server-Side Rendering with React and TypeScript – Atticus Engineering – Medium](https://medium.com/atticus-engineering/server-side-rendering-with-react-and-typescript-8cebb4400b3c)

ちなみにローカルで作業を行いましたので、以前書いた内容を取り込みつつ、その前提で進めさせていただきます。

### はじめのディレクトリ構成
最初に作業ディレクトリとして`test-dir`を用意し、その下に下記のようにディレクトリを作成しました。

```
test-dir/
├── dist/
└── src/
    ├── controllers/
    └── views/
        ├── components/
        │   └── sample/
        └── pages/
            └── sample/
```

### イニシャライズ
`test-dir`ディレクトリ直下で、npmパッケージとするためのコマンドを実行します。

```
npm init
```
すると、いろいろ質問されますが、ひとまずエンターをただただ押していって、、

```
Is this OK? (yes)
```
と聞かれたら`y`と打ってエンターキーを押すと`package.json`が生成されます。

```json:package.json
{
  "name": "test-dir",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
```

### Node.jsの他に必要なパッケージのインストール
今回の検証に必要なパッケージをインストールします。
### Webpack

```
npm install --save-dev webpack webpack-cli
```

### TypeScript

```
npm install --save-dev typescript awesome-typescript-loader source-map-loader
```

### Express

```
npm install --save express @types/express
```

### React


```
npm install --save react react-dom @types/react @types/react-dom
```

### tsconfig.jsonを作成
TypeScriptの設定ファイルである`tsconfig.json`を`test-dir/`直下に作成します。

```json:tsconfig.json
{
    "compilerOptions": {
        "outDir": "./dist/",
        "sourceMap": true,
        "noImplicitAny": true,
        "module": "commonjs",
        "target": "es5",
        "jsx": "react"
    },
    "include": [
        "./src/**/*"
    ]
}
```

### Reactコンポーネントの作成
`Sample`コンポーネントを書いてみます。

```typescript:src/views/components/sample/Sample.tsx
import * as React from "react";

export interface SampleProps { compiler: string; framework: string; library: string; }

export class Sample extends React.Component<SampleProps, {}> {
    render() {
        return <h1>Hello from {this.props.compiler} and {this.props.framework} and {this.props.library}!</h1>;
    }
}

export default Sample;
```
`Sample`コンポーネントをインポートする`index.tsx`も作ります。

```typescript:src/views/components/sample/index.tsx
import * as React from "react";
import * as ReactDOM from "react-dom";

import { Sample } from "./Sample";

ReactDOM.hydrate(
    <Sample compiler="TypeScript" framework="Express" library="React" />,
    document.getElementById("sample")
);
```
また、参考にした資料にもある`Counter`コンポーネントも作成してみます。

```typescript:src/views/components/counter/Counter.tsx
import * as React from 'react';

export interface CounterProps {
}

export interface CounterState {
    counter: number;
  }
  
  class Counter extends React.Component<CounterProps, CounterState> {
  
    constructor(props: any) {
      super(props);
      this.state = { counter: 0 };
    }
  
    incrementCounter() {
      this.setState({ counter: this.state.counter + 1 });
    }
  
    render() {
      return (
        <div>
          <h1>counter at: {this.state.counter}</h1>
          <button
            onClick={() => this.incrementCounter()}
          />
        </div>
      );
    }
  }
  
  export default Counter;
```
これも同様に`index.tsx`を作成します。

```typescript:src/views/components/counter/index.tsx
import * as React from "react";
import * as ReactDOM from "react-dom";

import Counter from "./Counter";

ReactDOM.hydrate(
    <Counter />,
    document.getElementById("counter")
);
```


### レンダリングされたReactコンポーネントを流し込むHTMLを作成

```typescript:src/views/pages/sample/html.ts
const html = ({ counter, sample }: { counter: string, sample: string }) => `
  <!DOCTYPE html>
  <html>
    <head>
    </head>
    <body style="margin:0">
      <div id="counter">${counter}</div>
      <div id="sample">${sample}</div>
    </body>
    <script src="js/counter.js" defer></script>
  </html>
`;

export default html;
```

### サーバサイドの処理を追加
`react-dom`の`server.renderToString（）`を利用してReactファイルをレンダリングするサンプルです。
レンダリングしたReactファイルをHTMLファイルに渡し、expressがそれを返却します。

```typescript:src/controllers/sampleController.ts
import * as express from 'express';
import * as React from 'react';
import { renderToString } from 'react-dom/server';

import html from '../views/pages/sample/html';
import Counter from '../views/components/Counter/Counter';
import Sample from '../views/components/Sample/Sample';

export default (req: express.Request, res: express.Response)  => {
    const counter = renderToString(React.createElement(Counter));
    const sample = renderToString(React.createElement(Sample));

    res.send(
      html({
        counter, sample
      })
    );
}
```

`/`にリクエストが来たら`sampleController`の処理が走るように、Expressのルーティングファイルを作成します。

```typescript:src/server.ts
import * as Express from 'express';
import sampleController from './controllers/sampleController';

const app = Express();

app.get('/', sampleController);

app.listen(process.env.PORT || 3000, function () {
  console.log('express app is started.');
});
```

### Webpackの設定ファイルを作成
今回は
 - サーバサイドで読み込むJSファイル
 - クライアントサイドで読み込むJSファイル
をそれぞれ作りたいので、Webpackの設定ファイルをそれぞれ用意しました。
サーバサイドで読み込むファイルは、直上で作成した`server.ts`です。

```javascript:webpack.server.config.js
const path = require("path");

module.exports = {
  target: "node",
  entry: "./src/server.ts",
  output: {
    filename: "js/server.js",
    path: path.resolve(process.cwd(), "dist"),
    publicPath: "/"
  },
  devtool: "cheap-module-eval-source-map",
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"]
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [
          {
            loader: "ts-loader"
          }
        ]
      }
    ]
  }
};
```
クライアントサイドで読み込むJSファイルは、それぞれのコンポーネントをインポートしている`index.tsx`ファイルです。`html.ts`にある、

```html
    <script src="js/counter.js" defer></script>
```
がwebpackでビルドされたJSファイルを読み込む部分です。
（sampleコンポーネントのjsファイルはクライアントサイドで読み込んでいませんが、足並みを揃える目的でentryに追加しています。）

```javascript:webpack.client.config.js
const path = require("path");

module.exports = {
  entry: {
    counter: "./src/views/components/counter/index.tsx",
    sample: "./src/views/components/sample/index.tsx"
  },
  output: {
    filename: "js/[name].js",
    path: path.resolve(process.cwd(), "dist"),
    publicPath: "/"
  },
  devtool: "cheap-module-eval-source-map",
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"]
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [
          {
            loader: "ts-loader"
          }
        ]
      }
    ]
  }
};
```

### package.jsonにコマンドを追加
webpackのコマンドと、アプリケーションを起動するコマンドを`package.json`に追加しましょう。

```json:package.json
{
...
  "scripts": {
    "build": "webpack --config webpack.client.config.js && webpack --config webpack.server.config.js",
    "start": "node dist/js/server.js",
...
}
```

### 動作確認
`package.json`にコマンドを追加したので、下記コマンドでビルドと起動を行います。

```
$ npm run build
$ npm run start
```
`localhost:3000`にアクセスするとサーバサイドレンダリングされたReactコンポーネントを含むHTMLが表示されます。

### 最終的なディレクトリ構成
下記のようになりました。

```
test-dir/
├── dist
│   └── js
│       ├── counter.js
│       ├── sample.js
│       └── service.js
├── node_modules
│   ├── ...
├── package.json
├── src
│   ├── controllers
│   │   └── sampleController.ts
│   ├── server.ts
│   └── views
│       ├── components
│       │   ├── counter
│       │   │   ├── Counter.tsx
│       │   │   └── index.tsx
│       │   └── sample
│       │       ├── Sample.tsx
│       │       └── index.tsx
│       └── pages
│           └── sample
│               └── html.ts
├── tsconfig.json
├── webpack.client.config.js
└── webpack.server.config.js
```
`src/services/｀を作成して、そこにもっとビジネスロジックを寄せて、controllerはrouterからimportされてserviceを呼ぶだけにしたいなぁ

# 参考文献
 - [Server-Side Rendering with React and TypeScript – Atticus Engineering – Medium](https://medium.com/atticus-engineering/server-side-rendering-with-react-and-typescript-8cebb4400b3c)