AngularでSSRをしてみる

# AngularでSSRをしてみる
SPAの敵はファイルサイズの大きいJSや初期化にかかる時間です。

そのなかでファイルサイズはAoTやuglifyで小さくしていくようにしています。
初期化にかかる時間を抑えるには、サーバーサイドレンダリングが1つの解になります。

Angularでは現在@angular/universalというSSRを実現するプロジェクトがあります。
今回はこれを使ってみます。

## 利用するには
angular-universalを利用するのもいいですが、ここは素早く利用できるuniversal-starterを使ってみましょう

まずはuniversal-starterをcloneしてきます。

```zsh
% git clone git@github.com:angular/universal-starter
Cloning into 'universal-starter'...
remote: Counting objects: 2070, done.
remote: Compressing objects: 100% (23/23), done.
remote: Total 2070 (delta 10), reused 0 (delta 0), pack-reused 2047
Receiving objects: 100% (2070/2070), 370.34 KiB | 437.00 KiB/s, done.
Resolving deltas: 100% (1272/1272), done.
Checking connectivity... done.
```

レポジトリのディレクトリに移動し、npm installを実行します

```zsh
cd universal-starter && npm install
```

起動してみる

```zsh
% npm start

> universal-starter@2.0.0 prestart /Users/teyosh/Test/advent/universal-starter
> npm run build


> universal-starter@2.0.0 prebuild /Users/teyosh/Test/advent/universal-starter
> npm run clean:dist


> universal-starter@2.0.0 clean:dist /Users/teyosh/Test/advent/universal-starter
> rimraf dist


> universal-starter@2.0.0 build /Users/teyosh/Test/advent/universal-starter
> webpack  --progress

[0] Hash: f3ecdceb6ff8b863cd64d0ac0448d61acda74259                            r Version: webpack 2.1.0-beta.27
Child
    Hash: f3ecdceb6ff8b863cd64
    Version: webpack 2.1.0-beta.27
    Time: 12533ms
                 Asset     Size  Chunks             Chunk Names
           0.bundle.js  2.23 kB       0  [emitted]
        main.bundle.js  2.99 MB       1  [emitted]  main
       0.bundle.js.map  2.35 kB       0  [emitted]
    main.bundle.js.map  2.95 MB       1  [emitted]  main
     [279] ./src async 160 bytes {1} [built]
     [446] ./src/+app/+lazy async ^\.\/lazy\.module.*$ 160 bytes {1} [built]
        + 449 hidden modules
Child
    Hash: d0ac0448d61acda74259
    Version: webpack 2.1.0-beta.27
    Time: 12512ms
             Asset     Size  Chunks             Chunk Names
        0.index.js  2.24 kB       0  [emitted]
          index.js  2.66 MB       1  [emitted]  main
    0.index.js.map  2.35 kB       0  [emitted]
      index.js.map  2.56 MB       1  [emitted]  main
     [260] ./src async 160 bytes {1} [built]
     [406] ./src/+app/+lazy async ^\.\/lazy\.module.*$ 160 bytes {1} [built]
        + 429 hidden modules

> universal-starter@2.0.0 start /Users/teyosh/Test/advent/universal-starter
> npm run server


> universal-starter@2.0.0 server /Users/teyosh/Test/advent/universal-starter
> nodemon dist/server/index.js

[nodemon] 1.11.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: /Users/teyosh/Test/advent/universal-starter/dist/**/* src/index.html
[nodemon] starting `node dist/server/index.js`
Listening on: http://localhost:3000
```
ブラウザで確認

```zsh
/data.json Cache Miss
GET /data.json 200 3.359 ms - 62
GET /home 200 111.378 ms - -
```

<img width="996" alt="スクリーンショット 2016-12-19 14.19.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/5303/2b87c6d8-5ad2-c93a-b242-3fd38504705c.png">

こうなります。
これだけではSSRかどうかはわからないですね

## index.html
こちらがソースのindexになります

```html
<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Angular 2 Universal Starter</title>
  <meta name="description" content="Angular 2 Universal">
  <meta name="keywords" content="Angular 2,Universal">
  <meta name="author" content="PatrickJS">
  <meta name="viewport" content="width=device-width,minimum-scale=1">

  <link rel="icon" href="data:;base64,iVBORw0KGgo=">

  <link rel="prerender" href="http://localhost:3000/lazy">
  <link rel="preload" href="/assets/logo.png">
  <base href="/">

</head>
<body>

  <app>
    Loading Universal ...
  </app>

  <script async src="/main.bundle.js"></script>
</body>
</html>
```

