- 投稿日:2020-01-18T21:25:49+09:00
Nuxt.jsとFirebaseのFirestoreでリアルタイムダッシュボードを作った
やりたいこと
Nuxt.jsとfirestoreを使用して、リアルタイムでダッシュボードを更新するシステムを作る。
リロードとかせずサクサクと!firestoreの作成
コンソールからプロジェクトを作成して設定
※あとで書く
準備
①firebaseをインストール
yarn add firebase
ornpm install firebase --save
②plugins/firebase.jsの作成
firebaseの初期設定をします!
firebaseのページの設定画面下部にある情報をそのまま貼り付けてます。
開発環境と本番環境でfirebaseのアカウントを使い分けるためにENVにしています。
.env
で書いた環境変数名を書きますplugins/firebase.jsimport firebase from 'firebase/app' import 'firebase/firestore' firebase.initializeApp({ apiKey: process.env.FIRE_BASE_API_KEY, authDomain: process.env.FIRE_BASE_AUTH_DOMAIN, databaseURL: process.env.FIRE_BASE_DATABASE_URL, projectId: process.env.FIRE_BASE_PROJECT_ID, storageBucket: process.env.FIRE_BASE_STORAGE_BUCKET, messagingSenderId: process.env.FIRE_BASE_MESSAGING_SENDER_ID, appId: process.env.FIRE_BASE_APP_ID, measurementId: process.env.FIRE_BASE_MEASUREMENT_ID }) const db = firebase.firestore() export default db③nuxt.config.jsへ追記
nuxt.config.js...省略 plugins: [ ... { src: '~/plugins/firebase' } ], ...省略これでfirestoreを使う準備が整いました!
store/firestoreディレクトリを用意する
私はstore配下をAPI毎にディレクトリを切って、actions.js/getters.js/index.js/mutations.js
と分けて書く方法が好きです。
理由は見やすいし追いやすいからです。今回はfirestoreの通信についてなので、firestoreとディレクトリを切ります。
まずはactions.js
store/actions.jsimport db from '~/plugins/firebase' // コレクション名をセット const fireStore = db.collection('dashboard') export const actions = { GET_REALTIME_LIST({ commit }, payload) { fireStore .where('report_datetime', '>', payload.from) .where('report_datetime', '<', payload.to) .orderBy('report_datetime', 'asc') .orderBy('created_at', 'asc') .onSnapshot( res => { commit('GET_REALTIME_LIST', res) }, error => { console.error('GET_REALTIME_LIST', error) } ) }, UPDATE_FIRE_STORE({ commit }, payload) { fireStore .doc(payload.id) .set(payload) .then(res => { commit('UPDATE_FIRE_STORE', res) }) .catch(e => { console.error('UPDATE_FIRE_STORE', e) }) } } export default actions
const fireStore = db.collection('dashboard')
ここで接続をするコレクション名をセットします。
今回のコレクション名はdashboard
なのでこのように記述。
onSnapshot
これがリアルタイム同期をするポイントです。
他にも追加されたものだけ取得、削除されたものだけ取得、とか書き方はありますが
今回のダッシュボードは全体を常に同期していたいのでこう書いてます。また、firestoreではあまり柔軟にクエリが書けません。
between句もありません><
そこで.where('report_datetime', '>', payload.from) .where('report_datetime', '<', payload.to)このように書くと、between句のように使えます。
次はmutations.js
mutations.jsimport { GET_REALTIME_INCOMPLETE_LIST_FIRE_STORE, UPDATE_FIRE_STORE } from '~/store/types' export const mutations = { GET_REALTIME_LIST(state, content) { state.realtimeFireStore = content.docs.map(res => { const fireStore = res.data() fireStore.id = res.id return fireStore }) }, UPDATE_FIRE_STORE(state, content) { state.updateFireStore = content } } export default mutationsactionsでmutationsにcommitする際に、res.docsとやってもmutationsでmapで回す前にやっても大丈夫です。
responseではdocsの中に一覧が返ってきます。
そのまま直接参照することは出来ないので、mapで回しながらdata()とやってあげます。
fireStore.id = res.id
ここでなぜidを入れているかというと
データを入れる際に、自動生成IDで作成しているので自動生成された一意のIDがデータ内に入っていません。保持をしておかないとupdateで使用する際に困るので、データ内に突っ込んでます。
自動生成されたIDは、docs内にあります。IDはdata()で参照する中にはなく直接参照できるので、上記のように書きます。続いてgetters.js
getters.jsexport const getters = { getRealtimeFireStore: state => state.realtimeFireStore, getUpdateFireStore: state => state.updateFireStore } export default gettersここは特にポイントはありません
続いてindex.js
index.jsimport Actions from './actions' import Getters from './getters' import Mutations from './mutations' export const state = () => ({ realtimeFireStore: [], updateFireStore: '' }) export const actions = Actions export const getters = Getters export const mutations = Mutationsここも特にポイントはありません。
これでstoreに準備完了です。
pages/dashboard.vueの作成
さて、やっと画面に取り掛かります。
pages/dashboard.vue<template> <div> <div class="contents"> <h1 class="title">テスト</h1> </div> <div> <table class="dashboard"> <tr v-for="(dashboardRow, rowIndex) in dashboards" :key="rowIndex"> <td v-for="(dashboard, dashboardIndex) in dashboardRow" :key="dashboard.id"> <div class="dashboard-card"> <input type="text" :value="dashboard.title" @blur="save(rowIndex, dashboardIndex)" /> </div> </td> </tr> </table> </div> </div> </template> <script> import moment from 'moment' import { mapGetters, mapActions } from 'vuex' import chunk from 'lodash/chunk' import cloneDeep from 'lodash/cloneDeep' export default { name: "dashboard", computed: { ...mapGetters('firestore/', [ 'getUpdateFireStore', 'getRealtimeFireStore' ]), dashboards: { get(){ const getFireStore = cloneDeep(this.getRealtimeFireStore) const dashboards = chunk(filterData, 3).map((dashboardItems, index) => { if (dashboardItems.length !== 3) { for (let i = 0; i < 4 - dashboardItems.length; i++) { dashboardItems.push({}) } } return dashboardItems }) return dashboards }, set(dashboards){ return dashboards } } }, created() { this.getDashboard() }, methods: { ...mapActions('firestore/', [ 'UPDATE_FIRE_STORE', 'GET_REALTIME_LIST' ]), async getDashboard() { this.GET_REALTIME_LIST({ from: moment().subtract(3, 'days').format('YYYY-MM-DD 00:00:00'), to: moment().format('YYYY-MM-DD 23:59:59') }) }, async save(rowIndex, dashboardIndex) { await this.UPDATE_FIRE_STORE(this.dashboards[rowIndex][dashboardIndex]) }, } } </script> <style scoped> table.dashboard { width: 100%; margin-top: 40px; } table.dashboard td { width: 30%; padding: 20px 14px; } .dashboard-card { border: 1px solid #cecece; border-radius: 4px; padding: 10px 6px; background-color: #f7f1f1; box-shadow: 0px 0px 9px 0px #afafaf; } </style>直近3日のデータを取得しています。
created() { this.getDashboard() },createdでリアルタイムでgetするactions: GET_REALTIME_LIST しているメソッドを呼びます。
これで常にリアルタイム通信が可能となります。computed内で、リアルタイムでgetしているデータを(gettersのgetRealtimeFireStore)を加工してあげます。
横3個ずつズラ〜と出したいのでlodashのchunkでわけてあげます。
カード内のテキストボックスでは、文字firestoreに入れてるデータのtitleを表示しています。
この値を変更してフォーカスを外してタイミングで更新メソッド(save)を呼んでいます。actions:UPDATE_FIRE_STOREを呼んで、更新処理を行います。
この更新がかかると、今actions:GET_REALTIME_LISTがリアルタイムで取得しているので、
getters: getRealtimeFireStoreの値が変更されるため
computedのdashboardsが動き、画面上のカードがリアルタイムで変更されます。リアルタイムで動いている確認方法
方法①このlocalhostを2つ開き、画面に分けて表示して
片方で更新かけて、もう1つの方でリロードなしに変更がされれば成功です!方法②firestoreのコンソールで直接データを変更して、リロードなしにダッシュボード内のカードが更新されれば成功です!
以上でリアルタイムダッシュボードの実装は終わりです!
- 投稿日:2020-01-18T21:25:49+09:00
Nuxt.jsとfirestoreでリアルタイムダッシュボードを作った
やりたいこと
Nuxt.jsとfirestoreも使用して、リアルタイムでダッシュボードを更新するシステムを作る。
リロードとかせずサクサクと!firestoreの作成
コンソールからプロジェクトを作成して設定
※あとで書く
準備
①firebaseをインストール
yarn add firebase
ornpm install firebase --save
②plugins/firebase.jsの作成
firebaseの初期設定をします!
firebaseのページの設定画面下部にある情報をそのまま貼り付けてます。
開発環境と本番環境でfirebaseのアカウントを使い分けるためにENVにしています。
.env
で書いた環境変数名を書きますplugins/firebase.jsimport firebase from 'firebase/app' import 'firebase/firestore' firebase.initializeApp({ apiKey: process.env.FIRE_BASE_API_KEY, authDomain: process.env.FIRE_BASE_AUTH_DOMAIN, databaseURL: process.env.FIRE_BASE_DATABASE_URL, projectId: process.env.FIRE_BASE_PROJECT_ID, storageBucket: process.env.FIRE_BASE_STORAGE_BUCKET, messagingSenderId: process.env.FIRE_BASE_MESSAGING_SENDER_ID, appId: process.env.FIRE_BASE_APP_ID, measurementId: process.env.FIRE_BASE_MEASUREMENT_ID }) const db = firebase.firestore() export default db③nuxt.config.jsへ追記
nuxt.config.js...省略 plugins: [ ... { src: '~/plugins/firebase' } ], ...省略これでfirestoreを使う準備が整いました!
store/firestoreディレクトリを用意する
私はstore配下をAPI毎にディレクトリを切って、actions.js/getters.js/index.js/mutations.js
と分けて書く方法が好きです。
理由は見やすいし追いやすいからです。今回はfirestoreの通信についてなので、firestoreとディレクトリを切ります。
まずはactions.js
store/actions.jsimport db from '~/plugins/firebase' // コレクション名をセット const fireStore = db.collection('dashboard') export const actions = { GET_REALTIME_LIST({ commit }, payload) { fireStore .where('report_datetime', '>', payload.from) .where('report_datetime', '<', payload.to) .orderBy('report_datetime', 'asc') .orderBy('created_at', 'asc') .onSnapshot( res => { commit('GET_REALTIME_LIST', res) }, error => { console.error('GET_REALTIME_LIST', error) } ) }, UPDATE_FIRE_STORE({ commit }, payload) { fireStore .doc(payload.id) .set(payload) .then(res => { commit('UPDATE_FIRE_STORE', res) }) .catch(e => { console.error('UPDATE_FIRE_STORE', e) }) } } export default actions
const fireStore = db.collection('dashboard')
ここで接続をするコレクション名をセットします。
今回のコレクション名はdashboard
なのでこのように記述。
onSnapshot
これがリアルタイム同期をするポイントです。
他にも追加されたものだけ取得、削除されたものだけ取得、とか書き方はありますが
今回のダッシュボードは全体を常に同期していたいのでこう書いてます。また、firestoreではあまり柔軟にクエリが書けません。
between句もありません><
そこで.where('report_datetime', '>', payload.from) .where('report_datetime', '<', payload.to)このように書くと、between句のように使えます。
次はmutations.js
mutations.jsimport { GET_REALTIME_INCOMPLETE_LIST_FIRE_STORE, UPDATE_FIRE_STORE } from '~/store/types' export const mutations = { GET_REALTIME_LIST(state, content) { state.realtimeFireStore = content.docs.map(res => { const fireStore = res.data() fireStore.id = res.id return fireStore }) }, UPDATE_FIRE_STORE(state, content) { state.updateFireStore = content } } export default mutationsactionsでmutationsにcommitする際に、res.docsとやってもmutationsでmapで回す前にやっても大丈夫です。
responseではdocsの中に一覧が返ってきます。
そのまま直接参照することは出来ないので、mapで回しながらdata()とやってあげます。
fireStore.id = res.id
ここでなぜidを入れているかというと
データを入れる際に、自動生成IDで作成しているので自動生成された一意のIDがデータ内に入っていません。保持をしておかないとupdateで使用する際に困るので、データ内に突っ込んでます。
自動生成されたIDは、docs内にあります。IDはdata()で参照する中にはなく直接参照できるので、上記のように書きます。続いてgetters.js
getters.jsexport const getters = { getRealtimeFireStore: state => state.realtimeFireStore, getUpdateFireStore: state => state.updateFireStore } export default gettersここは特にポイントはありません
続いてindex.js
index.jsimport Actions from './actions' import Getters from './getters' import Mutations from './mutations' export const state = () => ({ realtimeFireStore: [], updateFireStore: '' }) export const actions = Actions export const getters = Getters export const mutations = Mutationsここも特にポイントはありません。
これでstoreに準備完了です。
pages/dashboard.vueの作成
さて、やっと画面に取り掛かります。
pages/dashboard.vue<template> <div> <div class="contents"> <h1 class="title">テスト</h1> </div> <div> <table class="dashboard"> <tr v-for="(dashboardRow, rowIndex) in dashboards" :key="rowIndex"> <td v-for="(dashboard, dashboardIndex) in dashboardRow" :key="dashboard.id"> <div class="dashboard-card"> <input type="text" :value="dashboard.title" @blur="save(rowIndex, dashboardIndex)" /> </div> </td> </tr> </table> </div> </div> </template> <script> import moment from 'moment' import { mapGetters, mapActions } from 'vuex' import chunk from 'lodash/chunk' import cloneDeep from 'lodash/cloneDeep' export default { name: "dashboard", computed: { ...mapGetters('firestore/', [ 'getUpdateFireStore', 'getRealtimeFireStore' ]), dashboards: { get(){ const getFireStore = cloneDeep(this.getRealtimeFireStore) const dashboards = chunk(filterData, 3).map((dashboardItems, index) => { if (dashboardItems.length !== 3) { for (let i = 0; i < 4 - dashboardItems.length; i++) { dashboardItems.push({}) } } return dashboardItems }) return dashboards }, set(dashboards){ return dashboards } } }, created() { this.getDashboard() }, methods: { ...mapActions('firestore/', [ 'UPDATE_FIRE_STORE', 'GET_REALTIME_LIST' ]), async getDashboard() { this.GET_REALTIME_LIST({ from: moment().subtract(3, 'days').format('YYYY-MM-DD 00:00:00'), to: moment().format('YYYY-MM-DD 23:59:59') }) }, async save(rowIndex, dashboardIndex) { await this.UPDATE_FIRE_STORE(this.dashboards[rowIndex][dashboardIndex]) }, } } </script> <style scoped> table.dashboard { width: 100%; margin-top: 40px; } table.dashboard td { width: 30%; padding: 20px 14px; } .dashboard-card { border: 1px solid #cecece; border-radius: 4px; padding: 10px 6px; background-color: #f7f1f1; box-shadow: 0px 0px 9px 0px #afafaf; } </style>直近3日のデータを取得しています。
created() { this.getDashboard() },createdでリアルタイムでgetするactions: GET_REALTIME_LIST しているメソッドを呼びます。
これで常にリアルタイム通信が可能となります。computed内で、リアルタイムでgetしているデータを(gettersのgetRealtimeFireStore)を加工してあげます。
横3個ずつズラ〜と出したいのでlodashのchunkでわけてあげます。
カード内のテキストボックスでは、文字firestoreに入れてるデータのtitleを表示しています。
この値を変更してフォーカスを外してタイミングで更新メソッド(save)を呼んでいます。actions:UPDATE_FIRE_STOREを呼んで、更新処理を行います。
この更新がかかると、今actions:GET_REALTIME_LISTがリアルタイムで取得しているので、
getters: getRealtimeFireStoreの値が変更されるため
computedのdashboardsが動き、画面上のカードがリアルタイムで変更されます。リアルタイムで動いている確認方法
方法①このlocalhostを2つ開き、画面に分けて表示して
片方で更新かけて、もう1つの方でリロードなしに変更がされれば成功です!方法②firestoreのコンソールで直接データを変更して、リロードなしにダッシュボード内のカードが更新されれば成功です!
以上でリアルタイムダッシュボードの実装は終わりです!
- 投稿日:2020-01-18T20:57:14+09:00
Conteful公式ドキュメント「Integrate Contentful with Nuxt.js」の翻訳
はじめに
Nuxt.jsは、国内でますますの盛り上がりを見せています。特に、ContentfulやMicroCMSといったHeadless CMSと組み合わせたJAMStackな構成が注目されています。
しかし、Headless CMSの中で圧倒的な実績と人気を誇るContentfulはドキュメントが日本語対応していません(※2020年3月24日現在)。Contentfulのドキュメントは(英語とはいえ)この上なく丁寧に解説されています。そこで、公式ドキュメントの
Integrate Contentful with Nuxt.js
を自分用に日本語に翻訳したものをQiita上で共有させて頂きます。
なるべく原文通りの翻訳を心がけていますが、適宜注をつけています。
参考になれば幸いです。(※以下本文です)
Integrate Contentful with(ContentfulをNuxt.jsと統合する)
新しいインタラクティブなCLIの開始ガイドを試してみると、おそらく、サーバー側とブラウザーでVue.jsを実行できるユニバーサルJavaScriptアプリケーションであることがわかりました。これは、ビルド時に静的サイトのセット全体を生成し、ユーザーが別の場所に移動したい場合、Vue.jsを使用してサイトが部分的に再レンダリングされることを意味します。かなりクールなものです!
このチュートリアルでは、Contentfulを使用して独自のNuxt.jsプロジェクトをセットアップする方法を示します。
Getting started with Nuxt.js(Nuxt.jsの使用を開始する)
Nuxt.jsアプリケーションで開始する推奨の方法は、vue-cliツールを使用することです。
$ npm install -g vue-cli
vue
コマンドが使用可能になったので、nuxt / starter
テンプレートを使用して新しいプロジェクトを初期化できます。$ vue init nuxt / starter <プロジェクト名>これで、新しく作成したプロジェクトに移動し
npm install
を実行するだけで、ユニバーサルJavaScriptアプリケーションをContentfulに対応させることができます。詳細については、Nuxt.jsの入門ガイドをご覧ください。npm run dev
コマンドは、プロジェクト内での開発に使用できます。実行すると、Nuxt.jsサンプルプロジェクトが表示されます。その後、微調整してContentfulを実装できます。Setting up the content model using the Contentful CLI(Contentful CLIを使用してコンテンツモデルを設定する)
Contentfulの統合を開始する前にスペースを設定し、コンテンツモデルを作成してデータを入力する必要があります。(あなたにとって)これがまったく新しい場合は、Contentfulデータモデルの紹介と、コンテンツ作成の完全な初心者向けガイドを最初に読むことをお勧めします。
すべてのデータ設定を手動で行うことができますが、CLIを使用して新しいスペースを作成し、データをフィードすることもできます。代わりにこれを行いましょう。
CLIは、提供されている方法のいずれかでインストールできます。
CLIを使用できるようになったので、
contentful login
を介してそれを認証する必要があります。このコマンドは、新しいブラウザウィンドウを開きます。このウィンドウでは、新しいアカウントを作成するか、ログインして新しいアクセストークンを取得できます。$ contentful login新しいスペースの作成は、CLIコマンドを1つ使用するだけで済み、app.contentful.comに手動で移動する必要はありません。
注:あなたのプランで使用可能な空きスペースがなくなった場合、スペースを作成すると追加料金が発生する場合があります。
$ contentful space create --name 'Nuxt tutorialすごい!これで新しいスペースが作成され、コンテンツをシードする準備ができました。 CLIツールはすぐに必要になるスペースIDを表示します。
スペース間でコンテンツを転送するにはContentful CLIから
contentful space export
およびcontentful space import
コマンドを使用します。
seed
コマンドは、定義済みの--space-id
フラグとともに使用できますが、便宜上、次のコマンドにも特定のスペースを使用するようにCLIを構成できます。$ contentful space use ? Please select a space: ... ❯ Nuxt tutorial (k8iqtj0xib4n) ...いくつかのデータをインポートします!
# after space selection using `contentful space use` $ contentful space seed --template blog # without space selection $ contentful space seed --template blog --space-id <your-new-space-id>新しいスペースには、人とブログ投稿のコンテンツタイプを含むコンテンツモデルが設定されました。
最後に行うことは、Content Delivery APIアクセストークンを作成することです。これは、WebアプリまたはCLIを使用して実行できます。
$ contentful space accesstoken create --name nuxt-tutorial ✨Successfully created access token nuxt (1234567890xxxxxxxxxxx)これは開始してNuxt.jsアプリに統合するのに十分です。
Integrating Contentful into Nuxt.js(ContentfulをNuxt.jsに統合する)
この時点でNuxt.jsプロジェクトは、
./pages/index.vue
で定義されているルートインデックスのみを使用できます。 Contentfulを使用するには、2つのことを行う必要があります。
- Contentful JavaScript CDA SDKを登録する
index.vue
ページコンポーネントでデータを取得するInstall the Contentful CDA JavaScript SDK(Contentful CDA JavaScript SDKをインストールします)
Contentfulを使用する最も簡単な方法は、JavaScript SDKをインストールすることです。これを行うには、
npm install contentful
を簡単に呼び出します。 JavaScript SDKはブラウザのJavaScriptバンドルに含まれるので、npm--save
フラグを使用して実稼働依存関係として保存する必要があります。$ npm install --save contentfulNuxt.jsは、サーバー(静的な事前レンダリング)およびクライアント側(動的な再レンダリング)でカスタムコードを使用できるようにするプラグイン機能を提供します。幸いなことに、JavaScript SDKはaxiosに基づいており、Node.jsおよびブラウザーコンテキストで使用できます。
これを使用するには、
plugins
ディレクトリにcontentful.js
という新しいファイルを作成します。このファイルの目的は、ブートストラッププロセス中に設定する定義済みの環境変数を使用してSDKクライアントを作成することです。// ./plugins/contentful.js const contentful = require('contentful') // use default environment config for convenience // these will be set via `env` property in nuxt.config.js const config = { space: process.env.CTF_SPACE_ID, accessToken: process.env.CTF_CDA_ACCESS_TOKEN } // export `createClient` to use it in page components module.exports = { createClient () { return contentful.createClient(config) } }次に、使用する環境変数を定義する必要があります。実行中にCLIですべての環境変数を設定する必要性を回避するには
npm run dev
では、.contentful.json
と呼ばれる新しい構成ファイルをセットアップできます。このファイルには、必要な構成が含まれています。
- 人のエントリID(ブログの所有者)
- 投稿データを取得するブログ投稿のコンテンツタイプID
- あなたのスペースID
- コンテンツ配信アクセストークン
すでにスペースID(
CTF_SPACE_ID
)とアクセストークン(CTF_CDA_ACCESS_TOKEN
)を使用しています。ブログ投稿のコンテンツタイプIDと、ブログの作成者である1人のエントリIDは、テンプレートデータで既に定義されています。ブログ投稿のIDはblogPost
になり、perosnのIDは15jwOBqpxqSAOy2eOO4S0m
になります。{ 「CTF_PERSON_ID」:「15jwOBqpxqSAOy2eOO4S0m」、 「CTF_BLOG_POST_TYPE_ID」:「blogPost」、 「CTF_SPACE_ID」:「YOUR_SPACE_ID」、 「CTF_CDA_ACCESS_TOKEN」:「YOUR_ACCESS_TOKEN」 }
nuxt.config.js
で、構成ファイルを要求し、env
プロパティを介してプラグインファイルで使用可能にすることができます。// ./nuxt.config.js const config = require('./.contentful.json') module.exports = { // ... env: { CTF_SPACE_ID: config.CTF_SPACE_ID, CTF_CDA_ACCESS_TOKEN: config.CTF_CDA_ACCESS_TOKEN, CTF_PERSON_ID: config.CTF_PERSON_ID, CTF_BLOG_POST_TYPE_ID: config.CTF_BLOG_POST_TYPE_ID } // ... }
env
プロパティは、Nuex.jsで、Node.jsコンテキストまたはVue.jsコンポーネントのブラウザーのcontext objectで実行したときにprocess.env
を介して使用できる値を定義する方法です。これは本当に便利になり、その理由がすぐにわかります。Fetch data and render every page(データを取得してすべてのページをレンダリングする)
Nuxt.jsは、使用可能なページとルートを定義する規則を定義します。サンプルテンプレートでは、ファイル
./pages/index.vue
が既に作成されています。このファイルは、サイトのエントリポイントになります。動的パラメーターを使用してルーターを定義することもできます。詳細については、Nuxt.jsルーティングドキュメントを読むか、完成したサンプルのフォルダー構造を確認することをお勧めします。Nuxt.jsでは、すべてのページコンポーネントの非同期データを定義できます。このデータは、ビルド時に取得され、その後すべてのページナビゲーションの前に取得されます。 Vue.jsのシングルファイルコンポーネントアプローチに慣れていない場合は、まずファイル構造を理解してください。
<template> <div> <!-- render data of the person --> <h1>{{ person.fields.name }}</h1> <!-- render blog posts --> <ul> <li v-for="post in posts"> {{ post.fields.title }} </li> </ul> </div> </template> <script> import {createClient} from '~/plugins/contentful.js' const client = createClient() export default { // `env` is available in the context object asyncData ({env}) { return Promise.all([ // fetch the owner of the blog client.getEntries({ 'sys.id': env.CTF_PERSON_ID }), // fetch all blog posts sorted by creation date client.getEntries({ 'content_type': env.CTF_BLOG_POST_TYPE_ID, order: '-sys.createdAt' }) ]).then(([entries, posts]) => { // return data that should be available // in the template return { person: entries.items[0], posts: posts.items } }).catch(console.error) } } </script>これらの数行は作成したばかりのContentfulプラグインをインポートし、新しいSDKクライアントを作成します。 Nuxt.jsには、プラグインディレクトリのショートハンドも用意されているため、Contentfulプラグイン(`〜/ plugins / contentful.js')を簡単にインポートできます。
エクスポートされたオブジェクトの
async
プロパティで、必要なデータを取得し、コンポーネントで使用できるデータで解決されるプロミスを返すことができます。この場合、作成日と1人の人物で並べられたブログ投稿を取得しています。そして、これらの呼び出しの設定値はenv
を介して利用できることがわかります。その後、テンプレートのデータにアクセスできます。サイドノート:理論上は、各ブログ投稿でリンクされているため、1回のAPI呼び出しを保存できますが、わかりやすくするために、ここでは2回呼び出すことにしました。
Nuxt.jsによって事前にレンダリングされた最初のルートを持つために必要なことはこれだけです。 Contentfulからのより多くのデータでより多くのルートを作成できます。これにより、事前にレンダリングされた複数のHTMLページが表示され、ナビゲーション中にブラウザーで自動的に再レンダリングされます。このプロダクションを準備するには、
npm run generate
を実行してページを静的に生成します。これらのページはデフォルトでdist
に書き込まれます。$ npm run generate ... Asset Size Chunks Chunk Names 0.nuxt.bundle.19d1fc79d53508bafb3c.js 9.02 kB 0 [emitted] pages/index 1.nuxt.bundle.073636965192d97508b7.js 2.8 kB 1 [emitted] layouts/default vendor.bundle.159c28fcc84fd619b9e6.js 213 kB 2 [emitted] vendor nuxt.bundle.4a191451179ff5f8654a.js 24.5 kB 3 [emitted] app manifest.2b2d08aa839a35d40759.js 1.51 kB 4 [emitted] manifest index.html 132 bytes [emitted] client-manifest.json 5.73 kB [emitted] ...Sum up(まとめる)
これは、インタラクティブな入門CLIガイドを使用して5分で達成できる簡単なウォークスルーでした。
Nuxt.jsとContentfulを使用すると、運用上の大きなオーバーヘッドなしで、かなり迅速にユニバーサルJavaScriptアプリケーションを作成できます。スタックを構築して、選択したCDNにプッシュするだけで、準備完了です。
最終的な実装を確認したい場合は、5分でわかるブログの例をご覧ください。そこには、いくつかのルートと高度なルートの事前レンダリングを含む完全なソリューションがあります。
楽しんでください!
次のステップ
- JavaScriptアプリでContentful Delivery APIを使用する
- 変更されたコンテンツのみを更新する方法 お探しのものではありませんか?よくある質問をお試しください。
(※以上で翻訳は終了です)
- 投稿日:2020-01-18T20:04:02+09:00
初心者によるプログラミング学習ログ 213日目
100日チャレンジの213日目
twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
213日目は
おはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) January 17, 2020
213日目
vue cliの導入とか設定
ふとvue.jsでポートフォリオつくろうとおもいたった#100DaysOfCode #駆け出しエンジニアと繋がりたい #早起きチャレンジ
- 投稿日:2020-01-18T19:09:20+09:00
Vue.jsを使って簡単なtodoアプリを作ってみた
Vue.jsを使って簡単なtodoアプリを作ってみた
身内用進捗内容報告記事
初めに
Vue.jsが初めてのjsライブラリとなったが苦難することなく作ることができた
コード
HTML
<div id="app"> <div> <li v-for="(todo, i) in todos"> {{ todo.text }} <button @click="remove(i)"> 削除 </button> </li> </div> <input v-model="todo" > <button @click="add"> 追加 </button> </div>javascript
var app = new Vue({ el: '#app', data: { todos: [], todo: 'sample', }, methods: { add() { this.todos.push({ text: this.todo }) }, remove(i) { this.todos.splice(i, 1) } } })
- 投稿日:2020-01-18T18:36:31+09:00
graphql clientはApolloよりfetchでやった方がいいのではないかという件
リポジトリ
https://github.com/gqlkit-repos/gqlkit-store
graphql client storeプラグインです。
これを、各フレームワークのcontextに突っ込んでやろうという魂胆
reduxはreact、vuexはvueという感じですが
クロスフレームワークでかつgraphqlの為の
client&storeというのを作ってみました。
nuxt.jsでは検証済resolverファーストアーキテクチャのgraphql client設計
. ├── index.js └── resolvers ├── Mutation │ ├── createStaff.js │ └── deleteStaff.js ├── Query │ └── staffs.js ├── cache.js └── client.jsたったのこれだけ
と言いたいところですが
これを例えば、nuxtで使うなら、nuxtのpluginsディレクトリに突っ込むので
一見、まぁまぁな規模感あるように見えます。というか、そもそもなのですが
クライアント側のvuexとかreduxだとかのストアが抱えている課題って
もうストアはストアで別個のフレームワークと捉えて設計した方が良いレベルではないかと思うので
これはなんか正解路線なのでは?と思っています。ちなみにQueryとMutation以下のjsファイルはデモ用のファイルです。
queryのコードの見た目も、mutationのコードの見た目もほぼ同じ
上のディレクトリツリーのstaffs.jsはこんな感じ
staffs.jsimport client from '../client' import cache from '../cache' import gql from 'graphql-tag' export const demand = gql` query { staffs { id name age sex } } ` export default async variables => { let re if (!cache.has('staffs').value()) { const { staffs } = await client.req(demand) cache.set('staffs', staffs).write() re = staffs } else { re = cache.get('staffs').value() } return re }mutationにあたるcreateStaff.jsはこんな感じ
mutationはrefetchしたいモジュールファイルをrefetchという名前で読み込みます。
そんでもってqueryでもmutationでも
クライアントツールはclient.req(demand, variables)
(variablesが必要ない場合は省略)createStaff.jsimport client from '../client' import cache from '../cache' import gql from 'graphql-tag' import refetch from "../Query/staffs"; export const demand = gql` mutation($name: String!, $age: Int!, $sex: String!) { createStaff(name: $name, age: $age, sex: $sex) { id name age sex } } ` export default async variables => { const res = await client.req(demand, variables) const staff = res.createStaff const staffs = cache.get('staffs').value() staffs.push(staff) cache.set('staffs', staffs).write() return refetch() }クライアントツールは21行のコードでqueryもmutationもOK!
fetchで書くとこんなシンプルになるとは正直、驚きました。
ただ、今後subscription導入のことも考えると
もうちょっと大きいファイルになる可能性はあります。
まだ、subscriptionは未経験なので。。。client.jsconst GQLKIT_SERVER_END_POINT = process.env.GQLKIT_SERVER_END_POINT || 'http://localhost:4000/query' const method = 'POST' const headers = { 'Content-Type': 'application/json', Accept: 'application/json' } export default { async req(demand, variables) { const res = await fetch(GQLKIT_SERVER_END_POINT, { method, headers, body: JSON.stringify({ query: demand.loc.source.body, variables }) }) const { data } = await res.json() return data } }キャッシュはどうするか?
キャッシュは完全切り分けの方針です。
Apolloのapiはキャッシュと密結合ですが
resolverファーストなgraphql clientアーキテクチャでは
キャッシュは完全別扱いにして
好きなin memory dbを使えば良いという方針で考えます。
resolverを書いて解決すれば良いじゃないか!という方針です。僕は今のところlowdbを使っています。
ここはお好みで好きなもの使ってよしです。cache.jsimport low from 'lowdb' import Memory from 'lowdb/adapters/Memory' const cache = low(new Memory()) cache.defaults({}).write() export default cacheまとめ
apolloがなぜ、あれほどまでに複雑なアーキテクチャを取っているのか
僕の知らない何らかの理由でああなっている可能性も大なのでこれがベストアンサーだなんて到底言い切る事はできないです。
これはあくまで、1アイデアというものです。あと、jsのオブジェクトをgormっぽい書き方で読み書き出来るような
ツールをご存知の方おられましたら教えて頂けると嬉しいです。サーバーとクライアントで違うノウハウというのを最小限に抑えたい
ということで
graphqlサーバーのアーキテクチャを真似たプラグインを作ってみたという話でした。
- 投稿日:2020-01-18T12:04:35+09:00
クライアントサイドだけで日本語PDFを出力したときの文字化け・改行不具合をpdfmake最新版で再ビルドして直した話
はじめに
- 常用文字にサブセット化を行ったものを id:naoa_yさんが作ってくれているのでそれを使っても良い
- ビルド後のファイル(vfs_fonts.js)で 5MB -> 2.3MBぐらいに減ってる
- ただしbuildが5年前なのでpdfmakeのバージョンが0.1.20と古い
- 最新は0.1.63 (released this on 11 Dec 2019)
- 上記のpdfmake@0.1.20だと日本語化したときの文字改行(word wrap)が適切にうごかない&改行されたとき文字化けする不具合があった
- 私の場合、可変長の文字を動的に組み込む必要があり、自動改行はどうしても必要だった。1行に収まる短文のみであれば問題ないかと
- 最新だとなおるかもしれないと、pdfmakeを最新にして使いたかったので、buildし直した。結論、治った!
- Vueと書いてるけど、フォント差し替えるまでは共通だとおもう
- @watameさんの記事を参考にしました。ありがとうございます。
TL;DR
- このリポジトリにある、
build/pdfmake.min.js
,build/vfs_fonts.js
を使えばよいです。2つ合わせると10MB程度になります- https://github.com/yazashin/pdfmake/tree/0.1.63-ja
- フォントサイズを減らしたい・違うフォントにしたいって人は下記を読みましょう
日本語対応pdfmakeをビルドする
fork&cloneする
- https://github.com/bpampuch/pdfmake
- よしなに自分のリポジトリへ
cloneした後branchを0.1系に切り替える
- "This is unstable master branch for version 0.2.x, for stable version 0.1.x see branch 0.1."とのこと
日本語フォントを入手する
- ここは好み・用途によるとおもいますが、私のケースではこのフォントがマッチしました
- https://ipafont.ipa.go.jp/#jp
- IPAexゴシックのみ利用(等幅フォント)
- メリデメを考えた時、メリットが勝ったのでこれを選択
- 用途として、珍しい漢字もつかわれるのでサブセット化したとき文字化けの発生が怖い
- pdfmakeとvfs_fontsで合計して10MBぐらいになる
- pdfmake.min.js 1.2MB / vfs_fonts.js 8.1MB
- 印刷ページごとに都度読み込みタイプだと辛い印象がある
- vueのようなSPAでindex.htmlにscriptタグでglobalに登録してしまえば、大きな問題にはならないって考えて進めた
- ぶっちゃけ最近はネット回線高速なので、SPAで初回ロード時に1回発生する10MB程度ならほとんど気にならないと思う
- 安定したらServiceWorkerのキャッシュ対象にしてしまうのもありだとおもう
- SEO気にする必要がない業務系サービスなのでこの判断ができている。っていう前提があります。
- むしろSEO気にするサービスで、SPA & PDF生成が必要って、かなりニッチだとはおもうけどw
フォントを入れ替える
vfs_fonts.jsの更新
- buildFontsを実行
$ yarn install .... success Saved lockfile. ✨ Done in 216.37s. $ ./node_modules/.bin/gulp buildFonts [10:45:48] Using gulpfile ~/workspace/pdfmake/gulpfile.js [10:45:48] Starting 'buildFonts'... [10:45:48] Finished 'buildFonts' after 189 ms
- pdfmakeへフォントを反映
$ yarn run build ... WARNING in webpack performance recommendations: You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application. For more info visit https://webpack.js.org/guides/code-splitting/ [10:50:16] Finished 'build' after 23 s ✨ Done in 24.49s.
- warning出てるけど無視して進める
vfs_fonts.jsの確認
Vue(typescript)で確認
- 今回つくったpdfmake & vfs_fonts は、npmとして登録はせず、publicなフォルダに静的jsとして利用する
- npmにするとdeploy時にciでビルドするとき、フォントのビルドから必要になるとか、そこらへん面倒でこうした
- vue-cliのversionによって構成かわりますが、
VUE_PROJECT_DIR/public/static-js/
配下においたindex.htmlに登録
- ポイントして、
defer
を忘れずにつけましょう。10MB近いjsの読み込みでHTMLのパースが止まってしまいUXに影響がでますindex.html... ~~~ ... <div id="app"></div> <!-- built files will be auto injected --> </body> <script defer src="/static-js/pdfmake.min.js"></script> <script defer src="/static-js/vfs_fonts.js"></script> </html>Vueの実装
- Vueで使うときはこんなかんじ。手打ちなので間違ってるかもしれません。
pdf.vuedeclare var pdfMake: any; @Component({}) export default class PrintPdf extends Vue { createPdf() { pdfMake.fonts = { IPA_gothic: { normal: 'ipaexg.ttf', bold: 'ipaexg.ttf', italics: 'ipaexg.ttf', bolditalics: 'ipaexg.ttf', } }; const defaultStyle = 'IPA_gothic'; const docDefinition = { content: 'Hello 日本語!', defaultStyle: { font: defaultStyle } } pdfMake.createPdf(docDefinition).print(); } }Thanks
- 投稿日:2020-01-18T06:16:46+09:00
【Vuetify】<v-avator>を使って背景色を動的に変更するサンプル
はじめに
Vue.jsのUIコンポーネントライブラリであるVuetifyのうち、
<v-avator>
を使って色を動的に変更するサンプルを残します。環境
OS: macOS Catalina 10.15.1 Vue: 2.6.10 vuex: 3.1.2 vuetify: 2.1.0前提:
<v-avator>
とは?Vuetifyが提供している、以下のようなアバターをお手軽に実装出来るコンポーネントです。
※画像は公式ドキュメントより拝借しました。今回はこの
<v-avator>
を使って、中の文字列が
- 1 -> 黄色
- 2 -> 青
- 3 -> 緑
と、それぞれ背景色を変わるというサンプルコードを残します。
※Vuetifyのインストールがまだの方は、以下記事の後半等ご参照下さい。
【環境構築】Docker + Rails6 + Vue.js + Vuetifyの環境構築手順 - Qiita結論:コード
子コンポーネント
シンプルに
<v-avator>
だけを表示するようにしています。SampleChild.vue<template> <div> <v-avatar :color="avatorColor" > {{ displayName }} </v-avatar> </div> </template> <script> export default { name: 'SampleChild', //propsでv-avator部分を親コンポーネント側で動的に変更出来るようにする props: { displayName: '', avatorColor: '', } } </script>親コンポーネント
続いて親コンポーネントです。
v-for
を使って、先程作成した子コンポーネントをnumbers
という配列の要素数に応じて表示するようにしています。SampleParent.vue<template> <div> <!--配列numbersの要素数だけ表示する--> <div v-for="number in numbers" :key="number.id"> <!--子コンポーネントがpropsで渡した2つの変数を親側で使用--> <SampleChild :avatorColor="color[number.name]" :displayName="number.name" > </SampleChild> </div> </div> </template> <script> import SampleChild from './SampleChild'; export default { name: 'SampleParent', components: { SampleChild, }, data() { return { //表示される配列 numbers: [ {id:1, name:1}, {id:2, name:2}, {id:3, name:3}, ], //number.nameに応じて変わる背景色 color: { 1: 'yellow', 2: 'blue', 3: 'green', } } }, } </script>出力
無事、動的に背景色が変更されていることが確認出来ました!
確認:
number.n
を変更した場合の出力以下のように変更してみます。
SampleParent<template> ...略 </template> <script> //...略 data() { return { numbers: [ {id:1, n:2}, //ここのnを1->2へ変更 {id:2, n:2}, {id:3, n:3}, ], //...略 } } }, } </script>変更結果
無事、
numbers.n
に応じて背景色が変化してくれています!この考えを応用すれば、特定のステータスの人だけアバターの背景色を変える、などの表示が可能となります。
おわりに
最後まで読んで頂きありがとうございました
どなたかの参考になれば幸いです
参考にさせて頂いたサイト(いつもありがとうございます)
- 投稿日:2020-01-18T02:35:41+09:00
Nuxt.js firebase GoogleAuth
plugins/firebase.jsimport firebase from "firebase"; if (!firebase.apps.length) { const firebaseConfig = { apiKey: "<apiKey>", authDomain: "<authDomain>", databaseURL: "<databaseURL>", projectId: "<projectId>", storageBucket: "<storageBucket>", messagingSenderId: "<messagingSenderId>", appId: "<appId>", measurementId: "<measurementId>" }; firebase.initializeApp(firebaseConfig); } export default firebase;/pages/login.vue<template> <div> <button type="button" @click="signIn()" class="btn btn-primary">Sign in with Google</button> <div><router-link to="/mypage" >マイページ</router-link></div> </div> </template> <script> import firebase from "@/plugins/firebase"; export default { created: function() { const user = firebase.auth().currentUser; console.log('user', user); if (user) { this.$router.push('/mypage'); } }, methods: { signIn() { const provider = new firebase.auth.GoogleAuthProvider(); firebase.auth().signInWithPopup(provider).then((result) => { console.log('result', result); this.$router.push('/mypage'); }).catch((error) => { console.error(error.code); }); } } } </script>pages/mypage.vue<template> <div> <div>{{email}}</div> <button type="button" @click="signOut()" class="btn btn-primary">ログアウト</button> <div><router-link to="/login" >ログイン画面</router-link></div> </div> </template> <script> import firebase from "@/plugins/firebase"; export default { data: function() { return { email: null }; }, created: function() { const user = firebase.auth().currentUser; console.log('user', user); if (user) { this.email = user.email; } else { this.$router.push('/login'); } }, methods: { async signOut(){ await firebase.auth().signOut(); this.$router.push('/login'); }, } } </script>