GAミント至上主義

安くて速いが好きなWEBアプリ開発者。最近はPython, Vue.js, Firebase, GKE。@株式会社ビズオーシャン

このブログとuyamazakについて(先頭固定)

【お約束】 投稿内容は個人の見解であり、所属する組織の公式見解ではありません。

株式会社ビズオーシャンのエンジニアリング部技術系リーダー。
コミケ3日目で10年以上売り子無欠勤
アズールレーン@竹敷でUI、UX研究中

GitHub: https://github.com/uyamazak/

ビズオーシャンで関わってるサービス

yagish履歴書

スマホでも履歴書が作れるPWA

プログラムとサーバー構築などシステム系全般担当
そのほかデザイン・コーディング等はhosogai (id:shellme)
rirekisho.yagish.jp

Temply

パスワード付のファイル送付サービス

一部難しいとこサポートとかコードレビュー
tminami (id:takuya_minami373)がメインで開発
そのほかデザイン・コーディング等はhosogai (id:shellme)

添付ファイルをメールで安全に送るTemply(テンプリー)

ありがとうポスト

WEB上から和紙のはがきでお礼状とか送れるサービス
企画、運用、ユーザーサポート、一部印刷とかだいたい全部。
開発・サーバー運用はソニックガーデン

ありがとうポスト | スマホから味のある和紙はがきが送れるサービス

略歴

Google App Engine standardのNode8 & Python3.7対応で移転祭りはっじまっるよ~!

わぁい!

GCPコスパ最強のアプリ環境App Engineで長年待ち望んだPython3が公開されました。まだβ

従来のGAEと大きく変わるためSecond generationと呼ばれてます。
App Engine standard environment runtimes  |  App Engine Documentation  |  Google Cloud

これまで、言語が古いとかライブラリが限られるとか、大きな欠点がなくなり、GKE+HTTPSロードバランサーを使わざるをえなかった状況がかなり減りそうな予感。

GKEと比較したGAEのメリットはパッと思いつくだけで

  • インスタンスの常時起動が不要!
  • HTTPSロードバランサーの最低料金(2000円/ぐらい)が不要!
  • SSL証明書の設定・更新も自動!
  • オートスケールの設定も楽ちん!
  • バージョンごとにユニークなURLが振られて確認しやすい!

Python3.7ということで、DjangoなんかはGAE一択になりそうな予感。

Nodeも8だから、Cloud Functionsの方で肥大化してしまったサービスはGAEに移した方が管理しやすいかも。

Nuxt.jsを使ったSSRのVue.jsアプリなんかもGAEでいけそう。

さすがにヘッドレスChromeは無理かな。。。と思ってググったら公式にあるからいけそう!
Using Headless Chrome with Puppeteer  |  App Engine standard environment for Node.js docs  |  Google Cloud

とはいえ、まだ触ってないので、これからガンガン試してみます。

Node.js超入門[第2版]

Node.js超入門[第2版]

Nuxt.jsいらない説

Vue使うなら最初からNuxt.js使えよ、という脳死条件反射みたいな風潮にちょっと反論してみる。

概論

まずVueで始めた方がいい。
最初から大きなアプリを作るつもりだったり、SSRが必要だったらNuxtでいいかも。

まずNuxtを使わない理由3つ

1. Vue CLI3でよく使う公式ライブラリはインストールできるようになった

最近アップデートされたVue CLI3では、プロジェクト作成時にVue Router、Vuex、テスト、lint関連、TypeScriptサポートなどが選べ、選んだ機能に最低限必要な設定ファイルや初期テンプレートが生成されるようになりました。
ホットリロードの開発サーバーも付いているのでほとんどの場合これで十分ではないだろうか。

SSRはついてないので自分でやるかNuxtを使った方がよさそう。
Vue SSR Guide | Vue.js Server-Side Rendering Guide

2. 「The Progressive JavaScript Framework」に反している

jp.vuejs.org

融通が効く
ライブラリと完全な機能を備えたフレームワークの間で拡張できる徐々に採用可能なエコシステム

だからこそNuxtが生まれた理由でもあると思うけど。
Vueは必要に応じてパーツを追加、拡張していこう、みたいな考えを貫いており、コア機能をシンプルに保っています。
その哲学は機能追加の議論などでも貫かれており、かっけーとよく思ってます。

