- 投稿日:2020-07-01T23:38:00+09:00
firestoreのデータをvue-chartでグラフ化する時にハマった
firestoreからデータを引っ張り出しvue-chartでレンダリングしようとした時に躓き時間をだいぶ割いてしまったので同じように悩んでる人が居れば(多分いない)助けになりたいと思い共有
データの流れ
API(openweathermap)→ firestore → vue-chart
絵画してみる
chart.vuemounted() { this.renderChart(this.chartData, this.options); },???????????状態
データはしかっりと入っている事を確認できる
色々やってみる①
watchで監視
chart.vuewatch: { data: { handler() { this.renderChart(this.chartData, this.options); }, deep: true, immediate: false } }mounted()では表示されずwatchで表示される
色々やってみる②
試しに数字べた書きしてみる
chart.vuedata() { return { loaded: false, Today: null, data: { labels: [1,2,3], datasets: [ { label: "osaka", data: [200,300,400,], },原因
データが完全に移動する前にレンダリングしようとしているのが原因でした。
タブの大きさ変更などで表示されていたのは、その時にはデータが移動完了していたからですねchart.vue<allchart v-if="loaded" :data="data" :options="options" />chart.vuesnapshot.forEach(doc => { this.data.labels.push(doc.data().Timestamp2); this.loaded = true; }これで解決できます
おまけ
公式をしっかり読みましょう
API呼び出しが非同期だということです。 この時、データが到着する前にあなたはチャートを表示しようとしてしまいます。
これを防ぐには、単純な v-ifが最善の解決策です。
Qiita読み漁る前に公式をしっかり読みましょう
- 投稿日:2020-07-01T22:58:55+09:00
オンライン会議で使えるOSSアプリケーションを作ってみた
auです。
リモートワークが浸透してきて、もっとデジタル化が進んで近未来を題材にしていた漫画のような世界になるのかなととてもワクワクしています。
今回は、大学生が1人で自由に機能を追加できるオンライン会議のOSSを作ってみました。
今の私の技術力だとできないこともあり、理想的なものというわけにもいかなかったのですが、いろんな人の意見をいただいたり、協力をし合って使いやすいものにできたらなと思います。
お礼
今回の企画のレビューをしていただいた企業様です。
ゲーム開発のメンバーを募集しているので興味がある方はぜひ。https://www.find-job.net/list/j126586.html?view=view
https://www.find-job.net/list/j126587.html?view=view
興味深い情報を発信をしているので、フォローして確認してみてください。
https://twitter.com/amazing_engine環境
npm 6.14.5
vue@2.6.11
@vue/cli 4.4.4なぜ作ったのか
オンライン会議のアプリを使っていて、こんな機能があったら嬉しいなー、簡単なゲームとかを1つの画面でやれたら面白いなーと思ったのをきっかけに、自由に制作できるようなOSSを作ってみました。
利用方法
起動方法
- GitHubからレポジトリをクローンする。
- モジュールをインストール
npm install
- サーバを起動する
npm run serve
- localhostにアクセスする。
- リンクマークを押してZoomの招待リンクを入力する。
- 必要に応じてファイルを作成し、パネル内のプラスマークからページを追加する
サイトバーからパネルの枚数も変更・編集をすることが可能です。
ファイルの追加方法
ファイルを記述して追加することで、自由にパネル内で利用することができます。
「src/components/pages」の中にファイルを作成し、MainPageでインポートし、必要箇所を編集することで利用することができます。自動で追加するようにした方が圧倒的に使いやすいと感じているので、後日実装したいと思います。
- ファイルを作成する
今回は「TestPage.vue」というファイル名にしました。
中身は以下の通りです。
TestPage.vue<template> <div class='main'> <h1>{{ msg }}</h1> </div> </template> <script> export default { name: 'TestPage1', data () { return { msg: 'testpage1' } } } </script> <style scoped> </style>- MainPage.vueでインポートする
MainPage.vueで以下の場所にスクリプトを記述します。
2-1. scriptタグ内でインポート
MainPage.vue// 省略 </template> <script> import TestPage1 from './../pages/TestPage1.vue' // 省略2-2. export default内のcomponent
TestPage1.vueTestPage1
2-3. export default内のpaneItems
idとファイル名.nameを辞書型で追加します。
MainPage.vuepaneItems: [ { id: 1, page: TestPage1.name }, // ここを追加する { id: 2, page: TestPage2.name } ]2-4. export defaulr内のmethods「paneItemClick」
MainPage.VuepaneItemClick (event, item, place, paneIndex) { if (place === 'upper') { switch (item.page) { // caseのブロックをファイル名に置き換える case TestPage1.name: this.upperPane[paneIndex].loadPage = TestPage1.name break case TestPage2.name: this.upperPane[paneIndex].loadPage = TestPage2.name break } this.menu[3].child[0].child[paneIndex].title = 'Pane ID: ' + (paneIndex + 1) this.upperPane[paneIndex].icon = false } else if (place === 'under') { switch (item.page) { case TestPage1.name: this.underPane[paneIndex].loadPage = TestPage1.name break case TestPage2.name: this.underPane[paneIndex].loadPage = TestPage2.name break } this.menu[3].child[1].child[paneIndex].title = 'Pane ID: ' + (paneIndex + 1) this.underPane[paneIndex].icon = false } this.closePaneModal() },文章で書いてみて、やっぱり自動で追加するようにした方が絶対にいいと思いました・・・。issueを発行しておこう。
ビデオ通話機能の実装
必要な機能
ビデオ通話の機能で必要なものは以下の通りです。
- 複数人でカメラとマイクを通して通信ができる
これくらいかなと思ったので、自分でも作ろうとしたのですが、P2Pで複数人で通信するとなると、通信量の計算やAPIの利用となり、難しいと判断しました。
調べてみるとZoomをiframeで埋め込むことができるようです。
https://devforum.zoom.us/t/embed-zoom-video-to-a-web-server/2882?page=2
苦労した点
iframeでページを開いた際に、マイクとカメラをWebサイト側で許可を出さなければいけません。
日本語だとヒットせずに、英語で調べると該当記事にたどり着くことができました。
Meeting.Vue<iframe v-show="iframe.hidden" :src="iframe.src" :height="height" :width="width" scrolling="no" frameborder="no" allow="geolocation;camera;microphone;" > </iframe>参考
https://blog.addpipe.com/camera-and-microphone-access-in-cross-oirigin-iframes-with-feature-policy/
自由にファイルを変更できるパネルの追加
このアプリの肝になる「自由にファイルを入れ替えるパネル」を実装しました。ここではパネルと読んでます。
必要な機能
- 自由に入れ替えることができるパネルの追加
- 機能を実装したファイルを挿入する
- パネルの領域を可変にする
自由に入れ替えることができるパネルの追加
まさにこれだというモジュールがありました。
https://antoniandre.github.io/splitpanes/
ボタンを押すことでパネルを追加でき、別々にページを埋め込むことができるようになりました。
機能を実装したファイルを挿入する
利用方法の説明で、ファイルの追加方法の通りです。
パネルの領域を可変にする
これは利用したモジュールに実装されていたので特に苦労はしませんでした。ありがとうございます。
苦労した点
ファイルを追加するということは、「コンポーネントをページに埋め込む」ということなのですが、方法がいまいち分からず結構調べました。
以下のように実装しました。
MainPage.vue<pane :size="100-size"> <splitpanes> <pane class="panes" v-for="underPaneN in underPaneNumber" :key="underPaneN"> <div v-show="underPane[underPaneN-1].icon"> <p>{{ underPaneN }}</p> <svg id="icon" @click="openPaneModal(underPane[underPaneN-1].id, 'under')" aria-hidden="true" focusable="false" data-prefix="far" data-icon="plus-square"</svg> </div> <component :is="underPane[underPaneN-1].loadPage"> </component>// ここを追加 </pane> </splitpanes> </pane>componentタグにページを追加すれば実装することができました。
https://qiita.com/myLifeAsaDog/items/233f10591be8ff42cf1d
完成品
確認している問題点
画面上部に謎の空白がある
以下の記事通り、脆弱性が確認されているモジュールがあったため、アップデートしました。
その後この問題が発生しました。
原因も特定できていないため困っています。
https://program-shoshinsya.hatenablog.com/entry/2020/06/19/144905
今後の展望
今後は、確認している問題点の解決や仲間が集まってくれたらオリジナルのビデオ通話機能を実装したいなと思っています。
他にもこんな機能があれば使いやすいということがあれば、issueなりコメントを残していただけると幸いです。
また、一緒にサービスを考えていく、ものづくりに挑戦してみる、プログラミングが好きという方と一緒に進めていきたいと考えています。
こちらのTwitterアカウントに連絡をください。https://twitter.com/au_prog_shoshi
感想
Vueを使ったことがなく、「こんなものを作りたい」という想いに技術力が足りているか不安でしたが、パソコンが固まるくらい調べてなんとか形にすることができました。
完成したものを見て「私以外のアイデアがもっとあればもっとやりやすいものができたのではないか」とも感じたので、今度は1人ではなく複数人で開発をしてみたいです。
大学1年からプログラミングを学び始め、現在の大学4年の段階で自分が作りたいものをある程度形にすることができるということが分かったので自信につながりました。
これからも作りたいものを作っていきたいと思いました。
- 投稿日:2020-07-01T21:23:18+09:00
Vue CLIでビルド時にメモリ不足になった場合の対処
スタックトレースの例
<--- JS stacktrace ---> ...省略... FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory対応
Node.jsのオプションでメモリサイズを拡張します。
package.jsonに埋め込んでもいいですし、package.json"build": "node --max_old_space_size=4096 node_modules/@vue/cli-service/bin/vue-cli-service.js build ",環境変数で設定してもOK
set or export NODE_OPTIONS=--max_old_space_size=4096
- 投稿日:2020-07-01T18:48:43+09:00
Vue.jsで作るLIFF v2アプリ
はじめに
まず、LIFFとはLINEが提供するウェブアプリのプラットフォームです。現在の最新バージョンは2系となっており、LIFF v2と表記されます。
以下は概要の引用ですが、LIFFを使用することでLINEの機能を活かしたWebアプリを開発することが出来ます。
https://developers.line.biz/ja/docs/liff/overview/LINE Front-end Framework(LIFF)は、LINEが提供するウェブアプリのプラットフォームです。このプラットフォームで動作するウェブアプリを、LIFFアプリと呼びます。
LIFFアプリを使うと、LINEのユーザーIDなどをLINEプラットフォームから取得できます。LIFFアプリではこれらを利用して、ユーザー情報を活用した機能を提供したり、ユーザーの代わりにメッセージを送信したりできます。本記事ではLIFFアプリをVue.js + TypeScriptで作成します。
手順
本記事では以下の手順でLIFFアプリを作成します。
- チャネルを作成する
- Vue.jsのプロジェクトを作成する
- LIFFアプリを開発する
1.の手順は主に
LINE Developers
上の話を、2.の手順ではVue CLI
でVue.jsのプロジェクトを作成する話をします。
上記手順は読み飛ばしても大丈夫という方は3.のLIFFアプリを開発する部分だけをお読みいただけると幸いです。チャネルを作成する
ngrokを起動
LIFFアプリを作成するためにはWebアプリをhttpsで公開する必要があります。
S3などにホスティングしても良いのですが、本記事ではngrokを使用してローカルで動くVue.jsアプリを公開します。
※ngrokのセットアップは主題から外れるため割愛します。ngrok http 8080
https://xxx.ngrok.io
はLIFFのエンドポイントURLとして使用するので控えておきます。プロバイダーを作成する
- LINE Developersにログインする
- プロバイダーを作成する
チャネルを作成する
作成したプロバイダーに
LINEログイン
チャネルを追加します。LIFFアプリを追加する
チャネルにLIFFアプリを追加します。
各設定項目については公式のドキュメントに詳しく書かれています。
エンドポイントURLには前述の手順で控えておいたngrokのURL、https://xxx.ngrok.io
を指定します。ここまでの手順で以下のようなLIFFアプリが作れているかと思います。
LIFF ID
はアプリ開発時に必要になります。ユーザーはLIFF URL
にアクセスすることでこのLIFFアプリを開くことが出来ます。Vue.jsのプロジェクトを作成する
プロジェクトの作成
本記事では
Vue CLI
を使用してプロジェクトを作成します。
(プロジェクト名は適宜置き換えてください)vue create liff-app
プロジェクトの設定は任意のもので構いませんが、本記事では以下のような設定で作成しています。
ngrokからアクセスできるようにする
以下の設定を
package.json
に追加します。package.json"vue": { "devServer": { "disableHostCheck": true } }動作確認
以下のコマンドでVue.jsアプリを起動し、
https://xxx.ngrok.io
からアクセスできることを確認します。npm run serve
ngrok経由でVue.jsアプリを開ければ、LIFFアプリとして実行することが出来ます。
前項の手順で作成したLIFF URL、https://liff.line.me/xxx
にアクセスすると以下のように表示されると思います。LIFFアプリを開発する
LIFF SDKを組み込む
LINE SDKはCDN経由でのみ配布されています。以下のscriptタグを
index.html
に追加します。index.html<script charset="utf-8" src="https://static.line-scdn.net/liff/edge/2/sdk.js"></script>この状態でLIFF SDKは使用可能な状態となりますが、せっかくTypeScriptで使用するので型の情報が欲しくなります。
幸運なことにliff-typeという素晴らしいパッケージが公開されているのでこちらを使用します。ドキュメントにある通り以下のコマンドでインストールし、
npm i -D liff-type
tsconfig.json
ファイルに以下の内容を追記します。
※追記内容は環境に応じて適宜読み替えてくださいtsconfig.json{ "compilerOptions": { "types": ["liff-type"] } }正しく設定が行えればインテリセンスが利いた快適なLIFFアプリ開発環境が作れると思います。
LIFFアプリを初期化する
LIFF SDKの機能を使用するためには、はじめに
liff.init()
を実行する必要があります。
作法として正しいのかは分かりませんが、今はアプリ全体でliffの機能を使用したいためApp.vue
のcreated
のタイミングで実行する処理を入れてみます。なお、
liff.init
の実行にはLIFF IDが必要となります。App.vue@Component export default class App extends Vue { @Prop({ type: Boolean, default: false }) loggedIn = false; created() { liff.init({ liffId: 'ここにLIFF IDを入れる' }) .then(() => { this.loggedIn = liff.isLoggedIn(); }) } }LIFFアプリが動作している環境を取得する
<template> <div class="liff-data"> <table> <tr> <th>OS</th> <td>{{ os }}</td> </tr> <tr> <th>Language</th> <td>{{ language }}</td> </tr> <tr> <th>LIFF SDK Version</th> <td>{{ sdkVersion }}</td> </tr> <tr> <th>LINE Version</th> <td>{{ lineVersion }}</td> </tr> <tr> <th>isInClient</th> <td>{{ isInClient }}</td> </tr> <tr> <th>isLoggedIn</th> <td>{{ isLoggedIn }}</td> </tr> </table> </div> </template> <script> import { Component, Vue } from "vue-property-decorator"; @Component export default class LiffData extends Vue { get os() { return liff.getOS(); } get language() { return liff.getLanguage(); } get lineVersion() { return liff.getLineVersion(); } get sdkVersion() { return liff.getVersion(); } get isInClient() { return liff.isInClient(); } get isLoggedIn() { return liff.isLoggedIn(); } } </script>ユーザのプロフィールを取得する
<template> <div class="profile"> <table> <tr> <th>IDトークンの生成URL</th> <td>{{ token.iss }}</td> </tr> <tr> <th>ユーザーID</th> <td>{{ token.sub }}</td> </tr> <tr> <th>チャネルID</th> <td>{{ token.aud }}</td> </tr> <tr> <th>トークンの有効期限</th> <td>{{ token.exp }}</td> </tr> <tr> <th>IDトークンの生成時間</th> <td>{{ token.iat }}</td> </tr> <tr> <th>ユーザー認証時間</th> <td>{{ token.auth_time }}</td> </tr> <tr> <th>nonce</th> <td>{{ token.nonce }}</td> </tr> <tr> <th>認証方法</th> <td>{{ token.amr }}</td> </tr> <tr> <th>表示名</th> <td>{{ token.name }}</td> </tr> <tr> <th>プロフィールの画像URL</th> <td>{{ token.picture }}</td> </tr> <tr> <th>メールアドレス</th> <td>{{ token.email }}</td> </tr> </table> </div> </template> <script> import { Component, Vue } from "vue-property-decorator"; @Component export default class Profile extends Vue { get token() { return liff.getDecodedIDToken(); } } </script>QRコードリーダを表示する
2020/07/01現在、QRコードリーダーの表示はiOS版LINEバージョン9.19.0以降では動作しないため、関数が存在するかを確認してから使用する必要があります。
参考:https://developers.line.biz/ja/docs/liff/developing-liff-apps/#opening-qr-code-reader
<template> <div class="qr"> {{ scanText }} <button v-on:click="qrScan"> <span>QRコード読み取り</span> </button> </div> </template> <script> import { Component, Vue } from "vue-property-decorator"; @Component export default class QR extends Vue { scanText = ""; qrScan() { if (liff.scanCode) { liff.scanCode().then(result => { const stringifiedResult = JSON.stringify(result); this.scanText = stringifiedResult; }); } } } </script>メッセージを送信する
2020/07/01現在、LIFFアプリからメッセージを送信する方法は現在のトークに送信する方法とターゲットピッカーを表示して、ユーザが選んだ相手に送信する方法の2種類存在します。
後者のターゲットを選択する方法はLINE 10.3.0以降でサポートされる機能のため、動作環境で使用可能かを確認してから送信します。<template> <div class="message"> <input v-model="message" /> <button v-on:click="sendMessage"> <span>送信</span> </button> </div> </template> <script> import { Component, Vue } from "vue-property-decorator"; @Component export default class Message extends Vue { message = 'LIFFへようこそ!'; sendMessage() { liff.sendMessages([{ 'type': 'text', 'text': this.message }]).then(function() { window.alert('Message sent'); }).catch(function(error) { window.alert('Error sending message: ' + error); }); } } </script>
- 投稿日:2020-07-01T18:48:43+09:00
Vue.js + TypeScriptで作るLIFF v2アプリ
2020/07/02追記
記事を公開した当日に公式のnpmパッケージが公開されました。
https://developers.line.biz/ja/news/2020/07/01/published-liff-npm-package/上記パッケージを使用することでLIFF SDKの導入と同時に型の情報を得ることが出来るので、本記事の内容よりそちらを推奨します。
はじめに
まず、LIFFとはLINEが提供するウェブアプリのプラットフォームです。現在の最新バージョンは2系となっており、LIFF v2と表記されます。
以下は概要の引用ですが、LIFFを使用することでLINEの機能を活かしたWebアプリを開発することが出来ます。
https://developers.line.biz/ja/docs/liff/overview/LINE Front-end Framework(LIFF)は、LINEが提供するウェブアプリのプラットフォームです。このプラットフォームで動作するウェブアプリを、LIFFアプリと呼びます。
LIFFアプリを使うと、LINEのユーザーIDなどをLINEプラットフォームから取得できます。LIFFアプリではこれらを利用して、ユーザー情報を活用した機能を提供したり、ユーザーの代わりにメッセージを送信したりできます。本記事ではLIFFアプリをVue.js + TypeScriptで作成します。
手順
本記事では以下の手順でLIFFアプリを作成します。
- チャネルを作成する
- Vue.jsのプロジェクトを作成する
- LIFFアプリを開発する
1.の手順は主に
LINE Developers
上の話を、2.の手順ではVue CLI
でVue.jsのプロジェクトを作成する話をします。
上記手順は読み飛ばしても大丈夫という方は3.のLIFFアプリを開発する部分だけをお読みいただけると幸いです。チャネルを作成する
ngrokを起動
LIFFアプリを作成するためにはWebアプリをhttpsで公開する必要があります。
S3などにホスティングしても良いのですが、本記事ではngrokを使用してローカルで動くVue.jsアプリを公開します。
※ngrokのセットアップは主題から外れるため割愛します。ngrok http 8080
https://xxx.ngrok.io
はLIFFのエンドポイントURLとして使用するので控えておきます。プロバイダーを作成する
- LINE Developersにログインする
- プロバイダーを作成する
チャネルを作成する
作成したプロバイダーに
LINEログイン
チャネルを追加します。LIFFアプリを追加する
チャネルにLIFFアプリを追加します。
各設定項目については公式のドキュメントに詳しく書かれています。
エンドポイントURLには前述の手順で控えておいたngrokのURL、https://xxx.ngrok.io
を指定します。ここまでの手順で以下のようなLIFFアプリが作れているかと思います。
LIFF ID
はアプリ開発時に必要になります。ユーザーはLIFF URL
にアクセスすることでこのLIFFアプリを開くことが出来ます。Vue.jsのプロジェクトを作成する
プロジェクトの作成
本記事では
Vue CLI
を使用してプロジェクトを作成します。
(プロジェクト名は適宜置き換えてください)vue create liff-app
プロジェクトの設定は任意のもので構いませんが、本記事では以下のような設定で作成しています。
ngrokからアクセスできるようにする
以下の設定を
package.json
に追加します。package.json"vue": { "devServer": { "disableHostCheck": true } }動作確認
以下のコマンドでVue.jsアプリを起動し、
https://xxx.ngrok.io
からアクセスできることを確認します。npm run serve
ngrok経由でVue.jsアプリを開ければ、LIFFアプリとして実行することが出来ます。
前項の手順で作成したLIFF URL、https://liff.line.me/xxx
にアクセスすると以下のように表示されると思います。LIFFアプリを開発する
LIFF SDKを組み込む
LINE SDKはCDN経由でのみ配布されています。以下のscriptタグを
index.html
に追加します。index.html<script charset="utf-8" src="https://static.line-scdn.net/liff/edge/2/sdk.js"></script>この状態でLIFF SDKは使用可能な状態となりますが、せっかくTypeScriptで使用するので型の情報が欲しくなります。
幸運なことにliff-typeという素晴らしいパッケージが公開されているのでこちらを使用します。ドキュメントにある通り以下のコマンドでインストールし、
npm i -D liff-type
tsconfig.json
ファイルに以下の内容を追記します。
※追記内容は環境に応じて適宜読み替えてくださいtsconfig.json{ "compilerOptions": { "types": ["liff-type"] } }正しく設定が行えればインテリセンスが利いた快適なLIFFアプリ開発環境が作れると思います。
LIFFアプリを初期化する
LIFF SDKの機能を使用するためには、はじめに
liff.init()
を実行する必要があります。
作法として正しいのかは分かりませんが、今はアプリ全体でliffの機能を使用したいためApp.vue
のcreated
のタイミングで実行する処理を入れてみます。なお、
liff.init
の実行にはLIFF IDが必要となります。App.vue@Component export default class App extends Vue { @Prop({ type: Boolean, default: false }) loggedIn = false; created() { liff.init({ liffId: 'ここにLIFF IDを入れる' }) .then(() => { this.loggedIn = liff.isLoggedIn(); }) } }LIFFアプリが動作している環境を取得する
<template> <div class="liff-data"> <table> <tr> <th>OS</th> <td>{{ os }}</td> </tr> <tr> <th>Language</th> <td>{{ language }}</td> </tr> <tr> <th>LIFF SDK Version</th> <td>{{ sdkVersion }}</td> </tr> <tr> <th>LINE Version</th> <td>{{ lineVersion }}</td> </tr> <tr> <th>isInClient</th> <td>{{ isInClient }}</td> </tr> <tr> <th>isLoggedIn</th> <td>{{ isLoggedIn }}</td> </tr> </table> </div> </template> <script> import { Component, Vue } from "vue-property-decorator"; @Component export default class LiffData extends Vue { get os() { return liff.getOS(); } get language() { return liff.getLanguage(); } get lineVersion() { return liff.getLineVersion(); } get sdkVersion() { return liff.getVersion(); } get isInClient() { return liff.isInClient(); } get isLoggedIn() { return liff.isLoggedIn(); } } </script>ユーザのプロフィールを取得する
<template> <div class="profile"> <table> <tr> <th>IDトークンの生成URL</th> <td>{{ token.iss }}</td> </tr> <tr> <th>ユーザーID</th> <td>{{ token.sub }}</td> </tr> <tr> <th>チャネルID</th> <td>{{ token.aud }}</td> </tr> <tr> <th>トークンの有効期限</th> <td>{{ token.exp }}</td> </tr> <tr> <th>IDトークンの生成時間</th> <td>{{ token.iat }}</td> </tr> <tr> <th>ユーザー認証時間</th> <td>{{ token.auth_time }}</td> </tr> <tr> <th>nonce</th> <td>{{ token.nonce }}</td> </tr> <tr> <th>認証方法</th> <td>{{ token.amr }}</td> </tr> <tr> <th>表示名</th> <td>{{ token.name }}</td> </tr> <tr> <th>プロフィールの画像URL</th> <td>{{ token.picture }}</td> </tr> <tr> <th>メールアドレス</th> <td>{{ token.email }}</td> </tr> </table> </div> </template> <script> import { Component, Vue } from "vue-property-decorator"; @Component export default class Profile extends Vue { get token() { return liff.getDecodedIDToken(); } } </script>QRコードリーダを表示する
2020/07/01現在、QRコードリーダーの表示はiOS版LINEバージョン9.19.0以降では動作しないため、関数が存在するかを確認してから使用する必要があります。
参考:https://developers.line.biz/ja/docs/liff/developing-liff-apps/#opening-qr-code-reader
<template> <div class="qr"> {{ scanText }} <button v-on:click="qrScan"> <span>QRコード読み取り</span> </button> </div> </template> <script> import { Component, Vue } from "vue-property-decorator"; @Component export default class QR extends Vue { scanText = ""; qrScan() { if (liff.scanCode) { liff.scanCode().then(result => { const stringifiedResult = JSON.stringify(result); this.scanText = stringifiedResult; }); } } } </script>メッセージを送信する
2020/07/01現在、LIFFアプリからメッセージを送信する方法は現在のトークに送信する方法とターゲットピッカーを表示して、ユーザが選んだ相手に送信する方法の2種類存在します。
後者のターゲットを選択する方法はLINE 10.3.0以降でサポートされる機能のため、動作環境で使用可能かを確認してから送信します。<template> <div class="message"> <input v-model="message" /> <button v-on:click="sendMessage"> <span>送信</span> </button> </div> </template> <script> import { Component, Vue } from "vue-property-decorator"; @Component export default class Message extends Vue { message = 'LIFFへようこそ!'; sendMessage() { liff.sendMessages([{ 'type': 'text', 'text': this.message }]).then(function() { window.alert('Message sent'); }).catch(function(error) { window.alert('Error sending message: ' + error); }); } } </script>
- 投稿日:2020-07-01T17:54:44+09:00
AWS S3からファイルを取得したFlask APIがファイルをレスポンスとして返戻し、Vueでダウンロードする
はじめに
忙しいタイトルをしていて、大変申し訳ありません。
現在、以下のアプリ構成でお仕事をしています。
フロントエンド: Vue.js
バックエンド: Flask (python)Flaskの方については、これまで全く触ったことがなかったので、新鮮さを噛みしめつつ、日々触っています。
そもそもpythonの方も、ファイルを読み込んでcsvを吐き出してくれるというプチツールを作ったことがあるぐらいですので、多くのことを勉強させてもらっています。本記事で書くこと
タイトルそのままですが、以下を実現します。
1. Flask(Api)が、AWS SDK for Python (Boto3)を使って、AWS S3からファイルを取得する。
2. 取得したファイルデータをレスポンスに詰め、クライアント(Vue.js)に返却する。
3. クライアント(Vue.js)がレスポンスからファイルをダウンロードする。
Lambda + API GateWayで良いのでは・・・
こんなこともできるんだというぐらいの気持ちで読んでください。本記事で書かないこと
- Lambda + API GateWayで上記の流れを実現すること
- Flaskについてのあれこれ
- pythonについてのあれこれ
- aws関連についてのあれこれ
- Vue.jsについてあれこれ
- fetch APIのあれこれ
おそらく上記は色々な方が実践済みだと思うので。
なぜやろうと思ったのか
そもそも、今回の機能は amplify で実現する予定でした。
が、「Internet Explorer」の場合は、なぜか、aws credentialの認証情報がamplifyにわたってくれず、s3からファイルを取得しようしたときに、403エラーになるという状況に・・・
そのため、このように実装することにしました。
ですので、「Internet Explorer」を対象外とする場合は、素直にamplifyを使うことをオススメします・・・環境
- vue: 2.6.11
- boto3: 1.12.38
- Flask: 1.1.2
- python: 3系
事前準備
- awscli、boto3をインストールしておく
aws configure
で、awscliを使えるようにしておくそれでは内容へ
1. Flask(Api)が、AWS SDK for Python (Boto3)を使って、AWS S3からファイルを取得する。
s3からファイルを取得する場合に、以下二つの方法があります。
ローカルにダウンロードboto3.resource('s3').Bucket(BUCKET).download_file(Filename=KEY, Key=KEY)メモリ上にダウンロードboto3.client('s3').get_object(Bucket=bucket, Key=key)Flaskのローカル(アプリケーション上)にファイルがダウンロードされてしまうと、そのファイルを消したりしないといけないのが面倒なので、後者のメモリ上にダウンロードする方法を採用しました。
ソースコード
s3からファイルを取得する@app.route('/file', methods=['GET']) def get_file_from_s3(): s3 = boto3.client('s3') bucket = 'YOUR_BUCKET_NAME' key = 'YOUR_FILE_NAME' obj = s3.get_object(Bucket=bucket, Key=key)これで、
/file
のエンドポイントを叩くことで、s3からファイルオブジェクトを取得することができました。
このobj
の内、Body
のバイト配列を今回は利用します。2. 取得したファイルデータをレスポンスに詰め、クライアントに返却する。
それでは、取得したファイルオブジェクトからレスポンスをつくりましょう。
レスポンスはmake_response()でつくります。ソースコード
取得したファイルオブジェクトからレスポンスをつくって返却してあげる@app.route('/file', methods=['GET']) def get_file_from_s3(): s3 = boto3.client('s3') bucket = 'YOUR_BUCKET_NAME' key = 'YOUR_FILE_NAME' obj = s3.get_object(Bucket=bucket, Key=key) response = make_response() # レスポンスobjectのdataに取得したファイル情報を設定します。 # read()で、ファイルを読み込みます。 response.data = obj['Body'].read() # ダウンロード時のファイル名を定義します。 # quoted_filenameでURLエンコードします。また、日本語のファイル名でもできるように、UTF-8エンコーディングもします。 quoted_filename = urllib.parse.quote(name) response.headers['Content-Disposition'] = "attachment; filename='{}'; filename*=UTF-8''{}".format(quoted_filename, quoted_filename) return response3. クライアントがレスポンス情報をダウンロードする。
それでは、クライアント(Vue.js)でレスポンスのファイル情報をダウンロードしましょう。
今回はFetch APIを利用します。ソースコード
レスポンスからファイルをダウンロードするfetch('http://XXXXXX/file', { method: 'GET', }).then((response) = >{ // blob()で、Blob形式にします return response.blob(); }).then((blob) => { // IEの場合は、msSaveBlobを使います if (window.navigator.msSaveBlob) { window.navigator.msSaveBlob(blob, 'ファイル名.拡張子'); } else { const a = document.createElement('a'); a.download = 'ファイル名.拡張子'; a.href = URL.createObjectURL(blob); a.click(); } })これで、
ファイル名.拡張子
のファイルをダウンロードできます。
ファイル名.拡張子
をContent-Disposition
のfilenameとしたい場合は、
response.headers.get('Content-Disposition)でContent-Disposition
を取得できるので、そこから抽出して使うと良いと思います。参考にさせていただいた記事
Flaskでファイルダウンロードを実現する3つの方法
Python の boto3 で S3 とダウンロード/アップロードする
Djnagoメモ Content-Dispositionのfilenameに日本語をセットする
- 投稿日:2020-07-01T15:20:49+09:00
IndexedDB + Vue CLI, Chart.js で、測定値の表示 ファイルのインポート可
概要
前と同様、IndexedDB + Vue CLIで
Dexie.js ライブラリを使用した構成となり。
測定値の登録、chart.jsグラフ表示・ブラウザ内 IndexedDBデータの エクスポート、インポート機能で
jsonファイル経由で可能で、
別PCや、外出先PCのブラウザにインポートできます。構成
Chrome 83
Vue CLI
dexie : 3.0.1
vue: 2.6.11
vue-router
chart.js
SinglePageApplication / SPA
Progressive Web Apps / PWA
参考
https://nori-life.com/vue-cli-chart-js/
npm 追加
Vue CLIで、chart.jsだけでは、描画できず。
vue-chartjs ライブラリも。追加しました。npm install vue-chartjs chart.js --savepackage.json
https://github.com/kuc-arc-f/vue_spa3b_4mdats/blob/master/package.json
chart.js sample
・canvas が、動作しないようで
chart部分を、コンポートネントにする例で。描画できそうでした
親の呼出し側chart_sample.vue
https://github.com/kuc-arc-f/vue_spa3b_4mdats/blob/master/src/components/IndexMdats/chart_sample.vueimport ChartView from './ChartView' // export default { name: 'LineSample', data () { return { } }, components: { ChartView } }・子の chart
ChartView.vue
https://github.com/kuc-arc-f/vue_spa3b_4mdats/blob/master/src/components/IndexMdats/ChartView.vueimport { Line } from 'vue-chartjs' export default { extends: Line, mounted () { this.renderChart({ labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], datasets: [ { label: 'Data-1', fill: false, backgroundColor: '#FF6384', borderColor: '#FF6384', data: [40, 39, 10, 40, 39, 80, 40] } ] }, { responsive: true, maintainAspectRatio: false } ) } }画面
・リスト
上部分に、エクスポート、インポートの
ボタンを配置
Vue components
・create
https://github.com/kuc-arc-f/vue_spa3b_4mdats/blob/master/src/components/IndexMdats/new.vue・index
https://github.com/kuc-arc-f/vue_spa3b_4mdats/blob/master/src/components/IndexMdats/Index.vue・chart
https://github.com/kuc-arc-f/vue_spa3b_4mdats/blob/master/src/components/IndexMdats/chart.vue
参考のページ
https://knaka0209.hatenablog.com/entry/indexed_db_4mdats
IndexedDB + Dexie.js で CRUDの作成。ファイルインポート可、Vue CLI版
https://qiita.com/knakaqi/items/765a1fb37a53a26278e9
- 投稿日:2020-07-01T13:44:20+09:00
Docker で Rails6 + Webpacker + Vue.js + Vue Router + Vuex + axios環境を整える
はじめに
今回は、Docker上でRuby on Rails6の環境構築をし、さらにwebpackerを用いてVue.jsの環境構築をする方法を紹介したいと思います。私が以前書いた、Ruby on Rails6内で、Vue.js + Vuex + Vue Router + axios環境を整えるをDocker上で行ったものになります。
使用PCはMacを想定しています。Windowsの方は、一部コマンドを読み替えてください。今回の例では、親フォルダ名を
rails_vue_mysql_on_docker
としています。また、DBはMySQLを採用しています。Ruby on RailsとVue.jsの環境構築
ターミナルを開きましょう。rails_vue_mysql_on_dockerフォルダを作り、その中に
Dockerfile
、docker-compose.yml
、Gemfile
、Gemfile.lock
を作成していきます。ターミナル$ mkdir rails_vue_mysql_on_docker $ cd rails_vue_mysql_on_docker $ touch Dockerfile $ touch docker-compose.yml $ touch Gemfile $ touch Gemfile.lock
Dockerfile
に以下をコピペします。DockerfileFROM ruby:2.6.5 ENV LANG C.UTF-8 ENV TZ=Asia/Tokyo RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ && apt-get update -qq \ && apt-get install -y nodejs yarn ENV APP_HOME /var/src/app RUN mkdir -p $APP_HOME WORKDIR $APP_HOME ADD Gemfile $APP_HOME/Gemfile ADD Gemfile.lock $APP_HOME/Gemfile.lock ENV BUNDLE_DISABLE_SHARED_GEMS 1 RUN bundle install
docker-compose.yml
に以下をコピペします。docker-compose.ymlversion: "3" services: db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: password ports: - "3306:3306" command: --default-authentication-plugin=mysql_native_password volumes: - mysql-data:/var/lib/mysql app: &app_base build: context: . volumes: - .:/var/src/app ports: - "3000:3000" links: - db working_dir: /var/src/app command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" cap_add: - ALL # Add all privilege container_name: app tty: true stdin_open: true privileged: true logging: driver: "json-file" options: max-size: "100k" dns: - 8.8.8.8 webpack-dev-server: build: . command: /bin/sh -c "bin/webpack-dev-server --hot --inline" ports: - "3035:3035" environment: WEBPACKER_DEV_SERVER_HOST: 0.0.0.0 volumes: - .:/var/src/app tty: true stdin_open: true depends_on: - app volumes: mysql-data: driver: local
Gemfile
に以下をコピペします。Gemfile.lock
は空ファイルのままで大丈夫です。Gemfilesource 'https://rubygems.org' gem 'rails', '~> 6.0.3'準備が整ったので、rails new します。
$ docker-compose run --rm app bundle exec rails new . --force --database=mysql --skip-bundle処理が進んでいき、以下のメッセージが表示された頃には、Gemfileが更新されています。
Could not find gem 'mysql2 (>= 0.4.4)' in any of the gem sources listed in your Gemfile. Run `bundle install` to install missing gems.
Dockerfile
のAdd
はGemfile
&Gemfile.lock
のキャッシュから更新があったときのみ検知してbundle install
を実行します。そのため、このタイミングでdocker-compose build
をします。$ docker-compose buildイメージのビルドが終わったら、次に進みます。
rails newで作られた、database.ymlの一部を変更します。database.ymldefault: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password #変更 docker-compose.ymlに合わせる host: db #変更 docker-compose.ymlに合わせる続いて、Vueのインストールや、migrationなどをします。
ターミナル$ docker-compose run --rm app bundle exec rails webpacker:install $ docker-compose run --rm app bundle exec rails webpacker:install:vue $ docker-compose run --rm app bundle exec rails db:create db:migrate $ docker-compose run --rm app bundle exec rails g controller top top思いつきのタイミングではありますが、ここで邪魔なmarginとpaddingはリセットしておきましょう。
app/assets/stylesheets/application.css/*省略*/ * { margin: 0; padding: 0; }続いて、
top.html.erb
に以下を追記します。この記述のおかげで、top.html.erb
と、app.vue
が紐付きます。app/views/top/top.html.erb<%= javascript_pack_tag 'hello_vue' %> <!-- 追記 -->どのgetのリクエストが来ても、top#topにつながるようにしておきます。こうすることで、routingをVue Routerで行うことができるようになります。
routes.rbRails.application.routes.draw do get 'top/top' # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html root to: 'top#top' namespace :api do #API用のルーティングはここに書く end get '*path', to: 'top#top', format: false #getのリクエストをまとめる endさて、ここで動作確認をしてみましょう。以下のコマンドを入力して、localhostの3000番ポートにアクセスしてください。
$ docker-compose up
Vueの開発環境を整理
さて、ここからVueの環境を整えていきます。
axios
、vue-router
、vuex
などのインストールをし、その後、app/javascript
の直下にviewsフォルダ、componentsフォルダ、storeフォルダ、routesフォルダを作成します。ターミナル[rails_vue_mysql_on_docker] $ docker-compose run --rm app yarn install [rails_vue_mysql_on_docker] $ docker-compose run --rm app yarn add axios vue-axios vue-router vue-template-compiler vuex vue-eslint-parser [rails_vue_mysql_on_docker] $ cd app/javascript [javascript] $ mkdir views components store routes [javascript] $ ls // views components store routes が作成されたことを確認する。 app.vue channels components packs routes store views続いて、以下のファイルを指定のフォルダ内に作成します。(Home.vueと、About.vue、Header.vueは動作確認のために用意するので、環境構築後の開発で不要と判断したら消して頂いて大丈夫です。
viewsフォルダ内 → Home.vue と About.vue componentsフォルダ内 → Header.vue storeフォルダ内 → store.js //Vuexの設定ファイル routesフォルダ内 → router.js //vue-routerの設定ファイルすなわち、以下のコマンドを打ちます。
ターミナル[javascript] $ cd views [views] $ touch Home.vue About.vue [views] $ cd ../components [components] $ touch Header.vue [components] $ cd ../store [store] $ touch store.js [store] $ cd ../routes [routes] $ touch router.js必要なファイルが揃いました!続いて、ファイルを編集していきます。
まずはapp.vue
を以下のように編集します。ルートとなるvueファイルです。このファイルはデフォルトで作られています。app.vue<template> <div id="app"> <Header></Header> <router-view></router-view> </div> </template> <script> import Header from "./components/Header.vue"; export default { components: { Header, }, data: function() { return { message: "Hello Vue!", }; }, }; </script> <style scoped> </style>viewsフォルダ内の
Home.vue
とAbout.vue
を編集しましょう。Home.vue<template> <section id="home"> <h1>{{ title }}</h1> </section> </template> <script> export default { data() { return { title: "Homeです" }; } }; </script> <style lang="scss" scoped> </style>About.vue<template> <section id="about"> <h1>{{ title }}</h1> </section> </template> <script> export default { data() { return { title: "Aboutです" }; } }; </script> <style lang="scss" scoped> </style>componentsフォルダ内の
Header.vue
を編集しましょう。Header.vue<template> <header> <span>{{ message }}</span> <router-link to="/">Home</router-link> <router-link to="/about">About</router-link> </header> </template> <script> export default { data() { return { message: "Headerです", }; }, }; </script> <style lang="scss" scoped> $background-color: skyblue; header { width: 100%; height: 80px; background-color: $background-color; display: flex; flex-direction: row; align-items: center; a { display: inline-block; margin: 0 20px; } } </style>Vuexの設定をしておきます。
store/store.jsimport Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store = new Vuex.Store({ state: {}, getters: {}, mutations: {}, actions: {}, }); export default store;Vue Routerの設定をしておきます。
router/router.jsimport Vue from "vue"; import Router from "vue-router"; Vue.use(Router); import Home from "../views/Home.vue"; import About from "../views/About.vue"; const router = new Router({ mode: "history", routes: [ //ルーティングの設定 { path: "/", component: Home, }, { path: "/about", component: About, }, ], }); export default router;最後に
hello_vue.js
を編集します。hello_vue.js
はVueCLIでいう、src/main.js
です。packs/hello_vue.jsimport Vue from "vue"; import Vuex from "vuex" import VueRouter from "vue-router"; import store from "../store/store.js"; import router from "../routes/router.js"; import App from "../app.vue"; import axios from "axios"; import VueAxios from "vue-axios"; Vue.use(Vuex); Vue.use(VueRouter); Vue.use(VueAxios, axios); document.addEventListener("DOMContentLoaded", () => { const app = new Vue({ store, router, render: (h) => h(App), }).$mount(); document.body.appendChild(app.$el); });完成!!
以上でRails on Dockerプロジェクト内でVue.js、及びVuex、Vue Router、Axiosを利用するための設定が完了しました!
サーバーを起動してみましょう。docker-compose.ymlで、rails s
とbin/webpack-dev-server
が同時に起動するように、設定してあるので、以下のコマンドだけで、十分です。foremanを使用しません。[rails_vue_mysql_on_docker]$ docker-compose upお疲れ様でした!
- 投稿日:2020-07-01T13:44:20+09:00
Docker上 で Rails6 + Webpacker + Vue.js + Vue Router + Vuex + axios環境を整える
はじめに
今回は、Docker上でRuby on Rails6の環境構築をし、さらにwebpackerを用いてVue.jsの環境構築をする方法を紹介したいと思います。私が以前書いた、Ruby on Rails6内で、Vue.js + Vuex + Vue Router + axios環境を整えるをDocker上で行ったものになります。
使用PCはMacを想定しています。Windowsの方は、一部コマンドを読み替えてください。今回の例では、親フォルダ名を
rails_vue_mysql_on_docker
としています。また、DBはMySQLを採用しています。Ruby on RailsとVue.jsの環境構築
ターミナルを開きましょう。rails_vue_mysql_on_dockerフォルダを作り、その中に
Dockerfile
、docker-compose.yml
、Gemfile
、Gemfile.lock
を作成していきます。ターミナル$ mkdir rails_vue_mysql_on_docker $ cd rails_vue_mysql_on_docker $ touch Dockerfile docker-compose.yml Gemfile Gemfile.lock
Dockerfile
に以下をコピペします。DockerfileFROM ruby:2.6.5 ENV LANG C.UTF-8 ENV TZ=Asia/Tokyo RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ && apt-get update -qq \ && apt-get install -y nodejs yarn ENV APP_HOME /var/src/app RUN mkdir -p $APP_HOME WORKDIR $APP_HOME ADD Gemfile $APP_HOME/Gemfile ADD Gemfile.lock $APP_HOME/Gemfile.lock ENV BUNDLE_DISABLE_SHARED_GEMS 1 RUN bundle install
docker-compose.yml
に以下をコピペします。docker-compose.ymlversion: "3" services: db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: password ports: - "3306:3306" command: --default-authentication-plugin=mysql_native_password volumes: - mysql-data:/var/lib/mysql app: &app_base build: context: . volumes: - .:/var/src/app ports: - "3000:3000" links: - db working_dir: /var/src/app command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" cap_add: - ALL # Add all privilege container_name: app tty: true stdin_open: true privileged: true logging: driver: "json-file" options: max-size: "100k" dns: - 8.8.8.8 webpack-dev-server: build: . command: /bin/sh -c "bin/webpack-dev-server --hot --inline" ports: - "3035:3035" environment: WEBPACKER_DEV_SERVER_HOST: 0.0.0.0 volumes: - .:/var/src/app tty: true stdin_open: true depends_on: - app volumes: mysql-data: driver: local
Gemfile
に以下をコピペします。Gemfile.lock
は空ファイルのままで大丈夫です。Gemfilesource 'https://rubygems.org' gem 'rails', '~> 6.0.3'準備が整ったので、rails new します。
$ docker-compose run --rm app bundle exec rails new . --force --database=mysql --skip-bundle処理が進んでいき、以下のメッセージが表示された頃には、Gemfileが更新されています。
Could not find gem 'mysql2 (>= 0.4.4)' in any of the gem sources listed in your Gemfile. Run `bundle install` to install missing gems.
Dockerfile
のAdd
はGemfile
&Gemfile.lock
のキャッシュから更新があったときのみ検知してbundle install
を実行します。そのため、このタイミングでdocker-compose build
をします。$ docker-compose buildイメージのビルドが終わったら、次に進みます。
rails newで作られた、database.ymlの一部を変更します。database.ymldefault: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password #変更 docker-compose.ymlに合わせる host: db #変更 docker-compose.ymlに合わせる続いて、Vueのインストールや、migrationなどをします。
ターミナル$ docker-compose run --rm app bundle exec rails webpacker:install $ docker-compose run --rm app bundle exec rails webpacker:install:vue $ docker-compose run --rm app bundle exec rails db:create db:migrate $ docker-compose run --rm app bundle exec rails g controller top top思いつきのタイミングではありますが、ここで邪魔なmarginとpaddingはリセットしておきましょう。
app/assets/stylesheets/application.css/*省略*/ * { margin: 0; padding: 0; }続いて、
top.html.erb
に以下を追記します。この記述のおかげで、top.html.erb
と、app.vue
が紐付きます。app/views/top/top.html.erb<%= javascript_pack_tag 'hello_vue' %> <!-- 追記 -->どのgetのリクエストが来ても、top#topにつながるようにしておきます。こうすることで、routingをVue Routerで行うことができるようになります。
routes.rbRails.application.routes.draw do get 'top/top' # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html root to: 'top#top' namespace :api do #API用のルーティングはここに書く end get '*path', to: 'top#top', format: false #getのリクエストをまとめる endさて、ここで動作確認をしてみましょう。以下のコマンドを入力して、localhostの3000番ポートにアクセスしてください。
※初回のコンパイルは平常時より若干時間がかかることがあります$ docker-compose up
次のステップに行く前に、Control + C もしくは
$ docker-compose stop
コマンドを使って、コンテナを停止させておきましょう。
コマンドについて詳しくはこちらをご覧ください。Vueの開発環境を整理
さて、ここからVueの環境を整えていきます。
axios
、vue-router
、vuex
などのインストールをし、その後、app/javascript
の直下にviewsフォルダ、componentsフォルダ、storeフォルダ、routesフォルダを作成します。ターミナル[rails_vue_mysql_on_docker] $ docker-compose run --rm app yarn install [rails_vue_mysql_on_docker] $ docker-compose run --rm app yarn add axios vue-axios vue-router vue-template-compiler vuex vue-eslint-parser [rails_vue_mysql_on_docker] $ cd app/javascript [javascript] $ mkdir views components store routes [javascript] $ ls // views components store routes が作成されたことを確認する。 app.vue channels components packs routes store views続いて、以下のファイルを指定のフォルダ内に作成します。(Home.vueと、About.vue、Header.vueは動作確認のために用意するので、環境構築後の開発で不要と判断したら消して頂いて大丈夫です。
viewsフォルダ内 → Home.vue と About.vue componentsフォルダ内 → Header.vue storeフォルダ内 → store.js //Vuexの設定ファイル routesフォルダ内 → router.js //vue-routerの設定ファイルすなわち、以下のコマンドを打ちます。
ターミナル[javascript] $ cd views [views] $ touch Home.vue About.vue [views] $ cd ../components [components] $ touch Header.vue [components] $ cd ../store [store] $ touch store.js [store] $ cd ../routes [routes] $ touch router.js必要なファイルが揃いました!続いて、ファイルを編集していきます。
まずはapp.vue
を以下のように編集します。ルートとなるvueファイルです。このファイルはデフォルトで作られています。app.vue<template> <div id="app"> <Header></Header> <router-view></router-view> </div> </template> <script> import Header from "./components/Header.vue"; export default { components: { Header, }, data: function() { return { message: "Hello Vue!", }; }, }; </script> <style scoped> </style>viewsフォルダ内の
Home.vue
とAbout.vue
を編集しましょう。Home.vue<template> <section id="home"> <h1>{{ title }}</h1> </section> </template> <script> export default { data() { return { title: "Homeです" }; } }; </script> <style lang="scss" scoped> </style>About.vue<template> <section id="about"> <h1>{{ title }}</h1> </section> </template> <script> export default { data() { return { title: "Aboutです" }; } }; </script> <style lang="scss" scoped> </style>componentsフォルダ内の
Header.vue
を編集しましょう。Header.vue<template> <header> <span>{{ message }}</span> <router-link to="/">Home</router-link> <router-link to="/about">About</router-link> </header> </template> <script> export default { data() { return { message: "Headerです", }; }, }; </script> <style lang="scss" scoped> $background-color: skyblue; header { width: 100%; height: 80px; background-color: $background-color; display: flex; flex-direction: row; align-items: center; a { display: inline-block; margin: 0 20px; } } </style>Vuexの設定をしておきます。
store/store.jsimport Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store = new Vuex.Store({ state: {}, getters: {}, mutations: {}, actions: {}, }); export default store;Vue Routerの設定をしておきます。
router/router.jsimport Vue from "vue"; import Router from "vue-router"; Vue.use(Router); import Home from "../views/Home.vue"; import About from "../views/About.vue"; const router = new Router({ mode: "history", routes: [ //ルーティングの設定 { path: "/", component: Home, }, { path: "/about", component: About, }, ], }); export default router;最後に
hello_vue.js
を編集します。hello_vue.js
はVueCLIでいう、src/main.js
です。packs/hello_vue.jsimport Vue from "vue"; import Vuex from "vuex" import VueRouter from "vue-router"; import store from "../store/store.js"; import router from "../routes/router.js"; import App from "../app.vue"; import axios from "axios"; import VueAxios from "vue-axios"; Vue.use(Vuex); Vue.use(VueRouter); Vue.use(VueAxios, axios); document.addEventListener("DOMContentLoaded", () => { const app = new Vue({ store, router, render: (h) => h(App), }).$mount(); document.body.appendChild(app.$el); });完成!!
以上でRails on Dockerプロジェクト内でVue.js、及びVuex、Vue Router、Axiosを利用するための設定が完了しました!
サーバーを起動してみましょう。docker-compose.ymlで、rails s
とbin/webpack-dev-server
が同時に起動するように、設定してあるので、以下のコマンドだけで、十分です。foremanを使用しません。[rails_vue_mysql_on_docker]$ docker-compose upお疲れ様でした!
- 投稿日:2020-07-01T10:48:23+09:00
Vuetifyで作った画面のテストをCypressで自動化しようとした時に調べたことや、つまづいたこと
はじめに
Vue.js、Vuetifyで作った画面をCypressでどうにか自動化したいと、いろいろ触って調査して気づいた点を書いていこうと思います。
Cypressについて
Cypressは、E2Eテスティングフレームワークです。
Cypress導入手順
yarn install
yarn install cypress --save-devtestsフォルダにe2eフォルダを配置
tests/e2e/fixtures
tests/e2e/plugins
tests/e2e/screenshots
tests/e2e/specs
tests/e2e/support
tests/e2e/videos
plugins/index.js
各フォルダ設定
module.exports = (on, config) => { return Object.assign({}, config, { fixturesFolder: 'tests/e2e/fixtures', integrationFolder: 'tests/e2e/specs', screenshotsFolder: 'tests/e2e/screenshots', videosFolder: 'tests/e2e/videos', supportFile: 'tests/e2e/support/index.js' }) }support/index.js
Cypressのカスタムコマンドを追記するファイルの指定
import './commands'サンプルテストコード
tests/e2e/spec/
フォルダ配下に、テストファイルを書く。
- 表記ルールは特に無い。日本語でもOK(例:検索画面test.js)
describe.skip('検索機能のテスト', () => { // テストケース(it関数)の直前で実行される beforeEach(() => { // Cypressコマンドのタイムアウト値を60秒に設定。(デフォルトは4秒) Cypress.config('defaultCommandTimeout', 60000); // 画面のサイズ指定 cy.viewport(1940, 1080); // テスト開始時、どのURLにアクセスするかを指定 cy.visit('http://localhost:8080'); // ログインをするための、カスタムコマンド(後述) cy.login('user'); }); // テストケース(it関数)の直後で実行される afterEach( () => { cy.contains('ログアウト') .click(); }); // テストケース it('検索ができること', () => { // 目的の画面へ移動するためのカスタムコマンド(後述) cy.gotoTestPage(); cy.wait(2000); cy.contains('検索') .click(); }); });テスト実行
画面を見ながらのテスト
yarn run cypress open以下のCypressの画面が開くので、テスト実行したいファイルをクリックします。
実行すると、このような画面が開く、テストコードの通りに実行が走ります。
Cypressの公式ページ
も参考にして下さい。コマンドライン上でのテスト
headless
オプションを付けることにより、コマンドライン上で実行が可能です。
shell script
yarn run cypress run --browser chrome --headless
- テストが途中で実行終わると、失敗時のスクリーンショットが
e2e/screenshots/
に保存されます- テスト実行の動作は、
e2e/videos/
にmp4が保存されますカスタムコマンド作成
- AppActionsという、Cypressに独自のコマンド(メソッド)を作成することができる機能。
- これにより以下の効果が期待できる
- 内部の複雑なDOM操作などを抽象化して、テストコード自体を見やすくする
- 特定のDOM操作を抽象化することで、idやinnerTextなどに変更があっても、カスタムコマンド内を修正するだけでよくなる(=保守性向上)
以下の記事も参考にしてください
サンプルコード
e2e/support/commands.js
に以下の様に記載することにより、
テストコード内で、cy.login()
cy.gotoTestPage()
と使用することができる。Cypress.Commands.add('login', username => { cy.get('[name=USER_ID]') .type(username); cy.get('[data-test=login]') .click(); }); Cypress.Commands.add('gotoTestPage',() => { cy.get('.navbar > div > .btn') .click(); cy.contains('テストページ') .click(); });Cypressのテストコードでの注意点とかテクニック
v-selectで
select()
が動作しないtype を使わないと駄目っぽい。
内部的にはselectタグでは無くて、別のもので動いているから?
<v-select :data-test='test' :items="selectItems" item-text="name" item-value="val" dense outlined />cy.get('[data-test=test]').type('item01{enter}', {force: true})ここの
{enter}
は特殊文字で、Enterを入力する操作扱いとなる。参考:type | Cypress Documentation
v-text-field で値を探す時
v-text-fieldは、HTMLにレンダリング後、labelとinputタグが並んで作られる
containsでlabelを探した後に、兄弟要素を探す next、prev でinputタグを取得できる
cy.contains('label', '取引先コード') .next('input') .type('001');面倒だったら、
data-*
属性を付与して get でもいける。Cypress公式のベストプラクティスでは、
data-*
を付与してテストするほうが、HTML、CSSに依存しないので良いとしている。Best Practices | Cypress Documentation
ただし、要素内のテキストに変更があった場合にテストが失敗して欲しい時は、
cy.contains
を使うべきだとも言っている。
- 送信ボタン → 保存ボタン に変わった場合
- 振る舞い自体が変わることになるので、テスト失敗して欲しい →
contains
- 失敗しないで欲しい →
cy.get([data-cy=hogehoge])
このあたりはどうするかは、プロジェクトの方針にもよるかもしれません。
ちなみにまだ試してませんが、テストだけに使う
data-test
などの属性をプロダクション環境時に削除する方法はあるそう。vue-loader 15で、テンプレート内の任意の属性(data-testなど)を除外する - Qiita
v-checkbox でチェックを入れたり外したりする時は、force: trueオプション必要
check、uncheck を使えば良いと書いてあるが、普通にやってもうまくいかない。
forceオプションを入れると、うまくいく。cy.get('[data-test=hoge]') .check({force: true,}); cy.get('[data-test=hoge2]') .uncheck({force: true,}); }途中でテストがこけると、それ以降のテストはスキップされる
- デフォルトの動作がそういうものらしい
タイムアウト値の設定を延ばす
Cypressのコマンドのタイムアウト値は、デフォルト4000msです。
SPAで取得したい要素がロードされる前にタイムアウトする場合は、個別にタイムアウト値を設定することができます。
cy.get('button', { timeout: 30000 })それすらも面倒だなと思った場合は、
defaultCommandTimeout
を延ばすことができます。beforeEach(() => { Cypress.config('defaultCommandTimeout', 60000); });参考
- Configuration | Cypress Documentation
- javascript - How to Wait until page is fully loaded - Stack Overflow
ボタンのラベルの前後に空白が入っている場合の対処
これはCypressというより、Vue、Vuetifyの仕様の話だと思うけど、
ダイアログの登録ボタンをクリックしたいということで、
cy.contains('登録').click()'
を書いたけど、何回も失敗してた。consoleを確認すると、親ページの方で「登録確認」ボタンがあったので、そちらを取得してしまっていたのが原因だった。
containsは、指定テキストにマッチした一番最初の要素を取得するので、登録ボタンをピンポイントに取得する必要があったので、正規表現を使うことにした。
cy.contains(/^登録$/).click()'
だけど、これだと何も取得できなくてエラーとなる。
そんなわけないだろうと思って、検証ツール上で生成されたHTMLのtextを確認すると、
登録なぜか前後に余計な空白が…。
cy.contains(/^ +登録 +$/).click()'
そういうものだと諦め、前後スペース有りの正規表現でなんとか取得できた。
テーブルに出てくる検索結果にアサーションを掛けたい場合
検索画面などで、検索ボタンを推してからテーブルに表示される結果にアサーションをしたい、なんてことがあると思います。
contains、parent、withinの組み合わせでなんとかできます。
取引先コード002で検索したら、検索結果にちゃんと出てきて、
テスト株式会社
という文字列が含まれているかどうかを確認したい場合、以下のようなコードでアサーションができます。// 取引先コード002の要素(td)の、親要素の行(tr)を取得し、withinでそのtr要素内でアサーションします。 cy.contains('002').parent('tr').within(()=> { cy.get('td').contains('テスト株式会社').should('be.visible'); });
should('be.visible')
は取得した要素が表示されているかどうかをアサーションするコマンドです。検索結果の任意の列の結果を見たいときもあると思います。
6列目の値が2020-07-01
が入っていることを期待する場合は、以下のような感じになります。cy.contains('002').parent('tr').within(()=> { cy.get('td').eq(5).should('eq', '2020-07-01'); });eqコマンドが、複数要素がある場合の何個目を取得する、といった形になります。
0から始まるので、配列とほぼ一緒と考えて良いと思います。
参考
余談ですが、単体のコマンドの説明だけではなく、こういったやりたいことが公式ドキュメントに整備されているのが、とても好感が持てます。
うまく要素が取得できないな? と思ったら
Cypressを使っていると、各コマンドが充実しており結構使いやすく感じます。
しかし、そもそもE2Eテストのコードを書いた経験が無いとか、HTMLとかにそこそこ詳しくないと、要素の取得とかで普通にハマります。(フロントエンド得意系の人だともっと早く解決できるのかもしれません)
そうなったときによくやるのが、
cypress open
でブラウザ表示した状態でテストし、Consoleで結果を確認していきます。
cy.get('button')
と書いた場合、全てのテストが終わるか、もしくはコマンド終わった辺りでテストをStopし、ブラウザでコマンドが実行されたところをクリック検証ツールのConsoleから、どんな要素が取得できているかを確認できます。
そもそもこの時に何も取得できていなかったり、取得したい要素が無かったりしたら、指定が間違っている可能性があります。
おわりに
癖みたいなものはありますけど、基本的に使い勝手は良いと感じています。
v-selectやv-text-fieldの要素取得のあれこれは、カスタムコマンドにまとめてしまえば他の人も扱いやすくなると思うので、本格的に導入を始めたらやっていければいいかなと思っています。参考記事
- 投稿日:2020-07-01T03:19:36+09:00
GithubActionsでVueアプリをS3にデプロイしてみた
はじめに
前回まででGithubActiosでBeanstalkやEC2の自動デプロイを行えるようにしてきました。
今回は、VueCLIで作ったフロントエンドアプリのデプロイをGithubActionsで自動化してみます。具体的には
- S3にデプロイ
- CloudFrontのキャッシュクリア(invalidation)
の作業を自動化します。
CloudFrontはACMを紐付け、S3のリソースをHTTPSで外部にホストするために利用しています。なお、VueCLIやS3の初期セットアップ方法についてはここでは触れません。
既にVueアプリをS3で運用していて、GithubActionsでデプロイを自動化したい人向けの内容になります。(Vueに限らずnpm run build
でビルドできるReactなども同様です)前提
- S3でVueアプリをホストしている
- Githubアカウントがある
開発環境
macOS Catalina
node v12.10.0
npm 6.10.3大まかな作業の流れ
- GithubActionsのworkflowを作成する
- Secretsに必要なキー、値を登録する
- pushして自動デプロイを確認
GithubActionsのworkflowを作成する
今回作成する workflow のStepsの概要は以下の通りです。
- Vueアプリケーションをビルドする
- ビルドされたファイル群をS3にデプロイ(Sync)する
- CloudFrontのキャッシュをクリア(invalidationを作成)する
実際に作成したファイルは以下になります。
(作成手順は後に説明します)deploy-s3.ymlname: deploy to S3 on: push: branches: [ master ] jobs: build: name: build and deploy to s3 runs-on: ubuntu-latest strategy: matrix: node-version: [12.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm ci - run: npm run build --if-present - run: npm test - name: s3 sync uses: jakejarvis/s3-sync-action@master with: args: --acl public-read --follow-symlinks --delete env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: 'ap-northeast-1' AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} SOURCE_DIR: 'dist' - name: invalidate cloudfront uses: chetan/invalidate-cloudfront-action@master env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: 'ap-northeast-1' DISTRIBUTION: ${{ secrets.AWS_CF_DISTRIBUTION }} PATHS: '/*'作成手順は以下になります。
雛形を使ってworkflowファイルを作成
workflow は
- YAML形式で記述する(ファイル名は任意)
.github/workflows
ディレクトリ内に置く(複数配置可)ことで実行できるようになります。
手で作成してもよいですが、ブラウザ上からテンプレートを使って作ると間違いもなく簡単です。今回は最初にVueアプリのビルドを行いたいので、
Node.js
の雛形を利用してみます。
(VueプロジェクトがGithubにPushされていない場合はPushしてRepositoryに登録しておきます)
npm run build
を含むテンプレートが表示されるのでStart commit
をクリックしてコミットコメントを記入してCommit new file
をクリックします。
localの開発環境で先ほどコミットした workflow ファイルを
master
からpull
して取得します。これで雛形となる workflow ファイルを作成できました。
以降は local で workflow ファイルを修正していきます。Node.jsの雛形をカスタマイズする
Node.js
の雛形は、複数のNodeバージョンでのビルド&テストを実施する workflow のため
Pull request
もトリガとなっている- npmのビルドが
v12.x
だけでなく、10.x
14.x
も実行されてしまうという点が余計のため、これらを削除して以下のようにしました。
deploy-s3.ymlname: deploy to S3 on: push: branches: [ master ] jobs: build: name: build and deploy to s3 runs-on: ubuntu-latest strategy: matrix: node-version: [12.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm ci - run: npm run build --if-present - run: npm testこれで
master
にPushされた時にnode v12.x
でのクリーンインストール&ビルド&テストのみが動きます。
このビルドでプロジェクト直下のdist
というディレクトリ内に、ビルドされたファイル群が出力されます。S3にデプロイするStepを追記する
S3へのデプロイは marketplace にサードパーティ製の Action があったのでそれを利用します。
https://github.com/marketplace/actions/s3-sync
ここの例に従って引数やパラメータを指定したものが以下になります。
- name: s3 sync uses: jakejarvis/s3-sync-action@master with: args: --acl public-read --follow-symlinks --delete env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: 'ap-northeast-1' AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} SOURCE_DIR: 'dist'
env
でよく使うものを以下に抜粋したので参考にしてください。
キー 設定する値 必須 デフォルト値 AWS_ACCESS_KEY_ID IAMのアクセスキーID(取得方法は後述の補足2参照) Y - AWS_SECRET_ACCESS_KEY IAMのシークレットアクセスキー( 〃 ) Y - AWS_S3_BUCKET S3のバケット名 Y - AWS_REGION S3のリージョン名 N us-east-1 SOURCE_DIR S3にアップロードするディレクトリ N ./ (リポジトリのルート) DEST_DIR アップロード先のS3のディレクトリ N / (S3のバケットのルート) 補足1:セキュアな情報は
Secrets
に登録して参照する値の指定で
${{ secrets.〜 }}
とある記述は、RepositoryのSecrets
に登録した値を参照しています。IAMのアクセスキーなどのセキュアな情報は workflow ファイル内にベタ書きしないようにします。
(Secretsの登録手順は後述します)補足2:IAMのアクセスキーIDとシークレットアクセスキーの取得方法
IAMのアクセスキーIDとシークレットアクセスキーは、AWSのコンソールにIAMでログイン後、ツールバーのアカウント名のメニューから
マイセキュリティ資格情報
>アクセスキー
>新しいアクセスキーの作成
から生成することができます。
*これはAWS CLIなどを使ってAWSのリソースを操作する際に使うセキュアな情報です。
*シークレットアクセスキーは、初回に作成時しか表示されないので注意してメモります。CloudFrontのキャッシュをクリアするStepを追記する
*CloudFrontを使ってない場合はこのStepは省略してよいです。
S3の前にEdgeサーバーとしてCloudFrontを使っている場合は、キャッシュをクリアしないと更新したS3のコンテンツがしばらく配信されません。
いつもは手動で行なっていたキャッシュクリア(invalidation)も自動化します。例によってmarcketplaceにサードパーティ製の Action があったのでそれを利用します。
https://github.com/marketplace/actions/invalidate-cloudfront
ここの例に従って引数やパラメータを指定したものが以下になります。- name: invalidate cloudfront uses: chetan/invalidate-cloudfront-action@master env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: 'ap-northeast-1' DISTRIBUTION: ${{ secrets.AWS_CF_DISTRIBUTION }} PATHS: '/*'
env
で指定しているキーは以下を参考にしてください。
キー 設定する値 必須 AWS_ACCESS_KEY_ID IAMのアクセスキーID(先述と同じ) Y AWS_SECRET_ACCESS_KEY IAMのシークレットアクセスキー( 〃 ) Y AWS_REGION リージョン名 Y DISTRIBUTION 対象のCloudFrontディストリビューションID Y PATHS キャッシュクリアする1つ以上のパスのリスト(スペース区切りで複数指定可) Y 以上で、冒頭の workflow ファイルの作成が完了しました。
Secretsに必要なキー、値を登録する
workflow 内のパラメータの指定で
${{ secrets.〜 }}
とある記述は、RepositoryのSettings
>Secrets
に登録した値を参照しています。
IAMのアクセスキーなどのセキュアな情報は workflow ファイル内にベタ書きせず、ここに登録して参照するようにします。
Secrets
の値は、仮にecho
などで出力してもマスキングされるので安全です。pushして自動デプロイを確認
Vueアプリのリソースの一部に変更を加えて
master
にCommit&Pushします。
その後、GithubのActions
タブで自動デプロイの状況を確認してみます。
このように、オールグリーンなら workflow は成功です!
実際にブラウザからも正常に変更が反映されていることが確認できるはずです。あとがき
S3へのリソースのアップロード(Sync)、その後のCloudFrontのキャッシュクリアという地味に面倒な作業から開放されました!!
AWSを対象としたCI/CDの工程で必要なStepは、大抵公式かmarketplaceでActionが公開されているので、簡単に workflow が作れるのも手軽でよいですね。