本田にGitHubのContribution数で勝てるサービス、「GitHubじゃんけん」を作った

# この記事について
Vue.jsとNode.jsを使って簡単なwebサービスを半日ほどで作ったので環境構築からデプロイまでざっと書きます。

# 作ったやつ
https://github-janken.herokuapp.com

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176284/acd57c70-12c2-72ee-3d9b-252ad6bd0def.png)

![スクリーンショット 2019-04-21 21.16.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176284/e51705e8-846f-35b8-c03e-3894a048d98a.png)


![スクリーンショット 2019-04-21 21.11.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176284/c3a13b52-9469-047f-2617-dda58487a140.png)


GitHub Usernameを入力してContribution数が多い人が勝ち。ただそれだけです。本田にも（多分）勝てます。リーナスは強すぎます・・・。

元ネタは[B: AtCoderでじゃんけんを - AtCoder Regular Contest 048 | AtCoder](https://arc048.contest.atcoder.jp/tasks/arc048_b)とそれから生まれたwebサービスである[AtCoderじゃんけん](https://atcoder-janken.appspot.com)です。

ツイートにもあるように、[ケイスケホンダがあまりにもじゃんけん強者らしい](https://nlab.itmedia.co.jp/nl/articles/1904/19/news144.html)ので、じゃあ彼にGitHubのContribution数で勝ってやろうという意図から生まれました。

【4/23 追記】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176284/7d2af9df-794e-17df-3372-36c930512582.png)

相手に勝負を挑めるようになりました。自分のGitHub Usernameを入力して「勝負を申し込む」を押すとリンクが発行されます。相手にそのURLを送りつけると、受け取った側は自分の名前を埋めるだけでその人と勝負できます。これにより、エンジニア同士のいさかいや宗教戦争はじゃんけんで公平かつ公正に勝敗を決めることができます。

# 技術スタック
* Vue.js(フロントエンド)
* Node.js(バックエンド)
* Heroku(インフラ)

# 環境構築からデプロイまで
## 環境構築
最初に`vue-cli`や`npm`や`heroku`などは使えるようにしておきます。その後、フロントエンドとバックエンドそれぞれにプロジェクトを作成します。`vue create`や`npm init`を実行した際に色々聞かれるのはデフォルトでOKです。

```
$ mkdir github_janken
$ cd github_janken
$ vue create frontend
$ mkdir backend
$ cd backend
$ npm init
```

ここまで行くと以下のようなディレクトリ構造になってます。

```
.
├── backend
│   └── package.json
└── frontend
    ├── README.md
    ├── babel.config.js
    ├── node_modules
    ├── package-lock.json
    ├── package.json
    ├── public
    └── src
```

## フロントエンド
もくもく開発します。

### APIのベースURL
`axios`でAPIを叩くようにしましたが、ローカルの開発環境と本番の環境でエンドポイントのベースとなるURLが異なるので、その辺りを考慮する必要があります。`vue-cli-service`の引数によって読み込まれる環境ファイルが変わるので、それによって環境変数を切り替えます。

`Axios`の`baseURL`はこのように環境変数を用いて指定します。

```javascript
axios.create({
    baseURL: process.env.VUE_APP_API_URL
})
```

開発用の環境変数は.envや.env.developmentに記述します。

```text:.env
VUE_APP_API_URL=http://localhost:3000/
```

本番用の環境変数は.env.productionに記述します。こちらはバックエンドをデプロイするURLになります。

```text:.env.production
VUE_APP_API_URL=https://github-janken-backend.herokuapp.com/
```

重要なポイントは、`VUE_APP_`から始まる変数だけがVueのコンパイル時にセットされるという点です。https://cli.vuejs.org/guide/mode-and-env.html#using-env-variables-in-client-side-code に書いていますが、知らないとハマりそうになります。（僕です。）


### Twitterのシェアボタン
シェアボタン自体は、https://publish.twitter.com/?buttonType=TweetButton&widget=Button から取得して貼り付けるだけなのですが、今回は動的に生成されたじゃんけん結果をツイートする必要があるため、`:data-text`の内容を`computed property`などで指定します。

```html
<a
    href="https://twitter.com/share?ref_src=twsrc%5Etfw"
    class="twitter-share-button"
    :data-text="tweet_msg"
    data-url="https://github-janken.herokuapp.com/"
    data-show-count="false"
>Tweet</a>
```

そしてhtmlのどこかに以下の`<scirpt>`を設置します。

```html
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
```

しかしこれではうまく動作しません。`<scirpt>`がロードされるタイミングでツイートする文章を確定してしまうので、後から`tweet_msg`を変更しても反映されないようです。

そこでじゃんけん結果が出るたびにこの`<script>` タグを動的に生成します。

```javascript
// Twitterボタンの動的生成
const script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://platform.twitter.com/widgets.js";
script.charset = "utf-8";

const first_script = document.getElementsByTagName("script")[0];
first_script.parentNode.insertBefore(script, first_script);
```

Vue.js使っているのに`insertBefore`とかは流石にダメが気がしますね（いい方法あればコメントお待ちしています。）

## バックエンド
バックエンドはスクレイピングのために利用しています。GitHub APIにはContribution数を簡単に取得できるAPIが無いらしいので、仕方なくhttps://github.com/users/teru01/contributions などのページから数字の部分を取り出しています。フロントエンドから直接スクレイピングしようかとも思ったのですが、[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)の制約のため不可能だったので、バックエンドのNode.jsでスクレイピングしています。

## デプロイ
フロントとバックエンドでそれぞれ別のherokuアプリとしてデプロイします。`package.json`の`scripts.start`に書いてあるコマンドを実行してくれるので、そこに`node`コマンドを記述します。


### バックエンド

```jsonld:package.json(抜粋)
"scripts": {
    "start": "node index.js"
},
```

### フロントエンド 

フロントエンドのプロジェクトにもそれを配信するサーバが必要なので`express`を用意します。

```
$ npm install express serve-static
```

プロジェクトルートに`server.js` などの名前でサーバープログラムを作成し、`package.json`を変更します。

```javascript:server.js
const express = require('express');
const serve_static = require("serve-static")
const path = require('path');
const app = express();
app.use(serve_static(path.join(__dirname, 'dist')));
const port = process.env.PORT || 80;
app.listen(port);
```


```json:package.json
"scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "postinstall": "npm run build",
    "start": "node server.js"
},
```

`postinstall`はherokuがdependenciesを解決した後で実行するコマンドです。`npm run build`が指定されているため、Vueがプロダクションモードでコンパイルされて、そのあとに`start`に指定したコマンドが実行されます。

# 参考
https://medium.com/binarcode/deploying-vue-apps-to-heroku-the-right-way-26b11c1ae5cd
https://atcoder-janken.appspot.com
https://qiita.com/y4u0t2a1r0/items/a6aea444efc8e8e65293
https://qiita.com/pure-adachi/items/c2c5730560650c80a5e0

# GitHub
https://github.com/teru01/github_janken_backend
https://github.com/teru01/github_janken_frontend