- 投稿日:2019-05-22T23:41:22+09:00
光の速さでデプロイ ~ Vue + Firebase hosting ことはじめ ~
概要
こんにちは、都内でフロントエンドエンジニアをやっていますかめぽんです。最近は、Next.js + Firebaseで個人プロダクトを作っているんですが、Firebaseが便利すぎてお米が3杯食べられるくらいに感動しています。
何と言っても、スピード感が出せるのは非常に大きいなと感じています。特に、デプロイまでは本当にあっという間で、ものの数分でデプロイができます。
今回、Vue-cliとfirebase hostingを使ってプロジェクトの立ち上げからデプロイまでを、ササッと体験してもらえればと思います。ターゲット
- 光の速さでデプロイしたい人
- Firebase実は使ってない人
- Vue好きな人
メリット
- SPAを光の速さでデプロイ出来る
- firebase hostingが出来るようになる
- Vue-cli触れる
本題
開発の流れ
今回の開発の流れは以下のように進めていきます。
事前に、firebase-toolsとVue-cliをグローバルインストールしておいてください。
- vue-cliでプロジェクトの立ち上げ
- firebaseプロジェクトの作成
- プロジェクト内でfirebaseの設定
- デプロイ
vue-cliでプロジェクトの立ち上げ
なにはともあれ、Vue-cliでプロジェクトを立ち上げましょう!以下コマンドにて、プロジェクトが一撃で作れます。
vue create [プロジェクト名]すると、色々質問されると思うのですが任意で進めてみてください。
今回はManually select featuresを選択し、好きな設定でカスタマイズしていきます。
スペースキーで選択・非選択、エンターで決定できます。
設定はまだまだ続きます、テストやLinterの設定などをお好みで選択していってください。
最後までいき、以下のような画面が出たらプロジェクト作成完了です!
firebaseプロジェクトの作成
次にFirebaseの設定です。https://console.firebase.google.com/u/0/ こちらのサイトにアクセスしてみてください。
すると以下のような画面が出るので、赤枠の部分を押してFirebaseプロジェクトを新規作成してみてください。
プロジェクト内でfirebaseの設定
はじめに、プロジェクト内でfirebaseを使えるようにするために以下コマンドを売ってみてください。(事前にfirebase-toolsをグローバルインストールしておいてください)
firebase initすると、こんな感じでターミナルが燃えていると思います。firebaseがもついろんな機能がありますが、今回は
Hostingを選んでください。
そしたら、あらかじめ作っておいたプロジェクトが表示されるのでそれを選んでください。
そして、firebaseの設定に関する質問を進めていくと以下のような画面が出るので、ここまでくればfirebaseプロジェクトとVue-cliで作ったプロジェクトをつなぎこむことが出来ました。
次に、Vue-cli側でアプリのデプロイをします。その前に
firebase.jsonの設定を少し変えます。Vue-cliのビルドしたファイルとfirebaseで読むときの設定を変えにいきます。npm run buildでVueの資材を吐き出せるのですが、その吐き出したファイルをホスティングするためにその対象ディレクトリを指定します。{ "hosting": { "public": "dist", //publicからdistへ "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ] } }設定は以上になります!
デプロイ
そして、ここからデプロイに移っていくわけなんですが、その前にVue-cliの資材をビルドします。以下コマンドを打てみてください。
npm run buildすると、
dist配下にファイルが流れ込んできたのがわかると思います。これらが、ホスティングする対象ファイルたちです。次に、以下魔法のコマンドを打つとホスティングされます。
firebase deployこの画面が出たらデプロイが完了です!なんて簡単なんでしょうか。
そして、ホスティングのURLにアクセスしてみて、吐き出した画面が表示されれば成功です!
まとめ
いかがだったでしょうか?非常に簡単でスピーディにデプロイが出来たと思います。実案件でも良いですが、爆速でプロトタイプを作りたいときなどにも非常におすすめなのでジャンジャン使っていきましょう!それでは!
- 投稿日:2019-05-22T22:57:13+09:00
【デモ環境あり】GASを使って掲示板を作ってみた話
はじめに
今回もGsuite(google)のサービスのみで掲示板アプリを作ってみました。
これを作ろうと思ったきっかけとしては、
Gsuiteのサービス一覧に組織全体に知らせるための掲示板のようなアプリケーションってないなー。
ないなら勉強がてら作ってみるかーと思ったのがきっかけです。イメージフロー
①:掲示板に新規に投稿したり、投稿している掲示板に対して返信を実行
②:返信の投稿の際に返信元の投稿者にメールを送信
③:掲示板の投稿を確認を行います。記事を閲覧したユーザは自動で既読情報を更新
デモ環境
今回、自分の個人googleアカウントでデモ環境を作って公開しようと思います。
デモなので本来のアプリケーションと違う部分や制限機能を設けています。名前や、記事に内容は全てサンプル用に用意したものになります。
アプリケーションのアクセス方法は匿名アクセスにしています。デモ環境はこちら
アプリケーション解説
これからはアプリケーションの解説を行なっていきます。
スプレッドシート
使用しているスプレッドシートは2つです。
- 投稿内容保持するスプレッドシート(データ)
- シートIDやファルダIDを保持するスプレッドシート(マスタ)
データプレッドシート
id 親id 削除フラグ タイムスタンプ ... 新規投稿内容も返信内容も一つのスプレッドシートで保持しています。
「親id」の部分で親子関係を管理しています。マスタスプレッドシート
UPDATA_FLG
データスプレッドシートのデータがスプレッドシートのセル制限を超えないかを時間のトリガー処理で行なっています。
もし、セル制限を超えそうな場合は、スプレッドシートのデータを直近投稿10件以外を別のシートに退避させます。そのトリガー処理時はアプリケーションにアクセスできないようにするためのフラグになります。
SNS_ACCESS_KEY
仮にトリガー処理でバックアップ処理を実行した場合、投稿記事の一意キーが変わってしまします。
そのバックアップ処理が変わったことを判断するための判断として使用するキーになります。アプリケーションアクセス時にクライアント側で保持し、実際にデータシートを更新時にキーが一致するかを判断しています。
SNS_BORAD_DATA , SNS_FILE
データのスプレッドシートIDと添付ファイルを保存するフォルダIDを設定しています。
web画面
ライブラリはVue.js
フレームワークはvuetify.jsこの二つを利用して作成しました。
投稿一覧画面
アプリケーションアクセス時、初めに表示する画面です。
自動で、直近の掲示板数件の投稿を取得します。
「さらに読み込む」をクリックすることでさらに数件読み込む仕様になっています。複数の検索ワードから検索が可能になっています。
表示するのを数件ずつ表示する関係上、リアルタイム検索は出来ないのが残念です。「新規投稿」ボタンから新規投稿が可能で、「リロード」ボタンで記事の再読み込みを行います。
一覧をクリックすることで詳細内容が表示されます。
新規投稿
特に説明することはないかと思います。
添付ファイルはGASのアップロード制限?で50Mbまでにしています。※デモ環境では新規投稿できないです。
詳細画面
枠内に表示されている、文章が掲示板の全本文になります。
投稿者本人の場合、左上に「ゴミ箱」ボタンが表示され、削除することが可能です。
※デモ環境では投稿者の名前は「デモ投稿太郎」になります。下に表示しているメッセージは掲示板に対してコメントをした内容を表示しています。
「リプライ」ボタンで返信画面の表示
「目」ボタンで閲覧者の確認(デモ環境では仮の人しか入っていません)
「リンク」ボタン、「添付ファイル」ボタンは投稿者が添付した場合、クリックし表示されます。返信画面
こちらも特に説明することはないと思います。
メール送信のチェックボックスでメール送信の有無を選択できます。※返信投稿は実際に、スプレッドシートに書き込まないので自由に試してください。
返信も返信者の投稿のみ削除が可能です。
以上で説明を終わります。
最後に
Gsuiteのサービスで組織全体に周知する掲示板が出来てくれたらもっと便利になるきがするんですけどねー。
あんまり需要がないのでしょうか?それと、一部スマホで操作すると思い通りの操作をしてくれないのでそこは改良する必要があると思っています。
なにかご質問や、指摘等がありましたらよろしくお願いします。
- 投稿日:2019-05-22T21:15:39+09:00
[Nuxt]firebaseの認証で無限にユーザー情報を取得できないとき by @penguin_fuyuno
結論
chromeの設定が悪い!!
嘘だろと思わるかもしれないが実際に起きたトラブルです
現象
ログインボタンを押す
googleLogin: function() { console.log("googleLogin"); firebase.auth().signInWithRedirect(new firebase.auth.GoogleAuthProvider());で リダイレクト 認証して リダイレクトさせる
次にログイン状態の判定
firebase.auth().onAuthStateChanged(user => { console.log(user); if (user) { console.log("true"); this.isLogin = true; this.userData = user; } else { console.log("false"); this.isLogin = false; this.userData = null; } })そして ログイン状態を判定させて ユーザー情報を入れる
が!!!!!
ユーザー情報が入らない!!!!
なんで?????
原因と因果
chrome://settings/content/cookies
ここの上から三番目が原因
ここがオンになっていると 認証機能が正しく動作しない
This browser is not supported or 3rd party cookies and data may be disabled.
https://github.com/firebase/firebase-js-sdk/issues/865
https://support.cloudhq.net/how-to-enable-3rd-party-cookies-in-google-chrome-browser/
発見方法
すべては safariでlocalhost をつかったらうまく動作した!!
そこから物語ははじまったchromeだとだめ
裏付けはこれでできます
googleLogin: function() { console.log("googleLogin"); // firebase.auth().signInWithRedirect(new firebase.auth.GoogleAuthProvider()); // ここを入れ替える firebase.auth().getRedirectResult().then(function(authData) { console.log(authData); }).catch(function(error) { console.log(error); }); },で コンソールで確認をする
すると
This browser is not supported or 3rd party cookies
があーだーこーだって言われる
これがさきほどの結論と解決方法につながるのである
感想
一日半かかりました
パンダになるくらい悩みました
コードが原因ではなかったです
今日もなんとか生きてます
これを読んでもらってあなたは10分で解決できることを願います
最後まで読んでくれてありがとうございます(。・ω・。)/
- 投稿日:2019-05-22T21:15:39+09:00
[Nuxt]firebaseのgoogle Auth無限にユーザー情報を取得できないとき by @penguin_fuyuno
結論
chromeの設定が悪い!!
嘘だろと思わるかもしれないが実際に起きたトラブルです
現象
ログインボタンを押す
googleLogin: function() { console.log("googleLogin"); firebase.auth().signInWithRedirect(new firebase.auth.GoogleAuthProvider());で リダイレクト 認証して リダイレクトさせる
次にログイン状態の判定
firebase.auth().onAuthStateChanged(user => { console.log(user); if (user) { console.log("true"); this.isLogin = true; this.userData = user; } else { console.log("false"); this.isLogin = false; this.userData = null; } })そして ログイン状態を判定させて ユーザー情報を入れる
が!!!!!
ユーザー情報が入らない!!!!
なんで?????
原因と因果
chrome://settings/content/cookies
ここの上から三番目が原因
ここがオンになっていると 認証機能が正しく動作しない
This browser is not supported or 3rd party cookies and data may be disabled.
https://github.com/firebase/firebase-js-sdk/issues/865
https://support.cloudhq.net/how-to-enable-3rd-party-cookies-in-google-chrome-browser/
発見方法
すべては safariでlocalhost をつかったらうまく動作した!!
そこから物語ははじまったchromeだとだめ
裏付けはこれでできます
googleLogin: function() { console.log("googleLogin"); // firebase.auth().signInWithRedirect(new firebase.auth.GoogleAuthProvider()); // ここを入れ替える firebase.auth().getRedirectResult().then(function(authData) { console.log(authData); }).catch(function(error) { console.log(error); }); },で コンソールで確認をする
すると
This browser is not supported or 3rd party cookies
があーだーこーだって言われる
これがさきほどの結論と解決方法につながるのである
感想
一日半かかりました
パンダになるくらい悩みました
コードが原因ではなかったです
今日もなんとか生きてます
これを読んでもらってあなたは10分で解決できることを願います
最後まで読んでくれてありがとうございます(。・ω・。)/
- 投稿日:2019-05-22T19:07:33+09:00
画像をリサイズしてblobでプレビュー表示する方法【Vue/Canvas】
ユーザーが画像をアップロードした際に、サーバー側へPOSTする前に
クライアント側で(容量が大きい場合)リサイズし、プレビュー表示したかったので実装しましたやっていること
- 画像をupload
- 内部でリサイズ
- base64からblobへトランスコード
- プレビュー表示
実際の挙動はこんな感じです!
実際のコード
template部分
index.vue<template> <div class="resize-img"> <!-- 画像選択 --> <div v-show="!resizedImg" class="resize-img__post"> <label for="file" class="resize-img__post__label">画像 <input id="file" ref="fileInput" type="file" accept=".jpeg, .png" @change="uploadImg"> </label> </div> <!-- プレビュー --> <div v-show="resizedImg" class="resize-img__preview"> <div class="resize-img__preview__circle" @click="clearUploadImg"> <span class="resize-img__preview__circle__close-icon">×</span> </div> <canvas ref="canvas" class="resize-img__preview__canvas"/> </div> </div> </template>script部分
index.vue<script> export default { data() { return { resizedImg: null }; }, destroyed() { this.clearUploadImg(); }, methods: { uploadImg(e) { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = (e) => { this.generateImgUrl(e.target.result); }; reader.readAsDataURL(file); }, generateImgUrl(file) { const image = new Image(); image.crossOrigin = 'Anonymous'; image.onload = (e) => { const resizedBase64 = this.makeResizeImg(image); // リサイズ済みのBase64をblobに変換 const resizedBlob = this.base64ToBlob(resizedBase64); // urlを生成してプレビュー表示できるようにする const resizedImg = window.URL.createObjectURL(resizedBlob); this.resizedImg = resizedImg; }; image.src = file; }, makeResizeImg(image) { const canvas = this.$refs.canvas; const ctx = canvas.getContext('2d'); // 2Dコンテキスト // 縦横で長い方の最大値を1000とする const MAX_SIZE = 1000; // MAX_SIZEよりも小さかったらそのまま if (image.width < MAX_SIZE && image.height < MAX_SIZE) { [canvas.width, canvas.height] = [image.width, image.height]; ctx.drawImage(image, 0, 0); return canvas.toDataURL('image/jpeg'); } let dstWidth; let dstHeight; // 縦横比の計算 if (image.width > image.height) { dstWidth = MAX_SIZE; dstHeight = (image.height * MAX_SIZE) / image.width; } else { dstHeight = MAX_SIZE; dstWidth = (image.width * MAX_SIZE) / image.height; } canvas.width = dstWidth; canvas.height = dstHeight; // リサイズ ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, dstWidth, dstHeight); // data_url形式に変換したものを返す return canvas.toDataURL('image/jpeg'); }, clearUploadImg() { this.resizedImg = null; if (this.$refs.fileInput && this.$refs.fileInput.value !== undefined) { this.$refs.fileInput.value = ''; } }, base64ToBlob(base64) { const bin = atob(base64.replace(/^.*,/, '')); const buffer = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) { buffer[i] = bin.charCodeAt(i); } return new Blob([buffer.buffer], { type: 'image/png' }); } } }; </script>css部分
style.scss.resize-img { width: 300px; height: 300px; margin: 0 auto; margin-top: 20px; &__post { border: 1px solid rgba(#000, 0.16); line-height: 30rem; &__label { display: inline-block; width: 100%; color: rgba(0, 0, 0, 0.4); text-align: center; & > input { display: none; } } } &__preview { width: 300px; height: 300px; &__circle { position: absolute; right: 37px; width: 27px; height: 27px; margin: 5px; padding: 2px 9px; border-radius: 50%; background-color: rgba(0, 0, 0, 0.3); &__close-icon { color: #fff; } } &__canvas { width: 100%; height: 100%; object-fit: cover; } } }試しに
MAX_SIZEを変更して検証MAX_SIZE = 1000 の場合
画像はMAX_SIZEよりも小さいので原寸表示されます
MAX_SIZE = 500 の場合
画像はMAX_SIZEよりも大きいので500pxにリサイズされます。いい感じ!
参考
弊社フロントエンドエンジニアの記事!まさしくな記事で参考にさせていただきました
* Vue.jsでファイルアップロードの実装--ついでにプレビュー機能も実装した話-- - Qiita実際にリサイズできるサービスのリンクと中のコードが書いてあって参考になりました
* HTMLとJavaScriptで画像をリサイズ | blog.PanicBlanket.comNuxtで書かれていて参考になりました
* Nuxt.js(vue.js)で画像を縮小してFirebaseStorageにアップロードする - QiitaサーバーへPOSTする際のファイル形式の参考になりました
(base64とかblobとか分からなかったので・・)
* Canvasで描画した画像を送信してサーバに保存する - QiitacanvasのdrawImageがよく分からなかったので、参考になりました
* drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)-Canvasリファレンス
- 投稿日:2019-05-22T12:51:44+09:00
【Vue.js初心者】Todoリストはこう作る!~Todo削除~
概要
前回より引き続き、
以下のVue.js公式で紹介されているサンプルについてどのように動作しているのか見ていきます。
https://jp.vuejs.org/v2/examples/todomvc.htmljsfiddleでも見れるので実際に触りながら見てみましょう。
https://jsfiddle.net/yyx990803/4dr2fLb7/?utm_source=website&utm_medium=embed&utm_campaign=4dr2fLb7こんな感じの画面構成です。
この記事の対象読者
・Vue.jsの公式ドキュメントに一通り目を通している
・でもまだよくわかっていない
・私もそうです機能概要
以下の機能が存在します。
・TODO追加
・TODO削除
・TODO完了
・完了済みTODO件数表示
・TODOフィルター
・完了済みTODO全削除前回はTODO追加について見ていきました。
今回はTODO削除について見ていきます。TODO削除
・画面操作
TODOリストの削除は、リストにカーソルを合わせたときにリスト右側に出てくる
赤いバツボタンをクリックすると削除できます。・removeTodo
ではバツボタンが押された際、何が起こるのでしょうか。
HTML<ul class="todo-list"> <li v-for="todo in filteredTodos" class="todo" :key="todo.id" :class="{ completed: todo.completed, editing: todo == editedTodo }"> <div class="view"> <input class="toggle" type="checkbox" v-model="todo.completed"> <label @dblclick="editTodo(todo)">{{ todo.title }}</label> <button class="destroy" @click="removeTodo(todo)"></button> </div> // 省略 </li> </ul>これはリスト部分のHTMLです。
filteredTodosをv-forで1要素づつ取得しています。
filteredTodosは、リストが格納されているtodosにフィルターを掛けたものです。
今のところはtodosと同義だと思っていてください。
v-for="X in Xs"は
Xsの要素を取り出しXに格納する、という意味です。1この中の
HTML<button class="destroy" @click="removeTodo(todo)"></button>この部分が削除ボタンですね。
クリックした際にremoveTodoが呼ばれているようです。
では次にJavaScriptを見てみます。JavaScriptremoveTodo: function (todo) { this.todos.splice(this.todos.indexOf(todo), 1) },引数で渡されたtodoをspliceでtodosから削除しています。2
これでtodosから削除されました。
さらに、todosが変更されたことによって、watch内の関数が呼ばれます。・watchでのtodos監視
JavaScriptwatch: { todos: { handler: function (todos) { todoStorage.save(todos) }, deep: true } },これは前回も登場した関数ですね。
todoStorage.saveを呼んでいます。JavaScriptvar todoStorage = { // 省略 save: function (todos) { localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)) } }そしてこれがtodoStorage.saveです。
localStorageに変更後のtodosを保持します。これで削除処理は完了です。
まとめ
追加処理より削除処理のほうが簡単でしたね。
ご参考になれば、いいねしてくれるとうれしいです。次回はレ点チェックをつけてTODOを完了状態する処理を見ていきましょう。
参考
- 投稿日:2019-05-22T12:43:28+09:00
CloudFront+S3環境で Vue Nuxt の静的化ページでリロードした時にエラー(access denied)となる時の対処法
CloudFront+S3の環境構築にデプロイ
nuxtアプリの
mode: 'universal'モードにて
アプリケーションから静的ファイルを生成する。
npm run generate静的なホスティングサイトにデプロイする準備が整ったものが全て入った dist フォルダが作成されます。
デプロイはAWS S3に先程生成されたファイルを配置することでデプロイできます。
「/」以外のURLでリロードした場合に403(access denied)エラーとなる
This XML file does not appear to have any style information associated with it.!~~~~
AccessDenied
AccessDenied対応策
エラーの原因として対応するファイルがないため、ブラウザリロードすると 403 エラーになってしまうようです。
CloudFrontのError Pagesで設定
403のときに/index.html(今回は「/」)へ転送するよう設定することでこの現象を回避することができます。
Custom Error Response で追加設定
HTTP Error Code: 403:Forbidden
Error Caching Minimum TTL (seconds): 0
Customize Error Response: Yes
Response Page Path:/
HTTP Response Code: 200:OK以上の設定でリロードしてもエラーとならずページが表示されます。
こちらのサイトの記事を参考に解決することができました。
https://dev.classmethod.jp/cloud/aws/s3-cloudfront-spa-angular-403-access-denied/
ありがとうございます!!
- 投稿日:2019-05-22T12:39:16+09:00
error Command "webpack-dev-server" not found. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
$ bin/webpack-dev-serverで出るタイトルのエラー
error Command "webpack-dev-server" not found. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.解決コマンド
yarnのアップグレード
$ brew upgrade yarn
webpackerインストール$ bundle exec rails webpacker:install
webpack-dev-server立ち上げ$ bin/webpack-dev-server
- 投稿日:2019-05-22T10:59:14+09:00
Vue.jsでインスタンス外からの変更を管理下に入れる
Vue.js外から取得した値を管理下に入れる方法です
例えば単純にjQueryなどで値を取得した際も使えますし
ECCUBEでcontrollerから受け取った値なんかも(jQueryを介して)管理下に入れることできますindex.html<div id="app" class="baz"> <p>{{ foo }}</p> <p>{{ bar }}</p> </div>app.jsvar app = new Vue({ el: '#app', data: { foo: 'ふー', bar: '' }, mounted: function () { this.bar = $('#app').attr('class'); }, })dataに登録して初期値を空にしておきます。
実際のサンプルはこちら他の言語やサービスに依存しないといけない場合こそ使える
上記コードだと
:class使えば管理下に入るじゃねーかという話なんですが、それが使えない場面ってCMSやEC系のサービスだと結構あるんですよね
僕の場合はECCUBEで役に立ちました。detail.twig. . <!--▼商品タグ--> <div id="product_tag_box" class="product_tag"> {% for ProductTag in Product.ProductTag %} <span id="product_tag_box__product_tag--{{ ProductTag.Tag.id }}" class="product_tag_list">{{ ProductTag.Tag }}</span> {% endfor %} </div> <hr> <!--▲商品タグ--> . .実際に利用した場面ではないですが。。。
例えば商品についているタグのidをとってくるコード{{ ProductTag.Tag.id }}を含んだidを管理下に入れたいなってときに上記方法が利用できます。やってることはシンプルなので知ってて当然なことかもしれませんが、jQueryを切り離せない、でもVue.jsも使っていきたいという人にとっては使えるかなと思います
ただ試行錯誤の末にたどり着いた力技だったので正攻法ではない気がしますw
欠点はdataが膨らんでくるので量が多いとなかなか悲惨ですこうやればもっとスマートだよっていうのがあればぜひ教えてください!


