例として最近みたVueにconst的なデータを持たせたいみたいな議論
Constant component data · Issue #6004 · vuejs/vue · GitHub

Vueを始めるときも、最初はCDNからの読み込みで触って、大きくなってきたらシングルファイルコンポーネントで書きたくなってvue-cli使って、みたいに大きくなっていったので、非常にいい思想だと思っています。

その過程でああ、だからVuexがいるんだ、router使うとこんな便利なんだ、みたいな途中の学習や納得が積み重なりVueへの理解が深まります。

一方Nuxt.jsはRailsみたいにいきなりどかっとサーバー環境まるごと全部入り環境な感じなので、Nuxtで一通り用意されてる構造を理解して、それに従って作っていく感じになります。

3. Vue公式ではない

NuxtはVueを使ってるけど別プロジェクトです。
それだけのことですが、NuxtはVueの変更に対し後追いになってしまったり継続が大変そうだなぁと思う。

関係ないけど面白いのがGitHubでコントリビューター一覧上位を見ると、全然空気が違う。

Vueは中国系とアニメアイコンが目立つのに対し
Contributors to vuejs/vue · GitHub

Nuxtはヨーロッパ系な感じ。
Contributors to nuxt/nuxt.js · GitHub

それでもNuxtを採用する理由

  • VueでSSRを手っ取り早くやりたい
  • Vueのコンセプトどうでもいいから一通り全部用意してもらいたい
  • みんな使ってるから使う

本番環境と開発環境でファビコンの色を変えるとすごい分かりやすい話

環境ごとにファビコンの色を変える小技。
前々職にいたデザイナーさんがやってたのを見てから、使わせてもらってます。

yagishの場合こんな感じ。タブを見ると一瞬で分かる。
f:id:uyamazak:20180814141916p:plain

明らかにヤバい紫のヤギ大根が開発(社内サーバー)
まだできてなさそうな緑のヤギ大根がステージング(Firebase)
白いヤギ大根が本番(Firebase)

ただそれだけのことなんだけど、実際使ってみると「これがデザインの力か」と実感できるはず。
これ開発?本番?と悩む時間が無くなる。
見て一発で分かる、考えたり読まなくていいって大事。

ちなみにyagishではロゴがSVGでVue側で操作できることを活かしてロゴも変えてます
f:id:uyamazak:20180814142145p:plain

実装方法としてはだいたい環境変数で切り替えるんだけど、ただテンプレートでlinkタグをif文で切り替えるか、フレームワーク等によってはプラグインでhead内のタグを管理してるから面倒だったりする。

ノンデザイナーズ・デザインブック [第4版]

ノンデザイナーズ・デザインブック [第4版]

GKEでIngressを使ってService単位でロードバランシングする

以前の下記記事の続きで
uyamazak.hatenablog.com

本番とステージング環境で、管理コストを削減するために

