Edited at

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


この記事について

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


作ったやつ

https://github-janken.herokuapp.com

image.png

スクリーンショット 2019-04-21 21.16.19.png

スクリーンショット 2019-04-21 21.11.12.png

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

元ネタはB: AtCoderでじゃんけんを - AtCoder Regular Contest 048 | AtCoderとそれから生まれたwebサービスであるAtCoderじゃんけんです。

ツイートにもあるように、ケイスケホンダがあまりにもじゃんけん強者らしいので、じゃあ彼にGitHubのContribution数で勝ってやろうという意図から生まれました。

【4/23 追記】

image.png

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


技術スタック


  • Vue.js(フロントエンド)

  • Node.js(バックエンド)

  • Heroku(インフラ)


環境構築からデプロイまで


環境構築

最初にvue-clinpmherokuなどは使えるようにしておきます。その後、フロントエンドとバックエンドそれぞれにプロジェクトを作成します。vue createnpm 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の引数によって読み込まれる環境ファイルが変わるので、それによって環境変数を切り替えます。

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

axios.create({

baseURL: process.env.VUE_APP_API_URL
})

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


.env

VUE_APP_API_URL=http://localhost:3000/


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


.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などで指定します。

<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>を設置します。

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

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

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

// 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の制約のため不可能だったので、バックエンドのNode.jsでスクレイピングしています。


デプロイ

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


バックエンド


package.json(抜粋)

"scripts": {

"start": "node index.js"
},


フロントエンド

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

$ npm install express serve-static

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


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);


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