- 投稿日:2019-11-24T23:35:03+09:00
CompositionAPIでdayjsをぶっこむ
Composition APIはthisを潰す
- dayjsインストール
- pluginsにdayjsを追加
- TypeScript用の設定
- 使い方
dayjsインストール
$ yarn add dayjs
pluginsにdayjsを追加
理由は後述しますが,plugins/dayjs/dayjs.jsとしています。
一個専用のディレクトリを切ります。plugins/dayjs/dayjs.jsimport Vue from 'vue' import dayjs from 'dayjs' import 'dayjs/locale/ja' import relativeTime from 'dayjs/plugin/relativeTime' import LocalizedFormat from 'dayjs/plugin/localizedFormat' import weekday from 'dayjs/plugin/weekday' dayjs.locale('ja') dayjs.extend(weekday) dayjs.extend(relativeTime) dayjs.extend(LocalizedFormat) Vue.prototype.$dayjs = dayjsnuxt.cofig.jsplugins: ['@/plugins/dayjs/dayjs']TypeScript用の設定追加
@/plugins/dayjs/dayjs.d.ts// 1. Make sure to import 'vue' before declaring augmented types import Vue from 'vue' import dayjs from 'dayjs' // 2. Specify a file with the types you want to augment // Vue has the constructor type in types/vue.d.ts declare module 'vue/types/vue' { // 3. Declare augmentation for Vue interface Vue { $dayjs(date?: dayjs.ConfigType, option?: dayjs.OptionType, locale?: string): dayjs.Dayjs; } } declare module 'dayjs' { interface Dayjs { weekday(): number weekday(value: number): Dayjs } }tsconfig.json"files": [ "plugins/dayjs/dayjs.d.ts" ]この型定義ファイルを書くときに,@/dayjs.d.tsって感じで置くと,pluginのdayjsをうまく読み込んでくれなかった。plugins下だと読み込んでくれるので,型定義ファイルとpluginファイルを機能ごとにひとまとめにする構成にした。
使い方
component.vue<script lang="ts"> import { createComponent, reactive } from '@vue/composition-api' export default createComponent({ setup (props: {}, { root: { $dayjs } }) { const state = reactive({ now: $dayjs().format('MM月DD日 (ddd)') }) return { state } } }) </script>下記でもいけますが,上記だと省略できていい感じです。
setup (props: {}, context) { const state = reactive({ now: context.root.$dayjs().format('MM月DD日 (ddd)') }) return { state } }参考
僕はとりあえず dayjs !
SETUP VUEX IN VUE.JS 3 COMPOSITION API WITH EXAMPLE
Some documentation/guidance to Typescript and using dayjs as a Vue.js plugin #611
- 投稿日:2019-11-24T23:35:03+09:00
NuxtのCompositionAPIでdayjsをぶっこむ
Composition APIはthisを潰す
- dayjsインストール
- pluginsにdayjsを追加
- TypeScript用の設定
- 使い方
dayjsインストール
$ yarn add dayjs
pluginsにdayjsを追加
理由は後述しますが,plugins/dayjs/dayjs.jsとしています。
一個専用のディレクトリを切ります。plugins/dayjs/dayjs.jsimport Vue from 'vue' import dayjs from 'dayjs' import 'dayjs/locale/ja' import relativeTime from 'dayjs/plugin/relativeTime' import LocalizedFormat from 'dayjs/plugin/localizedFormat' import weekday from 'dayjs/plugin/weekday' dayjs.locale('ja') dayjs.extend(weekday) dayjs.extend(relativeTime) dayjs.extend(LocalizedFormat) Vue.prototype.$dayjs = dayjsnuxt.cofig.jsplugins: ['@/plugins/dayjs/dayjs']TypeScript用の設定追加
@/plugins/dayjs/dayjs.d.ts// 1. Make sure to import 'vue' before declaring augmented types import Vue from 'vue' import dayjs from 'dayjs' // 2. Specify a file with the types you want to augment // Vue has the constructor type in types/vue.d.ts declare module 'vue/types/vue' { // 3. Declare augmentation for Vue interface Vue { $dayjs(date?: dayjs.ConfigType, option?: dayjs.OptionType, locale?: string): dayjs.Dayjs; } } declare module 'dayjs' { interface Dayjs { weekday(): number weekday(value: number): Dayjs } }tsconfig.json"files": [ "plugins/dayjs/dayjs.d.ts" ]この型定義ファイルを書くときに,@/dayjs.d.tsって感じで置くと,pluginのdayjsをうまく読み込んでくれなかった。plugins下だと読み込んでくれるので,型定義ファイルとpluginファイルを機能ごとにひとまとめにする構成にした。
使い方
component.vue<script lang="ts"> import { createComponent, reactive } from '@vue/composition-api' export default createComponent({ setup (props: {}, { root: { $dayjs } }) { const state = reactive({ now: $dayjs().format('MM月DD日 (ddd)') }) return { state } } }) </script>下記でもいけますが,上記だと省略できていい感じです。
setup (props: {}, context) { const state = reactive({ now: context.root.$dayjs().format('MM月DD日 (ddd)') }) return { state } }参考
僕はとりあえず dayjs !
SETUP VUEX IN VUE.JS 3 COMPOSITION API WITH EXAMPLE
Some documentation/guidance to Typescript and using dayjs as a Vue.js plugin #611
- 投稿日:2019-11-24T21:40:14+09:00
kintone カスタマイズを Vue.js + TypeScript + Pug + SCSS でモダンに開発する (1) 環境構築編
前置き
昨今の一般的な Web システムの開発者の皆さんにおかれましては今更かよとおっしゃる向きもあるかも知れませんが、kintone カスタマイズ界隈でも
Vue.js
とかReact
などのフロントエンドフレームワークが採用されている事例が増えて来ているかと思います。
公式の GitHub でも様々な言語向けに開発キットが公開されており、特に kintone が主戦場とするJavaScript / TypeScript
回りでは多くの便利なツール類が利用可能になっています。
kintone は公式の開発者向けドキュメントがけっこう充実しているものの、公式以外の(Qiita やはてなのような)ユーザー発信の技術記事ってそれほど多くない気がします。
そう言う背景もあり、一般的には広く採用され市民権を得ているフレームワークやトレンドの類が kintone カスタマイズ界隈に広まるまでタイムラグがあると言うか、情報の成熟が遅いように感じます。
と言うわけで、この記事では kintone カスタマイズにもモダンな開発手法を導入する手順を解説していきます。前提
以下の環境で作業しています。
- macOS Catalina
- Homebrew 2.1.16
- Node.js 13.1.0
- VisualStudio Code 1.40.1
もう少し古い環境(Node.js 10.x とか)でもほとんど変わらないかと思いますし、Windows だって別に構いません。
未だに生の JavaScript を書く文化のカスタマイズをやっているような開発者の皆さんも割と多いと思われますが、Node.js
くらいはなんとか自前で用意してください。今回のゴール
上述の「モダンな開発手法」と言うのは、具体的には以下の通りです。
- Vue.js (フロントエンドフレームワーク)
- TypeScript (静的型付け・クラスベースの開発言語)
- Prettier (コードフォーマッタ)
- ESLint (静的コード検証)
- Sass / SCSS (CSSプリプロセッサ)
- Pug (HTMLプリプロセッサ)
- Jest (単体テストツール)
- Babel (トランスパイラ)
- Webpack (モジュールバンドラ)
これらを利用して kintone カスタマイズの開発を便利に進め、かつ高品質な開発成果を出せるような環境を構築するのがこの記事の目的です。
フロントエンド界隈でバリバリやってる人たちにとっては今更そんな話?と言うレベルかも知れませんが。それぞれがどのようなものかと言うのは省くか最低限の説明に留めます。
他所様に素晴らしい記事がたくさんありますので。記事分量が多くなりそうなので、回数を分けて書いていきたいと思います。
今回は
Vue CLI
をインストールVue CLI
でプロジェクトを作るPug
をインストール- デフォルトのコンテンツを
Pug
で書き直してみるまでを説明します。
この記事ではまだ kintone 関係ないですね。(1) 環境構築編
Vue CLI をインストールする
上記のツール類を 1 つ 1 つ準備していくのは大変面倒なので、コマンド一発でよしなにしてくれる Vue CLI を利用します。
執筆時点の最新バージョンはVue CLI 4.0.5
です。
少し前までは3.x
でしたが、今のところ大きな違いは実感していません。インストールは公式で説明されている手順通りにやるだけです。
ターミナル(Windows の人ならコマンドプロンプトなり PowerShell なり Git Bash なり)で作業します。% yarn global add @vue/cli正しくインストールされたかどうかは以下のコマンドで確認できます。
% vue --version @vue/cli 4.0.5
正しく 4.0.5 がインストールされました。
Vue プロジェクトを作成する
これも公式の説明通りです。
Vue UI
はちゃんと使えば便利なのかも知れませんがコマンドで事足りるしなーとは思っています。% vue create (プロジェクト名)言わずもがなですが、カレントディレクトリにプロジェクトが作成されます。
今回は kintone-vue-ts と言うプロジェクトを作ることにしましょう。
なお、プロジェクト名には大文字を使う事はできません。単語区切りはハイフンかアンダースコアを用いれば良いでしょう。% vue create kintone-vue-tsここからウィザード形式でいろいろ訊かれていきます。
以下では筆者がよく使う設定で進めますが、組織やチームの方針で定めがあればそれに従うべきです。
ソロ活動ならお好みで。Please pick a preset:
プリセットに沿ってプロジェクトを作るか、アラカルト的に機能を選んでいくかを選択します。
Manually select features を選択して(下キーでフォーカスを移動して) Enter します。
Check the features needed for your project:
利用する機能を選択します。
下キーでカーソルを移動させ、スペースでオン/オフを切り替えられます。(緑がオン)
今回は
- Babel
- TypeScript
- CSS Pre-processors
- Linter / Formatter
- Unit Testing
Use class-style component syntax?
TypeScript
でクラス形式の記法を使用するかどうかの選択です。
Y として次に進みます。
Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)?
TypeScript
はそれ自体にJavaScript
へのコンパイラが含まれていますが、Babel
でトランスパイルを担当させるかどうかを選択します。
Y として次に進みます。
Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default):
CSS プリプロセッサとして何を使用するかの選択です。
初期値の Sass/SCSS (with dart-sass) のまま次に進みます。
Pick a linter / formatter config:
コードの静的解析とフォーマッタとして何を使用するかの選択です。
ここでは初期値の ESLint with error prevention only のまま次に進みますが、ここは地味に組織やチームの方針が影響するところかと思います。
Pick additional lint features:
自動的なコードの静的解析・フォーマット処理をどのタイミングでやるかの選択です。
最初の選択は保存時に実行、2 つ目の選択肢はコミット時に実行です。
保存時の自動補正はして欲しいがコミット時にコードをいじられるのは嫌なので、(初期値の)1 つ目だけにチェックを入れた状態で次に進みます。
Pick a unit testing solution:
単体テストツールとして何を使用するかの選択です。
Jest を選択して次に進みます。
Where do you prefer placing config for Babel, PostCSS, ESLint, etc.?
Babel
やESLint
などの設定をpackage.json
に持たせるか、個別の設定ファイルとして持たせるかの選択です。
package.json
に設定を記述していくとぐちゃぐちゃのスパゲティになるのは目に見えているので初期値の In dedicated config files を選択したまま次に進みます。
Save this as a preset for future projects?
今回作成した一連の設定をプリセットととして保存するかどうかの選択です。
今回は N として保存せずに進めますが、同じ設定でプロジェクトを開始する事が見込まれるなら分かりやすい名前をつけて保存しても良いでしょう。
選択はこれで終了です。
設定に応じて関連ライブラリ等がまとめてインストールされ、初期設定まで勝手にやってくれます。便利ですね!
プロジェクトを実行してみる
カレントディレクトリの下に、今作成したプロジェクト kintone-vue-ts のフォルダができています。
これを VS Code で開いてみます。
この状態で、既に動作確認ができます。
VS Code 上で新しいターミナルを開いて、以下のようにしてみましょう。% yarn serveブラウザで http://localhost:8080/ を開くとビルドされた内容が確認できます。
また、この状態ではソースの変更にリアルタイムに追従してコンテンツがリフレッシュされます。
試しにApp.vue
の4行目のHelloWorld
に渡しているmsg
属性の値を、App.vue<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="はじめての Vue.js + TypeScript App です!"/> </div> </template>ブラウザが自動的にリロードされて変更点が反映されます。すごいですね!
プロジェクトを紐解く
いろいろファイルが作成されていますが、触っていくのは基本的に
src
フォルダの中のファイルが中心です。
(そのほかのファイルは必要に応じて触れていきます)エントリポイントになるファイルが
main.ts
です。main.tsimport Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')同じフォルダの
App.vue
と言うファイルをインポートして、レンダリングしたものを#app
と言う要素にマウントしますぜ、と言う記述です。次にその
App.vue
を見てみます。App.vue<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator'; import HelloWorld from './components/HelloWorld.vue'; @Component({ components: { HelloWorld, }, }) export default class App extends Vue {} </script> <style lang="scss"> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>コードの細かい解説はしませんが、1 つの
.vue
ファイル内に見慣れた HTML ソースと TypeScript のスクリプトと CSS が混在して 1 つの コンポーネント を構成しているのが分かると思います。
また、ソース中程では./components/HelloWorld.vue
と言うファイルをインポートしているのも確認できます。
インポートしたHelloWorld
コンポーネントは HTML の 4 行目でmsg
と言う属性を渡しつつ使用されています。
Vue.js
ではこんな風に構成要素をコンポーネントとして分割して独立性を高めた形で実装していきます。Pug を導入する
HTML を簡単に書くためのプリプロセッサである
Pug
を導入します。
Vue CLI
のプラグインとして導入するのが簡単です。% vue add pug最終的に以下のように出れば OK です。
? Installing vue-cli-plugin-pug... yarn add v1.19.1 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... [4/4] ? Building fresh packages... success Saved lockfile. success Saved 31 new dependencies. info Direct dependencies └─ vue-cli-plugin-pug@1.0.7 info All dependencies (中略) ✨ Done in 15.92s. ✔ Successfully installed plugin: vue-cli-plugin-pugこの後に
ERROR Error: Plugin vue-cli-plugin-pug does not have a generator.と出ることもあるかもですが、とりあえずインストール自体はできているので大丈夫です。
それでは、
App.vue
の HTML 部分をPug
で書き直してみましょう。変更前のコードはこうですが、
App.vue<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/> </div> </template>
Pug
で書き換えるとこうなります。App.vue<template lang="pug"> #app img(alt="Vue logo" src="./assets/logo.png") HelloWorld(msg="Welcome to Your Vue.js + TypeScript App") </template>
template
タグに対して属性lang="pug"
と書くことで、Pug
による記述であることを明示します。ざっくり
Pug
の記法を説明すると、
- タグは <> で囲わずにそのまま書く
- タグの階層構造はインデントで記述する
div
は省略できるid="hoge"
は#hoge
と記述するclass="fuga"
は.fuga
と記述する- クラスが複数ある場合(
class="fuga piyo"
)は.fuga.piyo
と記述する- 属性はカッコで囲って記述する
- 終了タグは不要
と言う感じです。
この程度の短いコードだとPug
の導入効果は今ひとつ実感しづらいですが、数 10 行とか 100 行レベルのコードになると特に終了タグが不要と言う特性が格段に効いて来ます。次回は
とりあえず今回は下準備と言う事でここまで。kintone の話ほんと全然出て来ませんでしたすみません。
次回はいよいよこう言うモダンな開発環境をどう kintone にはめていくかをお話ししたいと思います。
- 投稿日:2019-11-24T17:56:53+09:00
Nuxt.jsでFirebaseUIを使ってログイン機能を作る
Nuxt.jsでFirebaseUIを使ってログイン機能を作ってみました。
ソースコードはこちらにあります(https://github.com/mk422756/nuxt-firebaseui-sample)FirebaseUIとは?
FirebaseUI(https://github.com/firebase/firebaseui-web) はFirebase Authenticationを簡単に使用するために、Firebaseがあらかじめ用意してくれているUIです。
今回はNuxt.jsを使用してFirebaseUIのWeb版を組み込んでいきます。
セットアップ
必要なもの
- Nuxt.jsをインストールするためのNode.js環境
- Firebaseプロジェクトを作成するアカウント
Firebaseのセットアップ
Firebaseのプロジェクトを作成します。
こちらの記事(https://blog.katsubemakito.net/firebase/firebase-make-newproject) を参考にしてプロジェクトを作成します。
プロジェクト名は適当で構いません。次に、Authenticationで使用するプロバイダーを有効化します。
コンソールから [Authentication] > [ログイン方法] に移動し、各プロバイダの設定を行います。
とりあえず、メールアドレスとGoogle認証を有効化します。
Twitter等の外部IDプロバイダを設定しようとすると、TwitterAPIキー等が必要になります。
APIキーを取得するには申請が必要だったり面倒なので、今回は省略します。
Nuxt.jsのセットアップ
Nuxt.jsと必要なライブラリをインストールしていきます。
npx create-nuxt-app nuxt-firebaseui-sampleNuxt.jsのセットアップです。設定を色々聞かれますがデフォルトでOKです。
セットアップが完了したら、次はライブラリをインストールしていきます。
cd nuxt-firebaseui-sample npm install --save firebase firebaseui npm install --save-dev @nuxtjs/dotenvfirebaseとfirebaseuiは認証に必要です。
@nuxtjs/dotenvは.env設定ファイルから環境変数を設定するために使用します。APIキーなどの設定値をGitで管理したくない場合に便利です。dotenvを使用するために、nuxt.config.jsに以下の設定を追加します。
export default { buildModules: ['@nuxtjs/dotenv'], };次にプロジェクトのルートに.envを作成し、Firebaseの設定を記述します。
Firebaseの設定値は、作成したFirebaseプロジェクトの [プロジェクトの設定] > [アプリの追加] でアプリを追加すると表示されます。FB_API_KEY=AIzaxxxx-xxxxxxxxxxxxxxxx FB_AUTH_DOMAIN=uitest-xxxxx.firebaseapp.com FB_DATABASE_URL=https://uitest-xxxxx.firebaseio.com FB_PROJECT_ID=uitest-xxxxx FB_STORAGE_BUCKET=uitest-xxxxx.appspot.com FB_MESSAGE_SENDER_ID=3000000000 FB_APPID=1:300000000:web:671xxxxxxxxxxxxxx実装
FirebaseUIの表示
今回は認証画面が表示されて、ログインすることができるだけのシンプルなページを作成します。
まず、components/Auth.vue を作成し、認証用のコンポーネントを作成します。<template> <div> <div id="firebaseui-auth-container"></div> </div> </template> <script> export default { mounted() { const firebase = require("firebase"); const firebaseui = require("firebaseui"); require("firebaseui/dist/firebaseui.css"); const uiConfig = { signInSuccessUrl: "/", signInOptions: [ firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID, firebase.auth.EmailAuthProvider.PROVIDER_ID, firebase.auth.GoogleAuthProvider.PROVIDER_ID, firebase.auth.FacebookAuthProvider.PROVIDER_ID, firebase.auth.TwitterAuthProvider.PROVIDER_ID, firebase.auth.GithubAuthProvider.PROVIDER_ID, firebase.auth.PhoneAuthProvider.PROVIDER_ID ] }; const firebaseConfig = { apiKey: process.env.FB_API_KEY, authDomain: process.env.FB_AUTH_DOMAIN, databaseURL: process.env.FB_DATABASE_URL, projectId: process.env.FB_PROJECT_ID, storageBucket: process.env.FB_STORAGE_BUCKET, messagingSenderId: process.env.FB_MESSAGE_SENDER_ID }; firebase.initializeApp(firebaseConfig); const ui = new firebaseui.auth.AuthUI(firebase.auth()); ui.start("#firebaseui-auth-container", uiConfig); } }; </script>mountedの中でFirebaseUI等をrequireしてるのは、FirebaseUIはSSRに対応していないからです。
SPAで動かすのならimportで大丈夫だと思います。
次に、Firebaseの初期化を行いFirebaseUIをスタートします。引数で指定したidのタグにFirebaseUIが表示されます。
Firebaseの初期化は.envで設定した設定値を使用します。
FirebaseUIのパラメータで使用するプロバイダを指定します。Twitter等は有効化していないためログイン等は失敗しますが、とりあえず表示させておきます。次に、pages/index.vueからAuth.vueを使用します。
<template> <div> <div class="container"> <div> <h1 class="title">FirebaseUI サンプル</h1> <auth /> </div> </div> </div> </template> <script> import Auth from "~/components/Auth.vue"; export default { components: { Auth } }; </script> <style> .container { margin: 0 auto; min-height: 100vh; display: flex; justify-content: center; align-items: center; text-align: center; } .title { font-family: "Quicksand", "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; display: block; font-weight: 300; font-size: 30px; color: #35495e; letter-spacing: 1px; } </style>開発用サーバーを起動します。
npm run devこれでFirebaseUIが表示され、新規登録とログインができるようになります。
Vuexで状態管理
ここまでで作成したもので、新規登録とログインはできるようになりました。
しかし、ログイン状態を管理していないため画面表示等ができません。
ですのでVuexを使用して状態を管理していきます。まず、store/user.jsを作成し、ログインユーザーのメールアドレスと表示名を管理します。
export const state = () => ({ email: '', displayName: '', }); export const actions = { login ({commit}, user) { commit ('setEmail', user.email); commit ('setDisplayName', user.displayName); }, logout({commit}) { commit ('setEmail', ''); commit ('setDisplayName', ''); }, }; export const mutations = { setEmail (state, email) { state.email = email; }, setDisplayName (state, displayName) { state.displayName = displayName; }, }; export const getters = { isLogin: state => { return !!state.email; }, displayName: state => { return state.displayName; }, };ログイン時と、ログアウト時のアクションを定義しています。
また、ログイン状態と表示名を返すゲッターも定義しています。次に、components/Auth.vueから定義したアクションを使用します。
<template> <div> <div v-show="isLogin"> <p class="login">ログイン中</p> <p class="name">名前:{{ displayName }}</p> <button @click="logout">ログアウト</button> </div> <div v-show="!isLogin" id="firebaseui-auth-container"></div> </div> </template> <script> export default { computed: { isLogin() { return this.$store.getters["user/isLogin"]; }, displayName() { return this.$store.getters["user/displayName"]; } }, methods: { logout() { this.$store.dispatch("user/logout"); const firebase = require("firebase"); firebase .auth() .signOut() .then(() => { console.log("ログアウトしました"); }); } }, mounted() { const firebase = require("firebase"); const firebaseui = require("firebaseui"); require("firebaseui/dist/firebaseui.css"); const uiConfig = { signInSuccessUrl: "/", signInOptions: [ firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID, firebase.auth.EmailAuthProvider.PROVIDER_ID, firebase.auth.GoogleAuthProvider.PROVIDER_ID, firebase.auth.FacebookAuthProvider.PROVIDER_ID, firebase.auth.TwitterAuthProvider.PROVIDER_ID, firebase.auth.GithubAuthProvider.PROVIDER_ID, firebase.auth.PhoneAuthProvider.PROVIDER_ID ] }; const firebaseConfig = { apiKey: process.env.FB_API_KEY, authDomain: process.env.FB_AUTH_DOMAIN, databaseURL: process.env.FB_DATABASE_URL, projectId: process.env.FB_PROJECT_ID, storageBucket: process.env.FB_STORAGE_BUCKET, messagingSenderId: process.env.FB_MESSAGE_SENDER_ID }; firebase.initializeApp(firebaseConfig); const ui = new firebaseui.auth.AuthUI(firebase.auth()); ui.start("#firebaseui-auth-container", uiConfig); firebase.auth().onAuthStateChanged(user => { this.$store.dispatch("user/login", user); }); } }; </script> <style scoped> .login { font-size: 1.25rem; } .name { font-size: 1.1rem; } </style>firebase.auth().onAuthStateChangedを使用すると、ログイン状態の変更をトリガーにして処理が行われます。
これを使用して、ログイン状態をVuexに渡します。
また、ログイン中であればユーザーの表示名とログアウトボタンを表示します。
logoutメソッドでは、signOutメソッドを使用し、signOutに成功するとVuexの状態をリセットするアクションを呼び出しています。これで、Vuexを使用してログイン状態を保持し、ログイン状態に応じて画面表示を切り替えることができました。
FirebaseUIの日本語化
各言語にローカライズするためには、自分でFirebaseUIをビルドする必要があります。
すいませんが、今回は省略します。。。
firebaseui-ja(https://www.npmjs.com/package/firebaseui-ja) というパッケージも存在しますが、バージョンの更新が止まっているので注意が必要です。所感
非常に簡単にログイン画面を作成できるのでとても便利だと思いました。
特にTwitter等のIDプロバイダとの連携を自前のUIで作成すると、IDプロパイダごとの処理とUIが必要なので、その手間が省けるのが非常に便利だと思いました。
- 投稿日:2019-11-24T16:34:31+09:00
[Vue.js][Jest]コンポーネントの単体テストを実行できるようにする
既存のVueアプリにコンポーネントテストを追加しようとしたときのお話です。
公式にサポートページがあるからそれでやればできるかなと思ったけど、
そのままの手順だといくつか躓いたので自分の対応を残しておきます。
Vueのバージョンが最新じゃないのが原因かもしれない。。環境
Version macOS Mojave 10.14.6 vue 2.5.18 node 10.5.0 セットアップ
公式サポート
Jest を使用した単一ファイルコンポーネントのテスト | Vue Test Utils
jestのインストール
yarn add --dev jest @vue/test-utilspackage.json{ "scripts": { ... + "test": "jest" ... } }*.vueファイルを処理するための設定
yarn add --dev vue-jestpackage.json+ "jest": { + "moduleFileExtensions": [ + "js", + "json", + "vue" + ], + "transform": { + ".*\\.(vue)$": "vue-jest" + } + },
webpackのエイリアス設定
package.json"jest": { ... + "moduleNameMapper": { + "^@/(.*)$": "<rootDir>/src/$1" + } },
Babel設定
yarn add --dev babel-jest babel-corepackage.json"jest": { ... "transform": { ... + "^.+\\.js$": "<rootDir>/node_modules/babel-jest" }, },
.babelrc+ { + "presets": [[ "@babel/env", { "modules": false } ]], + "env": { + "test": { + "presets": [[ "@babel/env", { "targets": { "node": "current" } } ]] + } + } + }
自分の場合はここまでやるとテストを実行することが出来ました。
テストの作成
ディレクトリ構成
ベストプラクティスはわからないけど、srcフォルダと同じ階層となるようにtestフォルダを作成しました。
├─src │ └─Components │ └─hoge.vue └─test └─Components └─hoge.spec.jsテストの記述
hoge.spec.jsimport { mount } from '@vue/test-utils' import hoge from '@/components/hoge.vue' describe('Component', () => { test('is a Vue instance', () => { const wrapper = mount(hoge) expect(wrapper.element).toMatchSnapshot() }) })公式サポートのサンプルを元に作成しています。
(toMatchSnapshot()
って何をテストしているのだろう。。)テストの実行
yarn test
でテストが実行されます。
問題なく実行されれば下記のようなログが出ます!Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 1 passed, 1 total Time: 1.749s, estimated 2s Ran all test suites. ✨ Done in 2.61s.おわりに
ここまででテストを実行する土台が整いました。
これから実際のコンポーネントテストをどのように書くか学んでアウトプットします!
- 投稿日:2019-11-24T15:52:41+09:00
[AWS SDK for PHP]署名付きPOSTを使い、直接クライアント側からAWS S3にファイルアップロード(PostObjectV4インスタンスを使用)
以下の流れで実装しました。
- PostObjectV4 のインスタンスを使い、署名付きPOSTに必要な情報を発行
- Vue.jsで、アプリケーションサーバを介さず、クライアント側から直接S3にファイルをアップロード
準備
AWS
S3でバケットを作成してください。
※IAMの設定は省略しますS3 CORSの設定
CORSを設定しておかなければなりません。
<?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <MaxAgeSeconds>3000</MaxAgeSeconds> <AllowedHeader>*</AllowedHeader> </CORSRule> </CORSConfiguration>AWS-SDK-PHPのインストール
$ composer require aws/aws-sdk-php実装
署名付きPOSTに必要なデータを発行するPHPの処理と、ファイルアップロードするVue.jsの処理を記載します。
実装した処理は以下の通りです。
- 署名付きPOSTに必要なデータ形式のjsonを返すAPI
- Vue.jsから↑のAPIを叩き、レスポンスを取得
- レスポンスを利用し、ファイルアップロード
API(PHP)
<?php // laravel apiのcontrollerの処理を一部抜粋 public function getPresignedUrl(Request $request) { $s3Client = new \Aws\S3\S3Client([ 'credentials' => [ 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), ], 'region' => env('AWS_DEFAULT_REGION'), 'version' => 'latest' ]); $bucket = env('AWS_BUCKET'); $requestData = $request->all(); $formInputs = [ 'acl' => 'public-read', 'key' => 'hoge/' . $requestData['filename'] . '.' . $requestData['fileext'], ]; $options = [ ['acl' => 'public-read'], ['bucket' => $bucket], ['starts-with', '$key', 'hoge/'], ]; $expires = '+20 minutes'; $postObject = new \Aws\S3\PostObjectV4( $s3Client, $bucket, $formInputs, $options, $expires ); $formAttributes = $postObject->getFormAttributes(); $formInputs = $postObject->getFormInputs(); return response() ->json([ 'url' => $formAttributes['action'], 'fields' => $formInputs ]); }Client側(Vue.js)
<script> export default { name: 'AwsS3Upload', methods: { async upload() { const upload_files = document.getElementById('upload-file'); const upload_file = upload_files.files[0]; // 署名付きPOSTのAPI叩く let preSignedUrl = await this.getPresignedUrl(); // S3へアップロード let uploadS3Path = await this.uploadS3(preSignedUrl, upload_file); }, async getPresignedUrl() { // ↓ここのファイル名は仮置きで適当になってますw let filename = 'fuga'; let filetype = 'image/jpeg' let fileext = 'jpg' try { const url = '/api/get-presigned-url?filename=' + filename + '&filetype=' + filetype + '&fileext=' + fileext; let response = await axios.get(url); console.log('S3署名付きURL取得 成功'); return response; } catch (error) { console.log('S3 署名付きURL取得 失敗'); } }, async uploadS3(presignedUrl, up_file) { let data = presignedUrl.data; try { var formdata = new FormData(); for (let key in data.fields) { formdata.append(key, data.fields[key]); } formdata.append("file", up_file); const headers = { "content-type": "multipart/form-data", } console.log('S3 アップロード 開始'); let response = await axios.post( data.url, formdata, { headers: headers, } ); console.log('S3 アップロード 成功'); return data.url + '/' + data.fields.key; } catch (error) { console.log('S3 アップロード エラー'); } }, } } </script>結論
署名付きPOSTで、S3へ直接クライアントサイドからファイルアップロードができました。
参考
【AWS S3】S3 Presigned URLの仕組みを調べてみた
CORS(Cross-Origin Resource Sharing)について整理してみた
PresignedPost.php
ブラウザからS3へのダイレクトアップロード
- 投稿日:2019-11-24T14:32:23+09:00
第2回 - Web API と Web UI でファイルダウンロード(Web UI 編)
はじめに
前回からファイルをダウンロードする簡単な Web API と Web UI のサンプルを紹介しています。
第1回目の前回は Node.js + Express.js での Web API を紹介しました。
第2回目の今回は Vue.js + Vuetify で簡単な Web UI を作り、前回の Web API を叩いて実際にファイルをダウンロードします。
- Web API 編
- Web UI 編(←イマココ)
環境
OS: macOS Mojave 10.14.6
Vue.js: 2.6.10
Vuetify: 2.1.0
axios: 0.19.0
file-saver: 2.0.2Download.vue
Download.vue<template> <v-container> <v-row> <v-col cols="2"> <v-btn v-on:click="click('sample.csv')" color="primary" block > Download csv </v-btn> </v-col> <v-col cols="2"> <v-btn v-on:click="click('sample.png')" color="primary" block > Download image </v-btn> </v-col> </v-row> </v-container> </template> <script> import axios from 'axios' import urljoin from 'url-join' import { saveAs } from 'file-saver' export default { methods: { async click (fileName) { const response = await axios.get(urljoin('http://localhost:3000/api/download'), { responseType: 'blob', params: { 'file': fileName } }).catch((error) => { console.log(error) }) if (response && (200 == response.status)) { const contentDisposition = response.headers["content-disposition"] const contentDispositions = contentDisposition.split(';') const fileNames = contentDispositions[contentDispositions.length - 1].split('=') fileName = fileNames[fileNames.length - 1] saveAs(response.data, fileName) } } } } </script>ボタンを2つ配置しただけのシンプルな UI です。
ダウンロードするファイルは CSV と PNG の2種類です。
ボタンをクリックすると対応するファイル名(sample.csv or sample.png)を引数にして Web API を実行します。
Web API 実行後、自動的にファイルを保存するようになっています。Web API実行
Download.vue(抜粋)const response = await axios.get(urljoin('http://localhost:3000/api/download'), { responseType: 'blob', params: { 'file': fileName } }).catch((error) => { console.log(error) })解説
Web API の実行には axios を使用しています。
また、responseType: 'blob'
で受け取る型を Blob 型に指定します。
(後述の saveAs@file-saver で Blob 型のデータを指定するため)ファイル保存
Download.vue(抜粋)const contentDisposition = response.headers["content-disposition"] const contentDispositions = contentDisposition.split(';') const fileNames = contentDispositions[contentDispositions.length - 1].split('=') fileName = fileNames[fileNames.length - 1] saveAs(response.data, fileName)解説
Web API 側で
content-disposition
(ヘッダー)にファイル名を指定しており、保存時のファイル名に使用しています。
ファイルの保存は file-saver を使用します(とても便利)。
saveAs に Blob 型のデータとファイル名を指定すると自動でダウンロードが始まります。動作確認
第1回目のアプリケーションも合わせて起動します。
各ボタンの押下でファイルのダウンロードが始まっているのがわかります。まとめ
2回に渡って Web API と Web UI でファイルダウンロードのサンプルを紹介しました。
複雑なコードを書かなくても、いくつかのライブラリを利用することで簡単に実装できました。
Web API でデータを取得し、保存用のモジュールでデータにデータを渡しているだけなので、アレンジもしやすいと思います。今回、使用したコードはGitHubで公開しています。
https://github.com/ponko2bunbun/vuetify-file-download-sample
- 投稿日:2019-11-24T11:03:04+09:00
Vue.js + Express + Sequelize + DockerでCRUD搭載のTodoリストを作ってみる
Vue.js + Express + Sequelize + DockerでCRUD搭載のTodoリストを作ってみる
この記事に書いてあること
- Expressの実装例
- Vue.jsの実装例
- 実行環境として利用するDockerコンテナの作り方
- Sequelizeの導入方法、簡単な使い方
- 拙い日本語
しがないエンジニアが人生初アドベントカレンダー参加となりますので、諸々ご容赦頂けますと幸いです。
対象者
細かい説明は割愛していますが、初心者向けに書いてます。
「ゴリゴリにDocker使った環境構築が知りたい!」とか、
「俺はSequelizeなんか使わずにSQLを一つずつ組むね!」とか、
「ここどうなってるかもうちょい細かく説明してほしい」みたいな人にはあまり適してません。あくまで、
記事をなぞっていくだけで手軽にCRUDがVue.js+Expressが体験できるっていうのを目的にしています。
各種バージョン
Docker for mac 19.03.5
Node.js 12.13.0
express 4.16.1
Sequelize-cli 5.5.1
vue-cli 4.0.5DBはsqlite3を利用(なんでもいいけど)
作るもの
CRUD機能を搭載した簡単なTodoリストを作ります。
完成イメージはこんな感じです。各種説明
Docker
言わずと知れたコンテナマン。コンテナの概念やメリットの説明は割愛。今回環境はこれで構築。
(ベストプラクティスを知りたい)Vue.js
みんな大好きフロントフレームワーク。特に難しいことはしません。
(vue-cliを使って雛形を作成)Node.js
みんな大好きサーバーサイドで動くJavaScript。特に難しいことはしません。
Sequelize
Nodeで使えるORM。RailsのActiveRecordみたいな物だと思ってもらえればOK。
特に難しいことはしません全体的なファイル構成
ファイル構成はこんな感じ。
rootディレクトリ配下をコンテナごとに区切り、後々コンテナにマウントしてあげる。rootDir/ ┣ docker-compose.yml ┣ vue/ ┃ ┣ Dockerfile ┃ ┗ frontapp/ ┃ ┗ Vue.jsの雛形ファイル群が入ってくる ┗ node/ ┣ Dockerfile ┗ Expressの雛形ファイル群が入ってくる全体構成
全体構成はこんな感じです。
基本Vue.jsコンテナがリクエストを受けて、axiosでNode.jsコンテナにサーバ通信しています。
SQLiteを使っているので、DBアクセスはNode.jsコンテナ内で処理されるようなイメージ。Node.jsの準備
早速開始、と行きたいところですが、まずは実行環境を構築。
Dockerhubから引っ張ってきてもいいのですが、せっかくなのでDockerfileを書いてあげましょう。
今回はとりあえず各種プログラムが実行できる環境が前提なので、コンテナ内は環境のみ。
ソースファイルはコンテナイメージに含めず、docker-composeを使って後でマウントしてあげることにします。node/DockerfileFROM node:12.13 RUN npm install -g express-generator sequelize-cliDockerfileの記述が終わったら一旦、Dockerfileからコンテナイメージを作成。
コンテナ起動時にローカルのディレクトリをマウントし、コンテナ内のデータが永続化できるようにします。docker build node/. -t serverapp:latest docker run -itd --rm --name serverapp -v $PWD/node:/node serverapp:latestコンテナの起動が完了したら、コンテナ内にログインする。
docker exec -it serverapp /bin/bashコンテナログイン後、expressコマンドを実行し、雛形ファイル群を作成。
cd /node express . npm install --save sequelize sqlite3 cors nodemon npm install
docker run
を実行した際にローカルのフォルダをマウントしているので、
ローカルのnode/
以下にexpressの雛形ファイル群ができているはず。続いて、DBを作成するためにコンテナに入ったまま
sequelize init
を実行し、
CRUDに必要なtask
モデルを作成する準備をします。sequelize initinit実行後、一旦コンテナからログアウトし、ローカルで
config/config.json
を下記のように修正。config/config.json{ "development": { "username": "root", "password": null, "database": "database_development", "host": "127.0.0.1", "dialect": "sqlite", "storage": "./data/development.sqlite3", "operatorsAliases": false }, "test": { "username": "root", "password": null, "database": "database_test", "host": "127.0.0.1", "dialect": "sqlite", "storage": "./data/test.sqlite3", "operatorsAliases": false }, "production": { "username": "root", "password": null, "database": "database_production", "host": "127.0.0.1", "dialect": "sqlite", "storage": "./data/production.sqlite3", "operatorsAliases": false } }Sequelizeのデータが保存される
./data
を作成する。mkdir data
もう一度コンテナにログインし、
task
モデルを作成します。docker exec -it serverapp /bin/bash sequelize model:create --name task --underscored --attributes taskname:string sequelize db:migrateマイグレーションが無事に成功すればtaskモデルが作成されます。
これでNode.jsの準備は完了。雛形準備に利用したコンテナは停止します。docker stop serverapp続いてフロントVue.js側を準備します。
Vue.jsの準備
Node側と同様にDockerfileを記述。
vue/DockerfileFROM node:12.13 RUN npm install -g @vue/cliDockerfileを元にコンテナイメージを作成。起動し、ローカルのフォルダをマウント。
docker build vue/. -t frontapp:latest docker run -itd --rm --name frontapp -v $PWD/vue:/vue frontapp:latestNode側同様、Vue.jsの雛形ファイル群を作成するため、一度コンテナ内にログインします。
docker exec -it frontapp /bin/bashコンテナログイン後、下記コマンドを実行。オプションは
default
で問題ありません。
その後の選択肢としては、yarn
とnpm
が出てくるけど、個人的にはnpmのが使いやすいのでそちらで。cd /vue vue create frontapp
以上でVue.js側の準備は終了。Node.js側同様に、一度コンテナは停止します。
docker stop frontappdocker-compose.ymlの準備
Node.jsとVue.jsそれぞれのコンテナを起動する際、composeファイルがあると起動/終了が楽なので、
docker-compose.yml
を下記のように記入。docker-compose.ymlversion: "3" services: node: build: node/. volumes: - ./node:/node working_dir: /node command: ["npm", "start"] ports: - "3000:3000" vue: build: vue/. volumes: - ./vue:/vue working_dir: /vue/frontapp command: ["npm", "run", "serve"] ports: - "8080:8080"プロジェクトのカレントディレクトリで、
docker-compose
コマンドを実行し、
Node.jsのコンテナとVue.jsのコンテナを起動する。docker-compose up -d # コンテナ終了は docker-compose downブラウザで3000ポートにアクセスしExpressの画面、8080ポートにアクセスしVue.jsの画面が表示されれば、開発用コンテナの構築は完了です。
localhost:3000
localhost:8080
docker-composeコマンドで初回起動時にコンテナイメージが再ビルドされ、
docker run
で起動した時のコンテナイメージとは別の名前でイメージ化されるため、docker run
コマンドで起動した時のコンテナイメージは不要のため削除します。docker rmi serverapp:latest docker rmi frontapp:latest実行環境の作成はこれにて終了。次から各種機能の実装に入っていきます。
Node.js
まずはサーバサイドの処理から実装します。
処理実装に入る前に、ソースコード変更時に自動的にNodeが再起動されるように少しだけ工夫。
app.js
とdocker-compose.yml
を修正します。node/app.js... var indexRouter = require("./routes/index"); var usersRouter = require("./routes/users"); var cors = require("cors"); var app = express(); app.use(cors()); ... app.listen(3000, function() { console.log("Node server is started"); }); module.exports = appdocker-compose.yml... working_dir: /node command: ["./node_modules/.bin/nodemon", "app"] ports: ...Node.jsの初期状態だとVueからのリクエストを受けられない(エラーがでてしまう)ので、
インストールしたcorsモジュールを追加して、corsを許可する必要があります。
また、nodemonを利用しファイル変更を検知、ファイルが変更されたら自動でサーバが再起動されるようにします。
設定を反映するため、一度コンテナ再起動を実行。docker-compose down docker-compose up -d
コントローラーの新規追加がめんどくさいので最初から用意されている
index.js
を利用します。
実装内容としてはこんな感じ。node/routes/index.jsvar express = require("express"); var router = express.Router(); const db = require("../models/index"); // Read router.get("/", async function(req, res, next) { try { const result = await db.task.findAll({}); res.send(result); } catch (err) { res.status(500).send(err); } }); //Create router.post("/task", async function(req, res, next) { try { const result = await db.task.create({ taskname: req.body.task }); res.send(result); } catch (err) { res.status(500).send(err); } }); //Update router.put("/task/:id", async function(req, res, next) { try { const result = await db.task.update( { taskname: req.body.task }, { where: { id: req.params.id } } ); res.send(result); } catch (err) { res.status(500).send(err); } }); //Delete router.delete("/task/:id", async function(req, res, next) { try { const result = await db.task.destroy({ where: { id: req.params.id } }); res.send({ result: result }); } catch (err) { res.status(500).send(err); } }); module.exports = router;送られてきたパラメータをそのままDBに登録するっていう簡単なCRUD処理の一覧です。
一つずつ説明していきます。// Read router.get("/", async function(req, res, next) { try { const result = await db.task.findAll({}); res.send(result); } catch (err) { res.status(500).send(err); } });getで
/
にアクセスされた際に、taskテーブルから全てデータを引っ張ってくるように実装。
DBのtaskテーブルから全てデータを引っ張ってくるのに、sequelizeのfindAll
というメソッドを使用します。
取得したデータをres.send
を使用して、リクエスト元に戻してあげます。router.post("/task", async function(req, res, next) { try { const result = await db.task.create({ taskname: req.body.task }); res.send(result); } catch (err) { res.status(500).send(err); } });postで
/task
にアクセスした際に、リクエストのbody内容をDBのtaskテーブルに登録。
sequelizeのcreate
というメソッドを利用します。router.put("/task/:id", async function(req, res, next) { try { const result = await db.task.update( { taskname: req.body.task }, { where: { id: req.params.id } } ); res.send(result); } catch (err) { res.status(500).send(err); } });putで
/task/:id
にアクセスした際に、idに紐づくレコードをupdateする処理。
sequelizeのupdate
メソッドを利用し、リクエストのbody内容でデータを更新します。router.delete("/task/:id", async function(req, res, next) { try { const result = await db.task.destroy({ where: { id: req.params.id } }); res.send({ result: result }); } catch (err) { res.status(500).send(err); } });deleteで
/task/:id
にアクセスした際に、idに紐づくレコードを削除する処理。
sequelizeのdestroy
メソッドを利用。Node.js側CRUDの処理は実装完了です。続いて、Vue.js側の実装。
Vue.js
デフォルトでHelloWorld.vueというコンポーネントが存在しているので、そこに肉付けしていくことにします。
まずは各種部品を設置。vue/frontapp/src/components/HelloWorld.vue<template> <div class="hello"> <form> <input type="text" style="display:none" /> <input type="text" /> <input type="button" value="add!" /> </form> <table align="center" border="0"> <tr> <th>task</th> <th>update</th> <th>delete</th> </tr> <tr> <td> <input type="text" /> </td> <td> <input type="button" value="update" /> </td> <td> <input type="button" value="delete" /> </td> </tr> </table> </div> </template> <script> export default { name: "HelloWorld" } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h3 { margin: 40px 0 0; } ul { list-style-type: none; padding: 0; } li { margin: 0 10px; } a { color: #42b983; } .table { height: 100%; text-align: center; } </style>まずはGETで
/
にアクセスした際に、apiアクセスを行う部分を実装する。vue/frontapp/src/components/HelloWorld.vue<script> import axios from "axios"; export default { name: "HelloWorld", data: () => ({ tasks: [], }), created: async function() { try { const result = await axios.get("http://localhost:3000"); this.tasks = result.data; } catch (err) { alert(JSON.stringify(err)); } } } ...
created
を定義しておくことで、ページが読み込まれた時に処理を実行することが可能。
created
でもmounted
でもどっちでもいいけど、今回の用途にはcreated
の方が適任ですね。
下記記事で詳しく書かれていたので、気になる人は参照してみてください。Vuejs APIアクセスはcreatedとmountedのどちらで行う?
axiosは非同期で実行されるので、処理を同期的に行うためにawaitで実行。
実行結果としてresult.data
が戻ってくるので、data
内のtasks
の内容をAPI実行結果に変更する。続いてtask追加処理を実装。まず入力されたデータにアクセスできるようにする必要があるため、
テキストボックスをv-modelでdata
と紐づけます。vue/frontapp/src/components/HelloWorld.vue... <form> <input type="text" style="display:none" /> <input v-model="currentTask" type="text" /> <input type="button" value="add!" /> </form> ... <script> import axios from "axios"; export default { name: "HelloWorld", data: () => ({ tasks: [], currentTask: "" }), ...こうすることで
input
に入力されたデータが、data
内currentTask
に反映され、関数内からデータが参照可能となります。
次に@click
イベントを実装し、ボタンが押された時に関数を呼び出すよう修正。vue/frontapp/src/components/HelloWorld.vue... <form> <input type="text" style="display:none" /> <input v-model="currentTask" type="text" /> <input type="button" value="add!" @click="taskCreate" /> </form> ... <script> ... created: async function() { try { const result = await axios.get("http://localhost:3000"); this.tasks = result.data; } catch (err) { alert(JSON.stringify(err)); } }, methods: { taskCreate: async function() { alert(this.currentTask); } } }; </script>こうすることで、ボタンが押された時に、
taskCreate
を呼び出すことが可能です。
仮実装としてボタンを押すと、テキストボックス内に入力したデータの内容をalert表示するように実装してます。
何かしらテキストボックスに入力し、ボタンを押したタイミングでalertが表示されてくればOK。取得したデータをサーバに送信するため、
taskCreate
関数の続きを実装していく。vue/frontapp/src/components/HelloWorld.vue<script> ... methods: { taskCreate: async function() { try { const result = await axios.post("http://localhost:3000/task", { task: this.currentTask }); this.tasks.push(result.data); this.currentTask = ""; } catch (err) { alert(JSON.stringify(err)); } } ...node.js側で定義したタスク追加の処理(/task)に繋げる。
サーバ処理終了後に下記部分で動的にtasks
にデータを追加。this.tasks.push(result.data); // data内tasks配列に戻り値(追加したデータ)を追加 this.currentTask = ""; // data内currentTaskとテキストボックスが双方向でバインドされているので、currentTaskを空にすることでテキストボックスが空になるこれで、入力されたデータがサーバにリクエストで送られ、データ保存が可能となります。
このままだとデータを追加しても追加したデータが表示されないので、data内のtasks
と<tr>
をv-model
を利用し紐づけます。
HelloWorld.vueのtable
部分を下記のように変更。vue/frontapp/src/components/HelloWorld.vue<table align="center" border="0"> <tr> <th>task</th> <th>update</th> <th>delete</th> </tr> <tr v-for="(task, index) in tasks" :key="task.id"> <td> <input v-model="task.taskname" type="text" /> </td> <td> <input type="button" value="update" /> </td> <td> <input type="button" value="delete" /> </td> </tr> </table>
v-for
を利用することでtasks
の数だけ<tr>
が生成されます。
こうすることで、ページ読み込み時にサーバからデータが取得され、今まで追加したタスクが出てくるようになり、
ボタンを押した時にもタスクが画面に追加されるようになる。これでタスク追加に関しては実装終了。続いてタスク削除を実装します。
まずは関数を用意。vue/frontapp/src/components/HelloWorld.vue<script> ... methods: { taskCreate: async function() { try { const result = await axios.post("http://localhost:3000/task", { task: this.currentTask }); this.tasks.push(result.data); this.currentTask = ""; } catch (err) { alert(JSON.stringify(err)); } }, taskDelete: async function(id, index) { try { await axios.delete("http://localhost:3000/task/" + id); this.currentTask = ""; this.tasks.splice(index, 1); } catch (err) { alert(JSON.stringify(err)); } } }
taskDelete
関数を追加しました。taskのidと配列のindexを引数に持ってあげます。
呼び出し部分はこんな感じ。vue/frontapp/src/components/HelloWorld.vue<tr v-for="(task, index) in tasks" :key="task.id"> <td> <input v-model="task.taskname" type="text" /> </td> <td> <input type="button" value="update" /> </td> <td> <input type="button" value="delete" @click="taskDelete(task.id, index)" /> </td> </tr>こうすることで、リクエスト先のURLが動的に作られます。
タスクのidが2
だった場合は、リクエスト先はaxios.delete("http://localhost:3000/task/" + 2);
となり、
タスクのidが10
だった場合は、リクエスト先はaxios.delete("http://localhost:3000/task/" + 10);
となります。また、配列のindexを引数に持ってあげることによって、spliceメソッドを使って配列を操作することができ、画面のデータを非同期で変更可能です。
this.tasks.splice(index, 1); // [{タスク1},{タスク2},{タスク3},{タスク4}] // indexに2が渡された場合は、 // [{タスク1},{タスク2},{タスク4}] // このように配列が操作される最後にタスク修正した際の処理を実装する。まずは関数の用意から。
vue/frontapp/src/components/HelloWorld.vue<script> ... taskUpdate: async function(id, val) { try { await axios.put("http://localhost:3000/task/" + id, { task: val }); alert("タスクを修正しました"); this.currentTask = ""; } catch (err) { alert(JSON.stringify(err)); } }呼び出し部分はこんな感じ。
vue/frontapp/src/components/HelloWorld.vue<tr v-for="(task, index) in tasks" :key="task.id"> <td> <input v-model="task.taskname" type="text" /> </td> <td> <input type="button" value="update" @click="taskUpdate(task.id, task.taskname)" /> </td> <td> <input type="button" value="delete" @click="taskDelete(task.id, index)" /> </td> </tr>
taskUpdate
を呼び出す時に、タスクのID、タスク名を引数で渡してあげることにより、
対象のタスクのみアップデートがかかるようにします。CRUDの全体が出来上がった最終形のHelloWorld.vueとしてはこんな感じ。
<template> <div class="hello"> <form> <input type="text" style="display:none" /> <input v-model="currentTask" type="text" /> <input type="button" value="add!" @click="taskCreate" /> </form> <table align="center" border="0"> <tr> <th>task</th> <th>update</th> <th>delete</th> </tr> <tr v-for="(task, index) in tasks" :key="task.id"> <td> <input v-model="task.taskname" type="text" /> </td> <td> <input type="button" value="update" @click="taskUpdate(task.id, task.taskname)" /> </td> <td> <input type="button" value="delete" @click="taskDelete(task.id, index)" /> </td> </tr> </table> </div> </template> <script> import axios from "axios"; export default { name: "HelloWorld", data: () => ({ tasks: [], currentTask: "" }), created: async function() { try { const result = await axios.get("http://localhost:3000"); this.tasks = result.data; } catch (err) { alert(JSON.stringify(err)); } }, methods: { taskCreate: async function() { try { const result = await axios.post("http://localhost:3000/task", { task: this.currentTask }); this.tasks.push(result.data); this.currentTask = ""; } catch (err) { alert(JSON.stringify(err)); } }, taskDelete: async function(id, index) { try { await axios.delete("http://localhost:3000/task/" + id); this.currentTask = ""; this.tasks.splice(index, 1); } catch (err) { alert(JSON.stringify(err)); } }, taskUpdate: async function(id, val) { try { await axios.put("http://localhost:3000/task/" + id, { task: val }); alert("タスクを修正しました"); this.currentTask = ""; } catch (err) { alert(JSON.stringify(err)); } } } }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h3 { margin: 40px 0 0; } ul { list-style-type: none; padding: 0; } li { margin: 0 10px; } a { color: #42b983; } .table { height: 100%; text-align: center; } </style>以上で完成です!
作ってみた感想
Vue.jsのバインディングが思っていた以上に使いやすく、非常に簡単にフロントの実装ができました。
規模が大きくなってきたらVuexとかライフサイクルとか考えないといけないこともあるけど、
この程度の規模のアプリケーションであれば、簡単に作ることができるのが良いですね。あと、ただ実装するだけだと面白く無いので実行環境はDockerにしたのも◯。
汎用的な実行コンテナとして使えるので、チュートリアル的な使い方には非常に適任。
構築も簡単なので非常に使いやすかったかと。Sequelizeの導入がちょっとだけめんどくさいけど、一度やってしまえばRailsチックにDB操作ができるので、
SQL書かずに手軽にDB操作したい!って人にはおすすめです。明日は@kazukimatsumotoさんの番です。よろしくおねがいします!
- 投稿日:2019-11-24T09:39:30+09:00
Nuxt.jsでメッセージの外出し&国際化対応
vue-i18nとnuxt-i18nがあるけど...
nuxt-i18n を使用します
- nuxt-i18nの方が更新が活発なのでこちらを使っていこうという単純な理由です。
- もう1つ、nuxt-community/nuxt-i18nのREADMEの通りにやってもいいのですがメンテがしにくいと思い、
プラグインとして登録
し、メッセージを定義したjsonファイルを読み込むようにします。(+ 言語として日本語を設定)インストール
- 2019年11月23日現在の最新6.4.0をインストール
npm install --save nuxt-i18n@6.4.0
- package.json確認
package.json//省略 "dependencies": { //省略 "nuxt-i18n": "^6.4.0", //省略 },プラグインを作成する
- nuxt-community/nuxt-i18nのREADMEの通りにやってもいいのですが、メンテがしにくいと思い
プラグインとして登録
し、メッセージを定義したjsonファイルを読み込むようにします。(+ 言語として日本語を設定)plugins/nuxt-i18n.ts
を作成。locales/ja.json
に定義されているメッセージを読み込む設定。nuxt-i18n.tsimport Vue from 'vue' import VueI18n from 'vue-i18n' Vue.use(VueI18n) export default ({ app, store }) => { // Set i18n instance on app // This way we can use it in middleware and pages asyncData/fetch app.i18n = new VueI18n({ locale: store.state.locale, fallbackLocale: 'ja', messages: { ja: require('~/locales/ja.json') } }) } export const i18n = new VueI18n({ locale: 'ja', messages: { ja: require('~/locales/ja.json') } })メッセージ定義ファイル(
ja.json
)を作成
- 一部抜粋してますが、
error
、info
などでIDのprefixを分けるとメンテがしやすいと思います。ja.json{ "error": { "E-0001": "[0] の参照権限がありません。", "E-0002": "[0] の編集権限がありません。", "E-0003": "[0] は入力必須です。", "E-0004": "[0] は選択必須です。" }, "warn": { "W-0001": "表示対象[0] が[1] 以上存在します。", "W-0002": "メール通知に失敗しました。自ら通知メールを送ってください。" }, "info": { "I-0001": "[0] が完了しました。", "I-0002": "メール通知に成功しました。" }, "confirm": { "C-0001": "[0] します。よろしいですか。", "C-0002": "編集内容は保存されていません。編集内容を破棄してよろしいですか。", "C-0003": "同一名の[0]が存在します。更新してもよろしいですか。" }, "placeHolder": { "P-0001": "パスワード", "P-0002": "E-mail", "P-0003": "10文字以上で入力下さい。", "P-0004": "100文字以内で入力下さい。" }, "tooltip": {} }nuxt.config.tsの編集
nuxt.config.tsplugins: [ '@/plugins/nuxt-i18n.ts' ],使用する
- importして
i18n.tc
で対象のメッセージを指定するだけ。- 例えばユーザ登録後にログイン画面に遷移させた際のメッセージの出力を例にすると...以下の通り。
login.vue<script lang="ts"> import { Component, Mixins } from 'vue-property-decorator' //省略 import { i18n } from '~/plugins/nuxt-i18n' @Component({}) export default class Login extends Vue { //省略 created() { if (CommonModule.SignUpJobDone) { this.message = i18n.tc('info.I-0003') //ココ } } }結果の確認
11/25にパラメータの渡し方を追記予定
以上!!
- 投稿日:2019-11-24T01:00:30+09:00
Nuxt TypeScript を docker-compose で構築する
はじめに
Nuxt TypeScript でセットアップの手順は公式にて公開しているが、docker-composeを用いた構築の情報が少なかったので纏めてみた。
また、Ver.2.9以降からTypeScriptへの導入手順が変わり、個人的に少し躓いた部分もあったのでその点も踏まえて共有しようと思う。
1. 実行環境
- macOS Catalina Ver.10.15.1
- Docker version 19.03.4, build 9013bf5
- docker-compose version 1.24.1, build 4667896b
- Nuxt.js Ver.2.10.2
- node.js 12.13.0-alpine
2. 前提条件
- Nuxt.js のバージョンが 2.9 以上である事。
- PC内に docker, docker-compose, node.js がインストールされている事。
docker , docker-compose , node.js がインストールされていない場合は下記を実行すると良い。
2.1. Docker のインストール
Docker をインストールする為に DockerHub にアクセスし、
Docker.dmg
をダウンロードする。
※ DockerHubを初めて利用する場合はアカウント作成が必要である。
Docker.dmg
を起動すると、Dockerのインストールが行われるので、インストール完了後に Docker を Applications にコピーする。また、他の方法としてHomebrewと呼ばれるパッケージ管理システムを用いてインストールする事ができる。以下にインストール手順を示す。
# Homebrew をインストール $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" # Homebrew が正しくインストールされているかどうか確認する $ brew doctor # Docker をインストールする $ brew install docker $ brew cask install dockerdocker, Homebrew のバージョンの確認は以下のコマンドを実行する事で確認できる。
# Docker $ docker --version Docker version 19.03.4, build 9013bf5 # Homebrew $ brew --version Homebrew 2.1.16 Homebrew/homebrew-core (git revision 00c2c; last commit XXXX-XX-XX) Homebrew/homebrew-cask (git revision 9e283; last commit XXXX-XX-XX)2.2. docker-compose のインストール
docker-compose のインストールは以下のコマンドを実行すると良い。
$ curl -L https://github.com/docker/compose/releases/download/1.3.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose # docker-compose コマンドを実行できるように、権限を設定する。 $ chmod +x /usr/local/bin/docker-compose補足:実行中、
Permission denied
エラーが表示された場合、/usr/local/bin
ディレクトリが書き込み可能が許可されていない可能性がある。その際、Compose のインストールをスーパーユーザで行う必要がある。sudo -i
を実行してから、上記の2つのコマンドを実行し、exit
する。3. Nuxt.js 環境構築
TypeScript に変換する前に、まず Nuxt.js のプロジェクトを作成する必要があるので作成手順に関して説明する。
3.1. ファイル構成
今回のファイル構成は以下の通りである。1つのディレクトリに異なるファイルが複数混同する事を避けるため、project ディレクトリを新規し、その中に
Dockerfile
を作成する。また、Nuxt.js のプロジェクト内のファイル階層も綺麗にしたかったので、
src
ディレクトリを新規作成し、その中に纏めている。以下に、ファイル構成を示す。
. ├── README.md ├── docker-compose.yml └── project ├── Dockerfile ├── README.md ├── node_modules ├── nuxt.config.ts ├── package.json ├── src │ ├── assets │ ├── components │ ├── layouts │ ├── middleware │ ├── pages │ ├── plugins │ ├── static │ ├── store │ └── vue-shim.d.ts ├── tsconfig.json ├── yarn-error.log └── yarn.lock3.2. Dockerfile
はじめに、Dockerfileを作成する。
node.js
に関しては、DockerHub にイメージが公開されているのでそれを使用する。Dockerfile# イメージ指定 FROM node:12.13.0-alpine # コマンド実行 RUN apk update && \ apk add git && \ npm install -g @vue/cli nuxt create-nuxt-app && \今回はイメージの軽量化の為に、
alpine
を使用した。※ alpine : Alpine Linuxと呼ばれる、
BusyBox
とmusl
をベースにしたLinuxディストリビューションを指す3.3. docker-compose.yml
次に、docker-compose.ymlを作成する。
docker-compose.ymlversion: '3' services: node: # Dockerfileの場所 build: context: ./ dockerfile: ./project/Dockerfile working_dir: /home/node/app/project # ホストOSとコンテナ内でソースコードを共有する volumes: - ./:/home/node/app # コンテナ内部の3000を外部から5000でアクセスする ports: - 5000:3000 environment: - HOST=0.0.0.03.4. dockerイメージ作成
Dockerfile, docker-compose.ymlを作成後、以下のコマンドを実行し docker イメージを作成する。
$ docker-compose build
実行後、イメージができているかどうかは以下のコマンドで確認できる。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE docker-nuxt-typescript_node latest 7f8324973b48 3 days ago 434MB node 12.13.0-alpine 5d187500daae 3 weeks ago 80.1MB3.5. Nuxt.jsの起動
コンテナ内で
npm
、yarn
コマンドを実行したい場合は下記の様に指定する。$ docker-compose run --rm <Container Name> <Command>以下のコマンドを実行し、Nuxt.jsを起動させセットアップを行う。
$ docker-compose run --rm nuxt npx create-nuxt-app ./project Project name (your-title) Project description (My wicked Nuxt.js project) Author name (author-name) Choose the package manager > Yarn Npm Choose UI framework (Use arrow keys) > None Ant Design Vue Bootstrap Vue Buefy Bulma Element Framevuerk iView Tachyons Tailwind CSS Vuetify.js Choose custom server framework (Use arrow keys) > None (Recommended) AdonisJs Express Fastify Feathers hapi Koa Micro # スペースで選択する事ができる。(Enterキーではないので注意) Choose Nuxt.js modules >(*) Axios ( ) Progressive Web App (PWA) Support Choose linting tools >(*) ESLint ( ) Prettier ( ) Lint staged files Choose test framework > None Jest AVA Choose rendering mode (Use arrow keys) Universal (SSR) // WEB SITE > Single Page App // WEB APPLICATION Choose development tools >(*) jsconfig.json (Recommended for VS Code)上記の設定は、筆者がセットアップに選択したものである。設定に関しては各々で必要なものを導入すると良い。
3.6. コンテナの立ち上げ
以下のコマンドを実行し、コンテナを起動させる。
$ docker-compose run node npm run dev # OR $ docker-compose run node yarn run dev起動後、http://localhost:5000/ にアクセスし、サンプルアプリ画面が確認できれば一旦完了である。
4. TypeScript化
最後に、Nuxt.js を TypeScript に変更する手順について説明する。
4.1. @nuxt/typescript-build, @nuxt/typescript-runtime のインストール
TypeScript化を進める前に、まずはじめに
@nuxt/typescript-build
、@nuxt/typescript-runtime
の2つを以下のコマンドを実行してインストールする。
(どちらか一方でも動くが、今回は両方インストールしておく)※ @nuxt/typescript-build :
nuxt build
で TypeScript を扱うためのもの。
※ @nuxt/typescript-runtime : Node.js 環境下で TypeScript を処理するためのもの。$ docker-compose run node npm install --save-dev @nuxt/typescript-build $ docker-compose run node npm install @nuxt/typescript-runtime # OR $ docker-compose run node yarn add --dev @nuxt/typescript-build $ docker-compose run node yarn add @nuxt/typescript-runtime
@nuxt/types
に関しては、上記の2つに内包されているのでインストールをする必要は無い。4.2. tsconfig.json の作成
Nuxt.js で作成されている
jsconfig.json
を削除し、tsconfig.json
を新規作成する。
tsconfig.json
のソースは以下の通りである。tsconfig.json{ "include": [ "./src/**/*" ], "compilerOptions": { "target": "esnext", "module": "esnext", "moduleResolution": "node", "lib": [ "esnext", "esnext.asynciterable", "dom" ], "esModuleInterop": true, "experimentalDecorators": true, "allowJs": true, "sourceMap": true, "strict": true, "noEmit": true, "baseUrl": ".", "paths": { "~/*": [ "./src/*" ], "@/*": [ "./src/*" ] }, "types": [ "@types/node", "@nuxt/types", "@nuxtjs/axios" ] }, "exclude": [ "node_modules" ] }今回は、プロジェクト内のディレクトリを
src
ディレクトリに移動させているので、include
、paths
などの設定を変更している。4.3. nuxt.config.ts の作成
次に、
nuxt.config.js
を削除し、nuxt.config.ts
を新規作成する。
nuxt.config.ts
のソースは以下の通りである。nuxt.config.tsimport { Configuration } from '@nuxt/types' const nuxtConfig: Configuration = { mode: 'universal', buildModules: ['@nuxt/typescript-build'], server: { port: 5000, host: 'localhost', }, head: { title: process.env.npm_package_name || '', meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }, { hid: 'description', name: 'description', content: process.env.npm_package_description || '' } ], link: [ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } ] }, loading: { color: '#fff' }, css: [ ], plugins: [ ], typescript: { typeCheck: true, ignoreNotFoundWarnings: true }, modules: [ '@nuxtjs/axios', '@nuxtjs/eslint-module', ], build: { extend(config, ctx) { } }, srcDir: 'src/' } module.exports = nuxtConfigVer.2.9からは、
nuxt.config.ts
内の Configuration の型定義が@nuxt/types
からExportされるようになった為、はじめに Import している。
また、追加された buildModules に@nuxt/typescript-build
を指定している。buildModules
に変更になった事で、build
内で使用するmodule
をdevDependencies
に指定できるようになった。4.4. vue-shim.d.ts の作成
vue-shim.d.ts
は、記述されているコードを TypeScript として認識させる為に必要となるファイルである。しかし、.vue
ファイル中にインポート等をする必要は無く、src
ディレクトリ内にあれば問題ない。
vue-shim.d.ts
のソースは以下の通りである。vue-shim.d.tsdeclare module "*.vue" { import Vue from "vue"; export default Vue; }4.5. package.json の修正
package.json
は、Nuxt.js のセットアップ時に作成されるが、TypeScript が適用されていないのでその点を修正する。修正する部分としてはpackage.json
内のscripts
が、nuxt コマンドなので、そこをnuxt-ts
コマンドに変更する。
修正後のソースは以下の通りである。package.json# 一部省略 { ... "scripts": { "dev": "nuxt-ts", "build": "nuxt-ts build", "start": "nuxt-ts start", "generate": "nuxt-ts generate", "lint": "eslint --ext .js,.vue --ignore-path .gitignore ." } ... }4.6. Nuxt TypeScript の実行
変更を踏まえ、再度 Nuxt.js を起動する。
$ docker-compose run node yarn run dev yarn run v1.19.1 $ nuxt-ts ╭─────────────────────────────────────────────╮ │ │ │ Nuxt.js v2.10.2 │ │ Running in development mode (universal) │ │ │ │ Listening on: http://localhost:5000/ │ │ │ ╰─────────────────────────────────────────────╯ ℹ Preparing project for development ℹ Initial build may take a while ✔ Builder initialized ✔ Nuxt files generated ℹ Starting type checking service... ℹ Using 1 worker with 2048MB memory limit ✔ Client Compiled successfully in 13.16s ✔ Server Compiled successfully in 11.98s ℹ No type errors found ℹ Version: typescript 3.6.4 ℹ Time: 11648ms ℹ Waiting for file changes ℹ Memory usage: 254 MB (RSS: 315 MB)上記を見て貰うと、
nuxt-ts
コマンドで実行している事が確認できる。
実行後、先程と同じく http://localhost:5000/ にアクセスし画面が表示する事を確認する。以上で、TypeScript化は完了である。
5. まとめ
今回は、Nuxt TypeScript を docker-comnpose で構築した。Ver.2.9 からの変更で docker-compose 下で動かなくなった人を少しでもサポートできるなら幸いである。
また、今回のサンプルは GitHub に公開しているので、何か不明点や修正点があれば随時対応していこうと思う。
◆追記◆
11/24 : Githubのproject/README.md
を英語翻訳化しました。