で、こっちがブラウザが取得したindexになります

```html

<!DOCTYPE html><html><head>
  <meta charset="UTF-8">
  <title>Angular 2 Universal Starter</title>
  <meta name="description" content="Angular 2 Universal">
  <meta name="keywords" content="Angular 2,Universal">
  <meta name="author" content="PatrickJS">
  <meta name="viewport" content="width=device-width,minimum-scale=1">

  <link rel="icon" href="data:;base64,iVBORw0KGgo=">

  <link rel="prerender" href="http://localhost:3000/lazy">
  <link rel="preload" href="/assets/logo.png">
  <base href="/">

<style>*[_ngcontent-a72c-1] { padding:0; margin:0; font-family: 'Droid Sans', sans-serif; }
    #universal[_ngcontent-a72c-1] { text-align:center; font-weight:bold; padding:15px 0; }
    nav[_ngcontent-a72c-1] { background:#158126; min-height:40px; border-bottom:5px #046923 solid; }
    nav[_ngcontent-a72c-1]   a[_ngcontent-a72c-1] { font-weight:bold; text-decoration:none; color:#fff; padding:20px; display:inline-block; }
    nav[_ngcontent-a72c-1]   a[_ngcontent-a72c-1]:hover { background:#00AF36; }
    .hero-universal[_ngcontent-a72c-1] { min-height:500px; display:block; padding:20px; background: url('/assets/logo.png') no-repeat center center; }
    .inner-hero[_ngcontent-a72c-1] { background: rgba(255, 255, 255, 0.75); border:5px #ccc solid; padding:25px; }
    .router-link-active[_ngcontent-a72c-1] { background-color: #00AF36; }
    main[_ngcontent-a72c-1] { padding:20px 0; }
    pre[_ngcontent-a72c-1] { font-size:12px; }</style><style>blockquote[_ngcontent-a72c-3] {
  border-left:5px #158126 solid;
  background:#fff;
  padding:20px 20px 20px 40px;
}
blockquote[_ngcontent-a72c-3]::before {
  left: 1em;
}</style></head>
<body>

  <app _nghost-a72c-1="">
  <h3 _ngcontent-a72c-1="" id="universal">Angular2 Universal</h3>
  <nav _ngcontent-a72c-1="">
    <a _ngcontent-a72c-1="" routerLink="home" routerLinkActive="router-link-active" href="/home" class="router-link-active">Home</a>
    <a _ngcontent-a72c-1="" routerLink="about" routerLinkActive="router-link-active" href="/about">About</a>
    <a _ngcontent-a72c-1="" routerLink="todo" routerLinkActive="router-link-active" href="/todo">Todo</a>
    <a _ngcontent-a72c-1="" routerLink="lazy" routerLinkActive="router-link-active" href="/lazy">Lazy</a>
  </nav>
  <div _ngcontent-a72c-1="" class="hero-universal">
    <div _ngcontent-a72c-1="" class="inner-hero">
      <div _ngcontent-a72c-1="">
        <span _ngcontent-a72c-1="" xLarge="" style="font-size:x-large;">Universal JavaScript ftw!</span>
      </div>

      Two-way binding: <input _ngcontent-a72c-1="" type="text" value="ftw">

      <br _ngcontent-a72c-1="">
      <br _ngcontent-a72c-1="">

      <strong _ngcontent-a72c-1="">Router-outlet:</strong>
      <main _ngcontent-a72c-1="">
        <router-outlet _ngcontent-a72c-1=""></router-outlet><home _nghost-a72c-3=""><div _ngcontent-a72c-3="" class="home">
  Home component
  <strong _ngcontent-a72c-3="">Async data call return value:</strong>
  <pre _ngcontent-a72c-3="">{
  "data": "This fake data came from the db on the server."
}</pre>
  <blockquote _ngcontent-a72c-3="">This fake data came from the db on the server.</blockquote>
</div>
</home>
      </main>
    </div>
  </div>
  </app>

  <script async="" src="/main.bundle.js"></script>


<universal-script><script>
 try {window.UNIVERSAL_CACHE = ({"APP_ID":"a72c","CacheService":"{\"/data.json\":{\"data\":\"This fake data came from the db on the server.\"}}"}) || {};} catch(e) {  console.warn("Angular Universal: There was a problem parsing data from the server")}
</script></universal-script></body></html>
```

`<app></app>`の中身が生成されてブラウザに送られているのが分かるかと思います。

初期化の処理が省かれるのはもちろん、SEO対策もできる一石二鳥です。

# AoT
もちろんAoTにも対応しています。