を使いまわし、
/api/* → 本番用Service
/api-staging/* → ステージング用Service
とパスでサービスを分けたいときはIngressが必要だったのでやってみた。

クラスタでよければ、HTTP(S)ロードバランサーの設定だけでクラスタ単位で向けられるのでできる。

Ingressとは

Nianticのゲーム名ではなく、直訳すると「入ること」とか「入場権」的な意味らしい。
ネットワークの文脈だともっといい訳があるかもしんない。
2016年夏ごろにGKEで構築した時は見た覚えがなく、HTTP(S)ロードバランサーの設定でやっていたので、新しめの機能なんだと思う。
今からGKEで構築してドメインSSLを使うならGoogle推奨のやり方となる。

Setting up HTTP Load Balancing with Ingress  |  Kubernetes Engine Tutorials  |  Google Cloud

GKE上でロードバランサーが動くわけではなく、実装はHTTP(S)ロードバランサーで行われている。

それぞれの環境のDeployment, Derviceを作る

今回の主題ではないのでハマりどころ以外はざっくり

以下kcはkubectlのエイリアス

Deploymentは下記二つ

% kc get deploy
NAME                     DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
varuna-django            1         1         1            1           21h
varuna-django-staging    1         1         1            1           21h

それに対するServiceは下記

% kc get service
NAME                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes               ClusterIP   10.27.240.1     <none>        443/TCP          21h
varuna-django            NodePort    10.27.254.67    <none>        8080:30001/TCP   21h
varuna-django-staging    NodePort    10.27.242.187   <none>        8080:30002/TCP   21h

Ingress用のyamlを書く

公式ドキュメントを参考に振り分けする
ingress.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: varuna-ingress
spec:
  rules:
  - http:
      paths:
      - path: /*
        backend:
          serviceName: varuna-django
          servicePort: 8080
      - path: /api/*
        backend:
          serviceName: varuna-django
          servicePort: 8080
      - path: /api-staging/*
        backend:
          serviceName: varuna-django-staging
          servicePort: 8080

Deploymentでのヘルスチェック用のパスの設定

IngressのDeploymentに対するヘルスチェックはデフォルトで/、ステータス200になっている。
今回のサービスだと/api/hc的なパスにしていたので、readinessの設定が必要。しばらくハマった。
下記ページでも上がっていた。

github.com
edit deploy でreadinessProbeとついでに同じようなlivenessProbeも設定しておく。

readinessProbeとlivenessProbeについて、それぞれ詳しくは公式ドキュメント
Configure Liveness and Readiness Probes - Kubernetes

kc edit deploy varuna-django
# 適当にいろいろ削った
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    run: varuna-django
  name: varuna-django
spec:
  replicas: 1
  selector:
    matchLabels:
      run: varuna-django
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: varuna-django
    spec:
      containers:
      - image: asia.gcr.io/your-project/tag
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /api/hc_pdf
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 3
          periodSeconds: 3
          successThreshold: 1
          timeoutSeconds: 3
        name: varuna-django
        ports:
        - containerPort: 8080
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /api/hc
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 3
          periodSeconds: 3
          successThreshold: 1
          timeoutSeconds: 3

その他いろいろ

ロードバランサーのIPを静的にするとか、ロードバランサーSSL証明書(Let's Encrypt)の設定をする

Googleが自動でやってくれるのを待ち望んでいるがまだのようなので自分で作ったスクリプトで設置した
自分で作っておいてなんだけど、なかなか設定がめんどくさい。
GitHub - uyamazak/gcp-ssl-auto-renewer: Auto renew SSL Certificates commands on Google Cloud Platform (GCP).

これでクラスタを複数作らずに、サービス単位に振り分けることができた。
GKEというかKubernetesは発展途上なので、時間が空いたら以前やった手順を使わずに、公式ドキュメントを見直した方がよさそう。

コンテナ・ベース・オーケストレーション Docker/Kubernetesで作るクラウド時代のシステム基盤

コンテナ・ベース・オーケストレーション Docker/Kubernetesで作るクラウド時代のシステム基盤

Kubernetes完全ガイド (impress top gear)

Kubernetes完全ガイド (impress top gear)

Docker/Kubernetes 実践コンテナ開発入門

Docker/Kubernetes 実践コンテナ開発入門

個人開発にFirebaseとVue.jsとPWAがおすすめな理由

おかげさまで様々なメディアに取り上げてもらったり、はてブも800超えたりしているyagish。
想像以上の反響の大きさに、勢いがあるうちにもっと良くしようといろいろ施策を考えてます。
が、yagishで使ったVue.js、Firebase、PWAが新規事業だけでなく、個人開発にもすごくいいと感じたので、理由をメモっておきます。
お盆明けぐらいから終業後にまず自分用WEBアプリ作りたい。

rirekisho.yagish.jp

Firebaseの圧倒的低コスト

個人で毎月数千円の固定出費は痛いもの。Firebaseなら低コストで始められ、無料枠と従量性のおかげで忙しくて一時的に開発が止まってそのまま放置しててもお小遣いは安心です。
比較対象はEC2、GCE、GKEやVPSとしています。GAEだとこの点はいい勝負かも。

まずは無料枠で済む

firebase.google.com

fushiroyama.hatenablog.com

この記事にもありますが、Firebaseは無料枠があり、小さなWEBアプリでデータも大きくなければ、ほぼ無料といえる値段で始めることができます。
たとえ大きくなっても他でやるより安い場合が多いですし、困るレベルに利用されたなら、他への載せ替えは余裕だと思います。

ドメインSSLはFirebase Hosting

サービス名とドメインぐらいはちゃんと考えて決めた方がいいとは思いますが、プロトタイプレベルであれば、firebaseでもらえるサブドメインで問題なく動きます。
しかもSSL証明書も勝手に作成、更新してくれるので、自分でいろいろやる手間が省けます。Let's Encryptでやるにしろ自分で自動化するのは面倒です。
Hostingも開発中なら無料枠で余裕。

DBはFirestore

Firestoreも無料枠超えてからの従量課金なので、使わなければ無料で収まります。ちゃんとしたRDB立てるとなると、Cloud SQLになりますが、これは月5000円程度かかってしまいます。本番利用が推奨されていないmicroなどでも2000円ぐらいはかかるような。GCEやGKEで自分で立てるのはそこまで安くならない上、メンテが面倒すぎて論外です。

サーバーの処理はCloud Functions

サーバー側の処理が必要になったらまずCloud Functionsでなんとかすれば、リクエスト単位の課金なのでよほどの回数、使い方をしなければ安く済みます。
またキャッシュが使えるようなデータであればCache-Controlヘッダーの設定でCDNで返すことができ実行回数が減るので、もっとコストを抑えることができます。

長時間かかったり、動画、ビッグデータなどCPUやメモリを大量に使うようなバッチ処理はプリエンプティブ仮想マシンを使えばいいと思いますが、新規、個人開発でいきなり必要になることはほとんどないと思います

cloud.google.com

これらを活用すれば開発中、公開後アクセスがないうちは月数ドル以下で抑えることが可能です。
が、使い方によっては下記の記事のようにもちろんバカ高くなる可能性もありますので、保証はできません。

gigazine.net

圧倒的低開発スピード

Vue.jsなどの最新の開発環境と、Firebaseのライブラリを活用して圧倒的スピードで開発できます(人による)

Vue.jsなどの最新のJavaScript開発環境を活用

これは最近活発なnpm、node.js、ライブラリ、フレームワークなどJS界隈のおかげです。

私はVue.jsにしましたがReact、Angularでも状況や好みに合わせて選べば大きな違いにはならないかと思います。

個人的にPythonが読みやすいしブレが減るので好きなんですが、昨今のメリットを考えるとjavascriptを使うほかにないと思います。またES2016とかどんどん便利な機能が追加されているので、jsのストレスはどんどん減っていくと思います。

周りの話を聞くとnuxt.jsがいいよ、と言っている人も多いし、使ってる人も多いんですが、Vue-CLI3でrouter、Vuexなど公式機能は一通り使えるようになるので、私はまだ使わなくてもいいかなという感じがしました。Vue本体の機能を使いこなして不便を感じたら移ろうかな。

サーバー側のCloud Functionsもjsで

最近Python3も使えるようになったので、それでもいいんですが、やっぱり脳の切り替えや実際の書き換えコストがあるため、同じ言語、パッケージ、ライブラリ等を使って両方書けるのは大きいと思います。

最近Node.js 10が使えるようになったのでasync/awaitも使えます。

デザインフレームワークを活用

デザインフレームワークも充実しており、おなじみのBootstrap,マテリアルデザインなどをVueコンポーネントで使うことができます。

qiita.com

yagishではelement-uiを使い、他の社内システムではVuetify、8月から新しく開発するものはiViewを使用してます。

これらを使うことで、ほとんど自分でCSSを書くことなくレイアウトを組むことができると思います。

ユーザー認証にコード不要

ユーザー認証にはFirebaseのAuthenticationを使い、Google,、FacebookTwitterGitHubなどのソーシャルログインだけにすればログインのフォームUIですら必要ありません。
特に最初はGoogleログインだけにすればアプリ登録も不要なので早いです。
またパスポート不要のメール認証にも対応しており、今後使う予定です。

またDBのFirestoreではその構成単位であるコレクション、ドキュメント単位で上記Authenticationの情報を使ったルールで権限を与えることができるので、アプリ側でいちいちユーザーに権限があるのか、クエリごとにチェックする必要がなく、ルールさえしっかり設計すれば安心してコードを書くことができます。
ルールに沿ってないクエリは実行前にはじかれます。

yagishではuidをつかったコレクション(ディレクトリみたいな感じ)で切り、その下はそのユーザーでしか読み書きできない、というシンプルなルールだけで済んでます。

ユーザーが作ったコンテンツを一般公開したり、ユーザー間で共有、グループ機能などをする場合は結構複雑になってくると思いますが、下記の公式ドキュメントとか参考にいじりまくればなんとかなりそうです。

ユーザーとグループのデータアクセスを保護する  |  Firebase

学習コスト

新しい技術を身に着ける勉強時間を単純にコストとするのに納得がいかないし、Firebaseを学習して損はない。個人的にはコストとは感じないので論外。

Firestoreでコレクションだけ持ってるドキュメントを一覧で取得できなくてハマったらFirestoreの構造を実感できた

Firebase Admin SDKでデータを抜き出そうとしたら取れなくてハマった。
結論としてはコレクションのみを持つドキュメントは一覧を取得することができないのが分かった。

Firebase Consoleだと見れるけどどうやってるかわからないが、よく見ると注意書きがあることに後で気づく。

f:id:uyamazak:20180808111905p:plain

全体のデータ構造は下記のようになっている

/userdata/{uId}/backups/{backupId}

あえて図にすると下記のような感じ。

  • がコレクション、#がドキュメント
-userdata
  #yamada
    -backups
      #backup1(データ入り)
      #backup2
      #backup3
  #suzuki
    -backups
      #backup1
      #backup2
      #backup3
var cRef = db.collection('userdata')
cRef.get()
    .then(snap =>{
      console.log(snap.size)
    })
# 0

これはyamadaとsuzukiが欲しいがとれない

しかしbackupsまで入れば取れる

cRef.doc('yamada')
    .collection('backups')
    .get()
    .then(snap =>{
      console.log(snap.size)
    })
# 3

最初ルートは取れないかと思ったけど関係はなく、いろいろ試した末の違いとしては、ドキュメントにデータがあるかどうか。
userdata/yamadaのドキュメントにすでにあるbackupsのほかにフィールドを追加すれば取得することができた。

Firestoreのドキュメントはコレクションとフィールド(Key, Valueのデータ)の両方を持つことができる、ファイルシステムで言うディレクトリみたいなものなのでややこしい。

Firestoreを大きなJSONと考えるとこの問題は理解できないけど、Firestoreはファイルシステムや、JSON専用のKVSのようなものだと考え、途中のパスは単なるパスであり、jsの配列ような実体はないと考えると納得がいった。

そう体感してから下記ドキュメントを読むとそれはそうだなと思えた。
Cloud Firestore データモデル  |  Firebase

コレクションを「作成」したり「削除」したりする必要はありません。コレクション内の最初のドキュメントを作成すると、コレクションはすでに存在しています。コレクション内のすべてのドキュメントを削除すると、コレクションは存在しなくなります。
警告: ドキュメントを削除しても、そのサブコレクションは削除されません。

サブコレクションが関連付けられているドキュメントを削除しても、そのサブコレクションは削除されません。その後もサブコレクションには、リファレンスによるアクセスが可能です。たとえば、db.collection('coll').doc('doc') によって参照されるドキュメントは存在しなくなったにもかかわらず、db.collection('coll').doc('doc').collection('subcoll').doc('subdoc') によって参照されるドキュメントは存在する場合があります。ドキュメントを削除するときにサブコレクション内のドキュメントも削除する場合は、コレクションを削除するで説明されているように、手動で削除する必要があります。

Vue.jsとFirebaseで本番環境、ステージング環境をなるべく節約して構築する

yagish履歴書は、システム1人、デザイン1人、イラスト1人、計3人の小さなチームで作っていて、開発は非常に身軽なので、本番公開時もローカルの開発環境+Firebase本番環境だけだった。

rirekisho.yagish.jp

でもさすがに大きな変更を本番でやるのは怖いので、手間と費用をなるべく抑えつつ、Firebaseを使ったステージング環境を構築することにした。

開発環境はVue CLI 3で構築。

cli.vuejs.org

環境変数

これは3ファイルで分ける

.env.production
.env.development
.env.staging

ステージング環境について公式ドキュメントに書いてあるので、参考にする。
cli.vuejs.org

.env.staging

NODE_ENV=production
VUE_APP_TITLE=My App (staging)

ただNODE_ENVを上書きしてしまうので、ステージングの時に切り替えたい部分でNODE_ENV === 'staging'を判定に使うことができないため、VUE_APP_MODEという変数を追加して使うことにした。
あんまりきれいじゃない。
.env.staging

VUE_APP_MODE=staging

ビルド時に書き出しディレクトリを変えて、Dockerコンテナは一つで済ませる

開発環境はDockerコンテナで行っているが、これを本番、ステージングなどで分けてしまうと面倒くささが圧倒的に増すのでやりたくない。
開発サーバーの実行、ステージング用ビルド、本番用ビルドもすべて一つのコンテナで行う。

まず、buildコマンドを明示的にbuild-staging、build-productionでそれぞれ--modeをつけて分ける。

package.json

{
  "name": "yagish",
  "version": "1.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build-staging": "vue-cli-service build --mode staging",
    "build-production": "vue-cli-service build --mode production",
    // etc...
  },
// etc...

これだけだとビルドしたファイルが同じdistディレクトリに入ってしまい、間違えてステージングのファイルを本番にデプロイなんてことも起きそうなので分ける。

書き出し先ディレクトリはvue.config.jsのoutputDirで行う。
上記で書いた環境変数を使ってディレクトリを変える関数を作って切り替えた。

vue.config.js

const getOutputDir = () => {
  if (process.env.VUE_APP_MODE === "production") {
    return 'dist-production'
  } else if (process.env.VUE_APP_MODE === "staging") {
    return 'dist-staging'
  }
}
module.exports = {
  outputDir: getOutputDir(),
  // etc...

この設定でステージングはdist-stagingディレクトリに、
本番はdist-productionに書き出されるようになった。
distディレクトリは間違わないよう消しておいた

Firebaseにデプロイ

次にFirebase Hostingでデプロイ先プロジェクトを分ける設定。
もちろん事前に本番用、ステージング用でプロジェクトを作っておく。

公式にあった
Firebase CLI リファレンス  |  Firebase

firebase use --add

でステージング、本番用の設定を追加しておく

firebae.jsonに書くpublicはdeploy時、-pオプションで上書きできるのでこれで分ける。

最終的にステージングへのビルド、デプロイは下記2つで完了する

yarn build-staging
firebase deploy --project your-staging-project-name -p dist-staging

本番はこんな感じ

yarn build-production
firebase deploy --project your-production-project-name -p dist-production

これを実行中の開発用Dockerコンテナでexecするためシェルスクリプト化した。

staging_deploy.sh

sudo docker exec -it \
    $(sudo docker ps -a -q --filter="name=your-container-name") \
    yarn build-staging

sudo docker exec -it \
    $(sudo docker ps -a -q --filter="name=your-container-name") \
    firebase deploy --project your-staging-project-name  -p dist-staging
date

本番用はビルドでこけたらデプロイしないように止める必要がありそう。

API用のGKEとロードバランサーは本番と同じのを使う

GCPHTTPSロードバランサーは最低2000円/月ぐらいしてしまうので、分けるのは避けたい。
またドメインを分けるとSSL証明書とかの手間も増えるのでドメインも同じにしたい。

今回はHTTPSロードバランサーの設定でアクセスしてきたパスでバックエンドを分けた。

/api → 本番用バックエンド
/api-staging → ステージングバックエンド

というように振り分けるようにして、APIではどちらのパスも同じ処理へルーティングして同じコンテナでどちらでも動くようにした。
Vue側は環境変数で切り分ける。

まだ使ったことないけどIngressを使えばもっと簡単にできそうなので調査中。

CORSの設定

許可ドメインの設定はFirebaseや、GoogleAPIキー、Cloud StorageとかDjangoとか使ってるサービスごとにやるので何気にめんどう。
chromeのdeveloper consoleのエラーを見ながら設定しておく。