- 投稿日:2020-05-13T23:32:46+09:00
DockerとVueとNginxで動いたから残しておく。
注意点
色々試してみて、やっと動いたレベルで詳しくはないです。
一つの朗報としては、難しい言葉は一切ありません。(知らないだけ。)今回はDocker-composeと、Vueとnginxの三つを使って色々作成しました。
フォルダ構成
フォルダ構成の注目ポイントは
Dockerで使うフォルダはcomposeフォルダに配置していること。
またlocal.ymlとproduction.ymlでローカル用とマジモン用でcompose upするファイルを分けているということ。Vue
Vueで使用するファイルは、
Vue createでほとんど作ったまま。
(vuetifyを入れたくらい。)DockerfileFROM node:14.2.0-alpine WORKDIR /app COPY package*.json ./ RUN npm install COPY . . EXPOSE 8080nginxの設定
nginxのポイントは、
Entrykitを使っているのがポイント。
メリットは、環境変数を指定してそこから設定ファイル.confを作成してくれる。
今回は、production.ymlで環境変数を指定して、
DockerfileのENTRYPOINTでファイルの生成を指定している。
例:upstream.conf.tmpl⇨upstream.confなので
docker-compose upで起動して
dockerの中を見るとconfファイルが生成されている。
{{ var "BACKEND_HOST" }}などは、環境変数でproduction.ymlで指定した値が入ってくる。DockerfileFROM nginx:1.17.10 RUN apt-get update RUN apt-get install -y wget RUN wget https://github.com/progrium/entrykit/releases/download/v0.4.0/entrykit_0.4.0_linux_x86_64.tgz RUN tar -xvzf entrykit_0.4.0_linux_x86_64.tgz RUN rm entrykit_0.4.0_linux_x86_64.tgz RUN mv entrykit /usr/local/bin/ RUN entrykit --symlink RUN rm /etc/nginx/conf.d/* COPY /compose/production/nginx/etc/nginx/nginx.conf.tmpl /etc/nginx/ COPY /compose/production/nginx/etc/nginx/conf.d/* /etc/nginx/conf.d/ ENTRYPOINT [ \ "render", \ "/etc/nginx/nginx.conf", \ "--", \ "render", \ "/etc/nginx/conf.d/upstream.conf", \ "--", \ "render", \ "/etc/nginx/conf.d/public.conf", \ "--" \ ] CMD ["nginx", "-g", "daemon off;"]log.conflog_format json '{' '"time":"$time_iso8601",' '"remote_addr":"$remote_addr",' '"request":"$request",' '"request_method":"$request_method",' '"request_length":"$request_length",' '"request_uri":"$request_uri",' '"uri":"$uri",' '"query_string":"$query_string",' '"status":"$status",' '"bytes_sent":"$bytes_sent",' '"body_bytes_sent":"$body_bytes_sent",' '"referer":"$http_referer",' '"useragent":"$http_user_agent",' '"forwardedfor":"$http_x_forwarded_for",' '"request_time":"$request_time",' '"upstream_response_time":"$upstream_response_time"' '}';public.conf.tmplserver { listen {{ var "SERVER_PORT" | default "80" }} default_server; server_name {{ var "SERVER_NAME" | default "localhost" }}; charset utf-8; location / { proxy_pass http://backend; proxy_pass_request_headers on; proxy_set_header host $host; {{ if var "LOG_STDOUT" }} access_log /dev/stdout json; error_log /dev/stderr; {{ else }} access_log /var/log/nginx/backend_access.log json; error_log /var/log/nginx/backend_error.log; {{ end }} } }upstream.conf.tmplupstream backend { server {{ var "BACKEND_HOST" }} max_fails={{ var "BACKEND_MAX_FAILS" | default 3 }} fail_timeout={{ var "BACKEND_FAIL_TIMEOUT" | default "10s" }}; }nginx.conf.tmpluser nginx; worker_processes {{ var "WORKER_PROCESSES" | default "1" }}; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections {{ var "WORKER_CONNECTIONS" | default "1024" }}; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout {{ var "KEEPALIVE_TIMEOUT" | default "65" }}; gzip {{ var "GZIP" | default "on" }}; include /etc/nginx/conf.d/*.conf; }Docker-compose
ここで私は詰まったポイントがある。
BACKEND_HOSTに値を入れても入れても502 Bad Gatewayが出て苦しめられた。
BACKEND_HOSTは、nginxからどこにアクセスするかを指定する。production.ymlversion: "3" services: nginx: build: context: . dockerfile: ./compose/production/nginx/Dockerfile container_name: ajiki_nginx depends_on: - vue environment: SERVICE_PORTS: 80 WORKER_PROCESSES: 2 WORKER_CONNECTIONS: 1024 KEEPALIVE_TIMEOUT: 65 GZIP: "on" BACKEND_HOST: ajiki_vue:8080 # container_name:port BACKEND_MAX_FAILES: 3 BACKEND_FAIL_TIMEOUT: 10s SERVER_PORT: 80 SERVER_NAME: localhost LOG_STDOUT: "true" ports: - "80:80" networks: - ajiki_client volumes: - assets:/var/www/_vue vue: build: context: . dockerfile: ./compose/production/vue/Dockerfile image: ajiki_vue container_name: ajiki_vue command: npm run serve networks: - ajiki_client volumes: - assets:/ajiki_vue/.vue/dist privileged: true stdin_open: true tty: true networks: ajiki_client: external: true volumes: assets: driver: local使いそうなコマンド
コマンド# Dockerネットワーク作成 docker network create <name> # ビルド docker-compose -f production.yml build # 起動 -d でバックグラウンド docker-compose -f production.yml up # コンテナ一覧 docker container ls # コンテナに入る alpineはash docker exec -it <container_name> bashhttp://localhost/ にアクセスして画面が表示されたら成功。
最後に
まだよくわからないんですが、
BACKEND_HOSTのホストにremoteaddrのホスト名を指定しても動きました。
BACKEND_HOST: 172.20.0.1:8080なぜですか。。。ぜひ、補足や付け足した方がいいことなどあればリクエストください。
- 投稿日:2020-05-13T23:17:57+09:00
Vuejs+TypeScript×Firestoreで、追加,削除,更新を行う方法メモ
概要
Vuejs+TypeScriptを使って、Firestoreを扱うときのメモ
ドキュメント構成
Firestoreで下記のdocを更新したいときの方法
フィールド論理名 フィールド物理名 型 予算名 name string 予算額 cache number 色情報 color string 画面
新規作成と編集&削除を行う画面は下の感じで作成しました。
新規作成画面
編集&削除
画面ソース
Vue.jsでのdataの宣言
/view/plan/edit.vueexport default class PalnEdit extends Vue{ private plan: Plan = { id : "" , name : "" , cache : 0 , color : "#" + Math.floor(Math.random() * 16777215).toString(16) } }データベースに合わせたTypeScriptでの方を定義
Plan.d.tsexport interface Plan { id?: string; name?: string; cache?: number; color?: string; }画面での値はv-modeより受け取る
<b-form-group class="form-group"> <b-form-input type="number" placeholder="必須項目" v-model="plan.cache" :state="validated ? valid : null" /> <b-form-invalid-feedback>{{ errors[0] }}</b-form-invalid-feedback> </b-form-group>登録や更新削除ボタンに
@click:add()等のイベントも追加しておく各処理のソース
追加
登録ボタンのイベントを定義
/view/plan/edit.vueprivate add(){ delete this.plan.id; firebase .firestore() .collection("plan") .add(this.plan) .then(function() { }) .catch(function(error) { }); }更新
更新ボタンのイベントを定義
ドキュメントのidはurlパスから取得するように設定した。/view/plan/edit.vueprivate upd(){ const docid = this.$route.params.id; const paln: Plan = this.plan; delete paln.id; firebase .firestore() .collection("plan") .doc(docid) .update(paln) .then(() => { }) .catch(function(error) { console.error("Error adding document: ", error); }削除
削除ボタンのイベントを定義
ドキュメントのidはurlパスから取得するように設定した。/view/plan/edit.vueprivate del(){ this.busy = true; const groupid = localStorage.groupid; const docid = this.$route.params.id; firebase .firestore() .collection("plan") .doc(docid) .delete() .then(function() { }) .catch(function(error) { console.error("Error removing document: ", error); }); }
- 投稿日:2020-05-13T20:59:06+09:00
vue/max-attributes-per-lineがprettierでうまくフォーマットされなかった
Vue.js を eslint とprettierを使って開発してたらフォーマットがうまくいかなかったので解決策と設定をメモ
エディタはvscode問題
eslint-plugin-vueのルールmax-attributes-per-line(1行あたり属性値の最大数を制限するルール)をprettierがうまくフォーマットしてくれなかった
解決
issueのコメントに対策があった
https://github.com/prettier/eslint-plugin-prettier/issues/94https://github.com/prettier/eslint-plugin-prettier/issues/94#issuecomment-438781791
結局、属性値の最大数を増やして対応が良さそう
複数の属性値が許可されてしまうが、1行あたりの文字数の制限超えたらよしなに改行してくれるので酷くならないよねって感じ
根本的な解決ではないけど、それで良いやと思ったので設定を変える.eslintrc.json (落ち着いたやつ)
{ "env": { "browser": true, "es6": true, "node": true }, "parser": "vue-eslint-parser", "parserOptions": { "parser": "babel-eslint", "ecmaVersion": 2020, "sourceType": "module" }, "plugins": [ "vue", "prettier" ], "extends": [ "eslint:recommended", "plugin:vue/recommended", "plugin:prettier/recommended", "prettier/vue" ], "rules": { "prettier/prettier": [ "error", { "singleQuote": true, "htmlWhitespaceSensitivity": "ignore" } ], "vue/max-attributes-per-line": [ "error", { "singleline": 5, "multiline": { "max": 1, "allowFirstLine": false } } ] } }その他
vscodeの拡張として入れてるprettierがフォーマットしてたり、veturがフォーマットしてた?のでvscodeの設定も少しいじった
(いじりすぎて迷子になったので後で見直す)
- 投稿日:2020-05-13T20:59:06+09:00
Vue + ESLint + Prettier 設定 自分メモ
Vue.js を eslint とprettierを使って開発してたらフォーマットがうまくいかなかったので解決策と設定をメモ
エディタはvscode問題
eslint-plugin-vueのルールmax-attributes-per-line(1行あたり属性値の最大数を制限するルール)をprettierがうまくフォーマットしてくれなかった
解決
issueのコメントに対策があった
https://github.com/prettier/eslint-plugin-prettier/issues/94https://github.com/prettier/eslint-plugin-prettier/issues/94#issuecomment-438781791
結局、属性値の最大数を増やして対応が良さそう
複数の属性値が許可されてしまうが、1行あたりの文字数の制限超えたらよしなに改行してくれるので酷くならないよねって感じ
根本的な解決ではないけど、それで良いやと思ったので設定を変える.eslintrc.json (落ち着いたやつ)
{ "env": { "browser": true, "es6": true, "node": true }, "parser": "vue-eslint-parser", "parserOptions": { "parser": "babel-eslint", "ecmaVersion": 2020, "sourceType": "module" }, "plugins": [ "vue", "prettier" ], "extends": [ "eslint:recommended", "plugin:vue/recommended", "plugin:prettier/recommended", "prettier/vue" ], "rules": { "prettier/prettier": [ "error", { "singleQuote": true, "htmlWhitespaceSensitivity": "ignore" } ], "vue/max-attributes-per-line": [ "error", { "singleline": 5, "multiline": { "max": 1, "allowFirstLine": false } } ] } }その他
vscodeの拡張として入れてるprettierがフォーマットしてたり、veturがフォーマットしてた?のでvscodeの設定も少しいじった
(いじりすぎて迷子になったので後で見直す)
- 投稿日:2020-05-13T18:43:10+09:00
【Go】脱Go初心者のためにGoでオンライン交流会に役立つツールを制作してみた
はじめに
Go案件に携わりたいなと思い、お勉強のがてらこんなツールを作りました。
制作過程をまとめようと思っていたんですけど、つまづいたポイントをまとめただけになりました。。
合間を見てどんどん機能を追加していこうと思います!・URL
https://group-shuffle-app.herokuapp.com/・Github
https://github.com/yagi-eng/group_shuffle_guiツールの説明
以下の問題解決を目的としたツールです。
- 交流会などで、複数回参加者をシャッフルしてグループ分けする
- なるべく重複しないようにする
おおまかな流れ
- 参加者の組み分けをランダムに生成し、以下の評価方法に基づき評価
- 1.を複数回繰り返し、一番評価が良い組み分けを最良の組み分けとする
評価方法
- 組み分けに対して、参加者毎のスコアを以下のように定義
- 各回で同席した人との同席回数の合計を、全ての回で合計した値
- 全参加者のスコアの標準偏差が最も小さい組み分けを最良の組み合わせとする
例えば、1さんが1回目に[2,3,4]、2回目に[4,5,6]の人と同席になった場合。
1回目では、いずれも初同席なので同席回数はそれぞれ1となり、1さんのスコアは1+1+1=3
2回目では、4とは2回目の同席なので同席回数は2となり、1さんのスコアは2+1+1=4使用技術
- Go 1.14
- echo v3.3.10
- Heroku
- Vue.js
Goでつまづいたこと
ディレクトリ構成のベストプラクティスがわからない
以下の記事を参考にしました。
package分割がうまくできない
以下の記事を参考にしました。
Go Modules でインターネット上のレポジトリにはないローカルパッケージを import する方法go moduleを使うと実現できます。
import文に相対パスを指定する方法もありますが、現在では非推奨なようです。package化したメソッドの呼び出しは
(package名).(メソッド名)である必要があります。
当たり前のことなんですけど、これをうっかり見落としていて少し時間をかけてしまいました。go module導入したらechoがimportされなくなった
以下の記事を参考にしました。
GolangとEchoでお手軽にAPIサーバを立てる$ go mod initした後に
$ go get github.com/labstack/echo/v4すると、いい感じに解消してくれます。
自分の場合は順番が逆でした。ちなみに、以下のコマンドで、
go.modファイルから不要なモジュールを削除してくれます。$ go mod tidy型が違うだけの処理を共通化できない
具体的に、
util/slice/slice.goの以下2つのメソッドを1つにまとめたかったができなかった。slice.go// DevideArr スライスを分割する func DevideArr(arr []int, lenOfEachSlice int) [][]int { arrs := [][]int{} len := len(arr) for i := 0; i < len; i += lenOfEachSlice { end := i + lenOfEachSlice if len < end { end = len } arrs = append(arrs, arr[i:end]) } return arrs } // DevideArrStr スライスを分割する func DevideArrStr(arr []string, lenOfEachSlice int) [][]string { arrs := [][]string{} len := len(arr) for i := 0; i < len; i += lenOfEachSlice { end := i + lenOfEachSlice if len < end { end = len } arrs = append(arrs, arr[i:end]) } return arrs }
interface{}を使えばできるかもと思ったが、[]interface{}を引数として、[]intを受け取ろうとしたらコンパイルエラーになった。Herokuにデプロイはできるが動作しない
以下のコミットのようにポート番号を環境変数から取得するように修正します。
https://github.com/yagi-eng/group_shuffle_gui/pull/4/commits/6ad3ee022d4990b8459c0141b2905278c3259eefserver.go- e.Logger.Fatal(e.Start(":1323")) + e.Logger.Fatal(e.Start(":" + os.Getenv("PORT")))Vueでチャレンジしたこと
formのvalidation
以下の記事を参考にしました。
これでわかるvue.jsのフォームバリデーションVuelidate
段階的に実装していくのでわかりやすかったです。axiosを使ったAPI通信
以下の記事を参考にしました。
axiosの使い方まとめ (GET/POST/例外処理)
Vueではないけど、初めて使いました。
- 投稿日:2020-05-13T10:58:28+09:00
[Vetur 2339] [E] Property '$store' does not exist on type 'CombinedVueInstance<Vue, unknown, ...
症状
以下のように this には $store は無いよ、と怒られます。
import Vue from 'vue' export default Vue.extend({ methods: { hello () { const value = this.$store.getters.value // ^^^^^^ // [Vetur 2339] [E] Property '$store' does not exist on type // 'CombinedVueInstance<Vue, unknown, ... } } })対応
以下の記事を参考にさせていただいて、妥協してワーニングを切りました。
tsconfig.json{ "compilerOptions": { "noImplicitAny": false, "noImplicitThis": false, }, }検討
本来は以下の記事を参考にさせていただいて、Vuex にも型定義をつけたかったのですが、いまは妥協しました。
- 投稿日:2020-05-13T09:10:37+09:00
【Vue】見た目とロジックを分ける
この記事は、主に下記の記事を参考にしています。
Vueを用いた開発プロジェクト用に「コンポーネント設計・実装ガイドライン」を作った話 - Qiita
Vue使いなら知っておきたいVueのパターン・小技集 - QiitaContainerとPresenter
もともとはReactから派生してきた考え方です。
下記記事がわかりやすいです。
[redux] Presentational / Container componentの分離 - react-redux.connect()のつかいかた - Qiita
ComponentとContainerについて - Qiita
Presentational and Container Components - Dan Abramov - MediumContainerとPresenterを分けることのメリット
最初はなんでわざわざ分けるのかと不思議でしたが、下記のメリットが大きいのかなと思いました。
同じPresentational Componentに異なる状態ソースを持たせることで、それらを別のContainer Componentsに再利用することができるようになる
ComponentとContainerについて - Qiita より引用似たようなPresentational Componentが増えてきたらかなり恩恵を受けられるのではないでしょうか。
実装してみる
全体像
containerがロジック、presenterが UI- それぞれのcomponentがに、container, presenterファイルが生成される
containerがpresenterをwrapする- componentを呼び出す時は、
containerを呼び出すContainer
特徴
・データや振る舞いに責務を持つ
・データや振る舞いをPresentational Componentや他のContainer Componentに提供する
・VuexのstoreやVue Routerのrouteを参照しても良い(しなくても良い)
・通常、DOMのマークアップやCSSスタイルを持たない。(仮にDOMを持つとしても、それはラッパー用のdivタグなど)
Vueを用いた開発プロジェクト用に「コンポーネント設計・実装ガイドライン」を作った話 - Qiita より引用presenterを読み込む
// presenterの読み込み // ここでcontainer が presenter を wrap することになる import SampleModal from './presenter' // connect 高階関数 const connect = (SampleModal) => { return { name: `${SampleModal.name}Container`, methods: { handleLogin(social) { this.$auth.loginWith(social) } }, render(h) { return h(SampleModal, { props: { // propsとしてmethod, handleLogin: this.handleLogin } }) } } } export default connect(SampleModal)高階関数 connect
connectが、ContainerとPresenterを結びつけるキーとなっています。
connectは、高階関数で、mapとかと同じですね。
JavaScript 高階関数を説明するよ - Qiita以下の
connectは、Presentational Componentを引数に取り、そのコンポーネントが関心を持つ、
VuexのmoduleのデータとVue Routerのメソッドへのアクセスを与えたContainer Componentを返す高階関数
Vueを用いた開発プロジェクト用に「コンポーネント設計・実装ガイドライン」を作った話 - Qiita より引用containrの呼び出し
com@ponentを使用する時は、
containerを呼び出して使用します。import SampleModal from '@/components/common/Sample/container'Presenter
特徴
- 見た目に責務を持つ
- コンポーネント自身の状態はほとんど持たない
- propsとしてデータとコールバックを受け取れる
- アクションやストアに依存しない
実装
<script> export default { name: 'SampleModal', // props としてuketoru props: { handleLogin: { type: Function, default: () => {} } }, render(h, context) { return ( <modal name="sign-in-modal" resizable={true} adaptive={true} scrollable={true} width={this.$device.isMobile ? '90%' : '40%'} height="auto" > <div class="modal-sign-in"> <p class="modal-sign-in-word">Sign In</p> <div class="modal-sign-in-button"> <button class="modal-sign-in-facebook" onClick={() => { this.handleLogin('facebook') }} > <font-awesome-icon icon={['fab', 'facebook']} class="modal-sign-in-facebook-font" /> <span class="modal-sign-in-facebook-word"> Sign In with Facebook </span> </button> </div> </div> </modal> ) } } </script> // sampleなので、CSSは適当 <style scoped lang="scss"> @import '~assets/scss/variables'; .modal-sign-in { padding: 10px; .modal-sign-in-word { margin: 10px; } .modal-sign-in-button { margin: 5px; .modal-sign-in-facebook { max-width: 250px; .modal-sign-in-facebook-font { margin-right: 10px; } .modal-sign-in-facebook-word { font-weight: bold; } } } } </style>その他参考にした記事
- 投稿日:2020-05-13T00:13:51+09:00
Vue Test Utils カスタムコンポーネントでv-modelを適用したときのテスト
Vue Test Utils カスタムコンポーネントでv-modelを適用したときのテスト
やりたいこと
以下のようなカスタムコンポーネントにv-modelを適用したときに入力値をdataに反映させてテストをする。
sample.vue<template> <div> <formInput v-model="test" type="text"></formInput> </div> </template> <script> import formInput from '~/components/formInput.vue' export default { components: { formInput }, data() { return { test: '' } } } </script>NG例
input要素だとドキュメントにある通り、以下のようにdataに値を反映することができる。
しかし、上記のようなカスタムコンポーネントでこのコードを実行すると"wrapper.setValue() cannot be called on this element"のようなエラーが表示される。sample.spec.jsimport { shallowMount } from '@vue/test-utils' import Foo from '~/pages/sample.vue' const wrapper = shallowMount(Foo) describe('input要素だとこれでパスする', () => { test('テストする', () => { const textInput = wrapper.find('[type="text"]') textInput.element.value = 'some value' expect(wrapper.vm.test).toBe('some value') }) })解決策
inputイベントを発火させることで、入力値をdataに反映させてテストすることが可能。
sample.spec.jsimport { shallowMount } from '@vue/test-utils' import Foo from '~/pages/sample.vue' const wrapper = shallowMount(Foo) describe('カスタムコンポーネントのv-modelでもこれでパスする', () => { test('テストする', () => { const textInput = wrapper.find('[type="text"]') textInput.vm.$emit('input', 'some value') expect(wrapper.vm.test).toBe('some value') }) })参考
こちらにある通り、v-modelはv-onとv-bindを1行で表現したものなので、イベントを発火することでdataを更新することができます。
<input v-model="searchText"> <input :value="searchText" @input="searchText = $event.target.value">誰かの役に立つことを願って。。



