段階的にフロントエンド開発 with Vue

この投稿は Shinjuku.rb の発表資料です

---------------------------------

@tomlla or @tomlla_92

![singlion.jpg](https://qiita-image-store.s3.amazonaws.com/0/59941/875616c1-b4e6-42cc-7181-e532b00dc7e2.jpeg)


-----------------------------

### これまでの @tomlla のあらすじ

2012〜 
ネットワーク機器開発 c, sh, php, python
target: Linux 2.6系 x86,arm

2014〜 
java8 + PlayFramework1 社内システムの開発
(GulpでビルドしてVue+coffeeをやってた)

2016〜 
rails(coffee+jquery)
不動産系ベンチャー

2017 〜
java + spring(vue, vue+typescript)
広告出稿系システム

2018.04〜
rails ← **いまのジョブはこれ♪**


----------------------------

ちなみに 今の案件こんな感じです

- k8s on GCP
- CloudSQL
- ruby 2.5.1, rails 5.2.1 (最近上げた)
- circleci
- vuejs(with vue-cli-service)
- 1万ユーザーくらいの国内向けSaaS
- IEは対応しない


----------------------------
個人的な課題感

サーバーサイドレンダリングだけだと...

1. form系
    - 入力フォームでLive Validationしたいよね？
    - ネストした配列を含むフォームのバリデーションつらくない？
    - ユーザーの入力値, バリデーションエラーをサーバー側で再レンダリングするのつらくない?
2. 大量/大容量のサブリソース
    - 「500ms以下で返せた！やったー?」
    - ↑TTFB後のサブリソース読み込みのこと考えてる?


---------------------------

# 1.form系問題への取り組み

--------------------------
## js object → DOMへの反映
バイディングまじでありがたい 😍

```html
<script>
new Vue({
  data: {
    host: "....",
    allowIPs: [],
  },
  computed: {
    validationErrors() { ... },
  }
});
</script>

<template>
  <input :value="host" />
  <div v-if="hasValidationError" class="error" >
    {{ validationErrors["host"] }}  
  </div>
  <ul>
    <li v-for="(ip, i) in allowIPs" :key="i" >
      <input :value="ip" />
      <div v-if="hasValidationError" class="errors">
        {{ validationErrors["allowIPs"][i] }}
      </div>
    </li>
  </ul>
</template>
```

----

## サーバーへのpush

- [axios](https://github.com/axios/axios)をつかっています
- CSRFトークンはrailsのviewsでhtmlに埋め込んでおいたのを使う

```js
axios.defaults.headers.common["X-CSRF-Token"] = document.querySelector(...);
axios.post('/foo/bar', {
  host: this.host,
  allow_ips: this.allowIPs
}
.then(response => {...})
.catch(err=> {...});
```
- ActiveRecordやActiveModelのValidationErrorも整形してレスポンスとして返す

-----

**メリット:**
ユーザーの入力はブラウザに残るので
**バリデーションエラーの際、サーバー側で画面再構築しなくてよい😉**

-----

# まずここまでが第一段階でした


------
# この時点での状況

- npmやwebpack使わない
- vueを使いたいページで CDNからvueを読み込み(scriptタグ)
- 適当なところにidをつけてvue componentをマウント

-----

# お手軽!

--------
# そしてbinding最高!
---------------

- 自分でDOMごりごり構築しなくてよくなった
- api で投げるときもDOMではなくjs objectの値をつかえばok!

-----
# この段階のDXへの不満

- unit testできない
- VueのSFCが使えない
    - scoped cssがつかえない
- ページごとに使うVue/Axiosのバージョンがばらばらになりやすい..
- pug/stylus使いたい

--------------

# 加えて...

-------------

# サブリソース読み込み開始時間をもっとはやくしたい

----

# 2. 大量/大容量サブリソース読み込みへの対処

-----

## 課題をもういちど

あるページのサーバーサイドのレスポンスが500ms以内だとして...

- そのあとに, CSS, js, img, web-font,などの読み込みが始まる
- FontAwesomeとか普通に読み込むとかなり大きい

-----

そもそも実際には...

- 雑につくって放置してデータが増えるとサーバーサイドレスポンスが500msを超える.
(雑に機能追加して気づかぬうちにN+1など)

- そのあとにサブリソース読み込みが行われる

↓ということで

**一旦htmlを返したあとに、apiから必要コンテンツを取得する方式へ**

----

## 具体的にどういう構成にしたか

------------------

## この段階で使いはじめたフロントエンドエコシステム
- vue cli v3 および vue-cli-service
- vue-router
- webpack
- webpack-dev-server
- webpack-manifest-plugin
- html-webpack-plugin

※ vue cli v3のvue-cli-serviceを利用したいためwebpackerは利用せず

-----
## サーバーサイド(rails)

- 既存のページはそのまま
- 新規 layoutを作成して 改善対象ページだけ新規layoutを使う
  - **外枠のみrailsで返す**
  - メインコンテンツはapiで渡す
  - または[gon](https://github.com/gazay/gon)で渡す

※認証動作はrailsで行い、認可された後のページを今回の改善対象としています

-----
## サーバーサイド(rails)

**重要だったポイント:**

- webpackでビルドすると複数のファイルが出来上がる
    - ※設定によっていろいろ
- 今回の設定でビルドされたファイル名にはhashがついている
- rails側のlayout内で必要なファイルのビルド後のURLを出力する必要がある。

----------------------------
### どうするか

**案1: webpack-manifest-plugin**
もとのファイル名 -> ビルド後のファイル名(フルパス)のmappingをjson出力してくれる

**案2: html-webpack-plugn**
必要なjs,cssなどを読み込むlinkタグ,scriptタグが含まれたhtmlを生成してくれる

--------------------------------
# 選ばれたのは

----------

![petbottle_tea.png](https://qiita-image-store.s3.amazonaws.com/0/59941/bd2c6ce2-78eb-293c-e6d0-72642d22b1ba.png)


-------------------------

# html-webpack-plugn

---------------------------------

- rails起動時にhtml-webpack-pluginの出力するhtmlを読み込む。
    - `<head>` に配置するlink/scriptタグと、
    - `<body>` の最下部に配置するlink/scriptタグがある。
- webpack.rbというクラスを用意しクラスインスタンス変数内に保存
- `script`タグ`link`タグなどをlayoutファイル内の適切な位置に出力

---

ただし, やっていることがかなりざつなので

**できればwebpack manifest pluginを使う方式にしたい**

😅😅😅

-----

### ブラウザ側の動き 

- vue-routerでパスに対して特定のVue Componentをマウント
    - [dynamic import](https://vuejs.org/v2/guide/components-dynamic-async.html) をつかって必要なjsのみ読み込む
- componentマウント時にメインコンテンツをrailsサーバーから取得
- `旧layout <=> 新layout` のページ遷移では通常のサーバーサイドレンダリング
- `新layout <=> 新layout` のページ遷移ではcompnentの差し替えだけ行う(SPA的動き)

--------
デプロイ時

```bash
$ npm run build (or yarn build)
$ RAILS_ENV=production rails assets:precompile
$ RAILS_ENV=production rails s
```

-----

# ずるいけどBefore/After

-----

# Before

![before.png](https://qiita-image-store.s3.amazonaws.com/0/59941/f1531adb-75f5-0e9a-b523-4813eb85a368.png)

----
# After
![after.png](https://qiita-image-store.s3.amazonaws.com/0/59941/8ef198d1-ba25-2a7e-f959-674a766ff118.png)


-----------------

まとめ

★ 最近もとめられるUXを満たすためには、
サーバーサイドエコシステムだけだとかなり労力がいる。

★ 先に画面返して、重い処理(DBアクセス)は必要なタイミングで呼び出す方が良いっすよね



結果、webpackはじめフロントエンドエコシステムにどっぷり使うことになるかとは思うんですが


------------------

# 補足: 開発時どうやっているか

---------
## 起動するプロセスとアクセス先

- rails + webpack-dev-serverの2つを起動
  - 全部入りdocker-compose.yamlも用意している
- 画面動作確認は webpack-dev-server側へアクセス
- webpack-dev-serverの設定でrails側APIに対するリクエストはrailsへプロキシ