```zsh
% npm run build:prod:ngc
> universal-starter@2.0.0 build:prod:ngc /Users/teyosh/Test/advent/universal-starter
> npm run clean:ngc && npm run ngc && npm run clean:dist && npm run build:prod


> universal-starter@2.0.0 clean:ngc /Users/teyosh/Test/advent/universal-starter
> rimraf **/*.ngfactory.ts **/*.css.shim.ts


> universal-starter@2.0.0 ngc /Users/teyosh/Test/advent/universal-starter
> ngc -p tsconfig.aot.json


> universal-starter@2.0.0 clean:dist /Users/teyosh/Test/advent/universal-starter
> rimraf dist


> universal-starter@2.0.0 build:prod /Users/teyosh/Test/advent/universal-starter
> webpack --config webpack.prod.config.ts

Hash: b1b2d9e2afd25d442e1f48118b7ce828e0579d81
Version: webpack 2.1.0-beta.27
Child
    Hash: b1b2d9e2afd25d442e1f
    Version: webpack 2.1.0-beta.27
    Time: 27017ms
                          Asset     Size  Chunks             Chunk Names
        72fdbbaf74c66f7684c5.js  5.62 kB       0  [emitted]
                 main.bundle.js   616 kB       1  [emitted]  main
    72fdbbaf74c66f7684c5.js.map  32.3 kB       0  [emitted]
             main.bundle.js.map  4.53 MB       1  [emitted]  main
                     stats.json  2.61 MB          [emitted]
      [26] ./empty.js 191 bytes {1} [built]
     [216] ./src async 160 bytes {1} [built]
        + 351 hidden modules
Child
    Hash: 48118b7ce828e0579d81
    Version: webpack 2.1.0-beta.27
    Time: 27005ms
              Asset     Size  Chunks             Chunk Names
        0.bundle.js  8.94 kB       0  [emitted]
           index.js  1.66 MB       1  [emitted]  main
    0.bundle.js.map  32.4 kB       0  [emitted]
       index.js.map  6.26 MB       1  [emitted]  main
     [273] ./src async 160 bytes {1} [built]
        + 435 hidden modules
```

ファイルサイズは結構変わります。
AoTしていない場合

```zsh
% ls -lh client
total 11704
-rw-r--r--  1 teyosh  staff    12K 12 19 14:50 0.bundle.js
-rw-r--r--  1 teyosh  staff    20K 12 19 14:50 0.bundle.js.map
-rw-r--r--  1 teyosh  staff   2.2K 12 19 14:50 1.bundle.js
-rw-r--r--  1 teyosh  staff   2.3K 12 19 14:50 1.bundle.js.map
-rw-r--r--  1 teyosh  staff   2.9M 12 19 14:50 main.bundle.js
-rw-r--r--  1 teyosh  staff   2.8M 12 19 14:50 main.bundle.js.map

% ls -lh server
total 10296
-rw-r--r--  1 teyosh  staff    12K 12 19 14:49 0.index.js
-rw-r--r--  1 teyosh  staff    20K 12 19 14:49 0.index.js.map
-rw-r--r--  1 teyosh  staff   2.2K 12 19 14:49 1.index.js
-rw-r--r--  1 teyosh  staff   2.3K 12 19 14:49 1.index.js.map
-rw-r--r--  1 teyosh  staff   2.5M 12 19 14:49 index.js
-rw-r--r--  1 teyosh  staff   2.4M 12 19 14:50 index.js.map
```

AoTした場合


```zsh
% ls -lh client
total 15240
-rw-r--r--  1 teyosh  staff   5.5K 12 19 14:58 72fdbbaf74c66f7684c5.js
-rw-r--r--  1 teyosh  staff    31K 12 19 14:58 72fdbbaf74c66f7684c5.js.map
-rw-r--r--  1 teyosh  staff   602K 12 19 14:58 main.bundle.js
-rw-r--r--  1 teyosh  staff   4.3M 12 19 14:58 main.bundle.js.map
-rw-r--r--  1 teyosh  staff   2.5M 12 19 14:58 stats.json

% ls -lh server
total 15560
-rw-r--r--  1 teyosh  staff   8.7K 12 19 14:58 0.bundle.js
-rw-r--r--  1 teyosh  staff    32K 12 19 14:58 0.bundle.js.map
-rw-r--r--  1 teyosh  staff   1.6M 12 19 14:58 index.js
-rw-r--r--  1 teyosh  staff   6.0M 12 19 14:58 index.js.map
```

まだまだ、開発中で不安定ですが、SEO対策や初期化で悩んでいる場合は使ってみても良いかもしれないですね！！