- 投稿日:2019-12-14T22:14:09+09:00
Express + Vue.jsでhelloを出してみよう
おはようございますこんにちはこんばんは!
最近Front-endのフレームワークが色々出て流行ってるらしくて、
友達となんかやってみよー!になってはじめました。
最初、どのフレームワークを使おうかと、
'react / vue が人気らしいー何が違う?'とざっと検索してみたら
- Vue
- テンプレート形式でアプリの制作したいなら
- 簡単で「一旦動作」ができるのが好きなら
- 早くて軽量のアプリが作りたいなら
- React
- 大きい規模のアプリを作るなら
- もっと大きい情報が欲しいなら
こんな差がありました。
簡単なプロジェクトだから、VueにしようーでVueを選びました。
(実は韓国で何もわからずVueの本を買ってきたので、、最初からvueにしようと決めたこともあり、、笑)back-endはnode.jsのexpress フレームワークを使います(なんでもjsでやってみよう感)
やってみましょう^0^
node.js install
https://nodejs.org/
installします。ltsの方が安定的らしくてltsの方インストールしました。コマンドラインでインストる確認
$ node -v
バージョンが出力されたらokpackage.jsonでパッケージ管理
package.json生成
$npm init
express 設置 node_modulesというフォルダが生成されます
$npm install --save express
4.htmlファイルを置いておくpublic directoryをnode_modulesと一緒の段階に生成
生成
$mkdir public
入る
$cd public
index.htmlページ生成
$vi index.html
index.html<!DOCTYPE html> <html> <head> <title>Vue.js Sample</title> </head> <body> <div id="app"> <h1>{{ message }}</h1> <input v-model='message'> </div> <!-- vue.js読み込み--> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> new Vue({ el: '#app', data: { message: 'Hello Vue.js!' } }) </script> </body> </html>v-modelを使ってinputとtextareaのエレメントに両方向のデーターバインディングの生成が出来ます。
javascriptのjqueryだと inputのvalueを持って、、それをh1に適用して、、みたいなことを
nue Vue ~~ だけで出来ます。5.node.jsを実行するindex.jsを生成(node_modulesとpublicと一緒の段階)
$cd ../
$vi index.js
index.jsconst express = require('express'); const app = express(); const PORT = process.env.PORT = 8000; //port8000に指定、変えてもok app.use(express.static('public')); // app.listen(PORT, () => { console.log('Server is running at:',PORT); });6.index.jsのところで実行
$node index.js
consoleServer is running at: 8000表示されたら localhost:8000に接続すると
こういう画面が出て、下のinputに内容を変更するとHello Vue.js!の大文字が変更されます。
とても簡単!vue.jsの解説なんかより、一応、、画面表示してみよう!になっちゃったんですが、(汗)
これで終わり!参考
https://jp.vuejs.org/index.html
自分は韓国人なので、https://kr.vuejs.org/index.html こちら参照しました (笑)
https://joshua1988.github.io/web_dev/vue-or-react/ 韓国語です (汗)
- 投稿日:2019-12-14T19:33:20+09:00
ミニマムなVueファイルでcomponentの表示順を確認してみた。
どうもはじめまして。
エンジニア一年目の@mjnjvdです。今回はアドベンドカレンダーなるものにお誘いをいただいたので、
重い腰を上げてqiita初投稿をする決意をしました!現在案件でVueを用いたフロントエンド開発を担当しているのですが、
リロードをかけた時や画面遷移した時の画面表示の際に、小さい要素が先に表示されてしまう現象に陥ってしまいました。
そこで今回はミニマムなVueファイルにコンポーネントを持たせ、描画が行われる順序について確認していきたいと思います。使用したファイル
VueCLIで作成したプロジェクトを使っていきます。
今回は既存のApp.vueを以下のようにしcomponents配下にも同様のクラスを当てたコンポーネントを用意しました。
さらにchild1コンポーネント内でchildAコンポーネントを呼び出している構成を取っています。
そしてそれぞれのvueファイルのmountedにconsole.logを仕込んでいます。App.vue<template> <div class="parent"> {{text}} <child1/> <child2/> </div> </template> <script> import child1 from './components/child1.vue' import child2 from './components/child2.vue' export default { components: { child1, child2 }, data(){ return { text: "parent" } }, mounted: function(){ console.log('parent') } } </script> <style scoped> .parent{ border:solid black } </style>実際にブラウザに表示させてみると以下のようになります。
極限にシンプルですね。
実行結果
ではコンソール画面を開いてリロードをかけてみましょう。
結果は...
なんと!孫コンポーネントであるchildAコンポーネントから描画されていますね...
これは小さい要素から描画されてしまうのにも納得です。苦労した点
・Unexpected console statement (no-console)と言う謎エラー
App.vueに設置したconsole.logが原因となったエラーですね。
調べたところによるとVueCLIでプロジェクトを作成した際にデフォルトの設定を用いた結果として、ESlintもインストールされ、その初期設定でnoーconsoleが有効になってしまっていたようです。
設定をいじるのもありですが今回は煩わしさに勝てずカスタム設定でプロジェクト作成をし直し解決しました。
・child1内でcilldAのインポートがうまくいかない問題
同じ階層にあるファイルなので"import childA from 'childA.vue'"と書いていたのですが"import childA from './childA.vue'"とする必要があったようです。最後に
いかがでしたでしょうか。
同じようにVueの描画処理に苦労している方のお役に立てれば幸いです。それにしても自身が悩んだことを文章化するというのは非常に難しい作業ですね。(そもそも自分が悩んでいる状態なのか判断するのも難しいと感じているのですが...)
日常的にqiita投稿といかないまでも悩みを細分化して整理する作業はやってこうと思った次第であります。次回は@shg_shgさんの投稿です!
- 投稿日:2019-12-14T18:10:01+09:00
Vue.js テーブルの対象行を抜き出して詳細表示・編集する方法
概要
Vue.jsを使って、テーブルの選択した行のデータを編集フォームに表示し、
フォームで編集したデータをテーブルの対象行に戻す(更新する)といった機能の実装をしたので、
その時のメモ。環境
- Vue.js 2.6.10
実装機能概要
詳細
実装したコードのサンプルは以下のコードになります。
ポイントとしては、
- 編集開始時に、編集対象のインデックスを保持
- 編集フォームのデータとテーブルデータのオブジェクトの参照を分離するため、
Object.assign()
を使って、別オブジェクトとしてデータを作成しているsplice()
を使い、編集対象のインデックスのデータのみを更新するといったところだと思います。
<template> <div> <div class="detail"> <label> No: <input type="number" v-model.number="form.no" /> </label> <label> Name: <input type="text" v-model="form.name" /> </label> <label> Content: <input type="text" v-model="form.content" /> </label> <button type="button" @click="onSave">保存</button> <button type="button" @click="onCancel">キャンセル</button> </div> <hr /> <div class="table"> <table border="1"> <thead> <tr> <th style="width:120px;"></th> <th style="width:50px;">No</th> <th style="width:150px;">Name</th> <th style="width:300px;">Content</th> </tr> </thead> <tbody> <tr v-for="(d,index) in data" :key="index"> <td> <button type="button" @click="onEdit(index)">編集({{ index + 1 }}行目)</button> </td> <td>{{ d.no }}</td> <td>{{ d.name }}</td> <td>{{ d.content }}</td> </tr> </tbody> </table> </div> </div> </template> <script> export default { data() { return { form: {}, data: [ { no: 1, name: "test1", content: "テスト" }, { no: 2, name: "test2", content: "なし" }, { no: 3, name: "test3", content: "~~~~~~~~~" } ] }; }, methods: { onSave() { this.data.splice( this.currentTargetIndex, 1, Object.assign({}, this.form) ); this.form = {}; }, onCancel() { this.form = {}; }, onEdit(index) { this.currentTargetIndex = index; this.form = Object.assign({}, this.data[index]); } } }; </script> <style> </style>まとめ
そんなに複雑な実装でもないし、あまり使いどころはないかもしれないけど、
実際に作った機能なのでメモとして残しておこうと思う。複数のデータを一括で登録・編集するといった機能を実装するときには使えるかもしれない。
- 投稿日:2019-12-14T16:30:50+09:00
Vue.jsで作ったマークダウンエディタアプリをElectronでデスクトップアプリにする
はじめに
前回、Auth0を使ってプライベートなマークダウンエディタを作る(クライアントサイド編)で、ブラウザで動作するマークダウンエディタのプロトタイプを作成しました。
本記事では、Electronを使ってデスクトップアプリ化しようと思います。システム構成図
本記事では、前回記事で作成したクライアントをElectronを使ってデスクトップアプリにしていきます。
Vue.jsプロジェクトをElectron化する
Vue.jsのプロジェクトは2コマンド実行するだけでElectron化できます。
前準備:Vue.jsプロジェクトを作成する
Auth0を使ってプライベートなマークダウンエディタを作る(クライアントサイド編)の手順を一通り行い、http://localhost:3000/にアクセスすると以下のような画面が表示される状態にします。
Electronのコマンドを実行する
vue-cliプラグイン
のelectron-builder
を使うことでElectron化できます。
詳細はこちら→https://nklayman.github.io/vue-cli-plugin-electron-builder/以下のコマンドを実行します。
$ npm i -g @vue/cli $ vue add electron-builder途中で、使用するElectronのバージョンを聞かれるので、最新の
6.0.0
を選択します。$ npm i -g @vue/cli $ vue add electron-builder �? Installing vue-cli-plugin-electron-builder... > electron-chromedriver@5.0.1 install C:\Users\taka\.ghq\github.com\mono0423\p-mark-down-editor\node_modules\electron-chromedriver > node ./download-chromedriver.js + vue-cli-plugin-electron-builder@1.4.3 added 222 packages from 157 contributors and audited 30705 packages in 22.803s found 7 moderate severity vulnerabilities run `npm audit fix` to fix them, or `npm audit` for details ✔ Successfully installed plugin: vue-cli-plugin-electron-builder ? Choose Electron Version ^6.0.0 <-- バージョンはとりあえず最新の6.0.0を選択(他のでも大丈夫です) ~~~~~~~~~~~~~~~~~~~~~~~~略~~~~~~~~~~~~~~~~~~~~~~~~~~ ✔ Successfully invoked generator for plugin: vue-cli-plugin-electron-builder The following files have been updated / added: src/background.js .gitignore package-lock.json package.json You should review these changes with git diff and commit them.少し待つと、コマンドが成功するはずです。
electron-builder
によって、以下4ファイルが追加・更新されたようなので、バージョン管理している場合は忘れずにコミット・プッシュしておきましょう。
- src/background.js
- .gitignore
- package-lock.json
- package.json
いざ動作確認
package.json
のscripts
を見るといくつかエイリアスが追加されており、$ npm run electron:serve
で起動できそうなので、実行してみます。$ npm run electron:serve npm WARN lifecycle The node binary used for scripts is C:\Program Files (x86)\Nodist\bin\node.exe but npm is using C:\Program Files (x86)\Nodist\v-x64\12.11.1\node.exe itself. Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed with. DONE Compiled successfully in 7536ms 15:58:18 App running at: - Local: http://localhost:3000/ - Network: http://192.168.100.47:3000/ Note that the development build is not optimized. To create a production build, run npm run build. - Bundling main process... DONE Compiled successfully in 5384ms 15:58:23 File Size Gzipped dist_electron\index.js 651.00 KiB 148.88 KiB Images and other types of assets omitted. INFO Launching Electron...開発者ツールが表示されているので、×ボタンで閉じるとWebと同じ見た目になりました。
Auth0を使ったGoogleログインもでき、マークダウンエディタの機能も壊れることなくそのままデスクトップアプリとして動かすことができました。
まとめ
Vue.jsで作成したWebアプリは、vue-cliプラグインの
electron-builder
の力を借りることで、2コマンドだけでデスクトップアプリにすることができました。(うち1つはvue-cliのインストールだったので、vue-cliがインストールされていれば、1コマンドのみですね)デスクトップアプリとして動かした場合でも、Webで動いていた機能が壊れることなく動いたのは驚きました。(Electronの内部ではChromiumが使われているとのことなので、当たり前っちゃ当たり前ですが)
最近はPWAを使ってもデスクトップアプリっぽく見せることができるよう(Windows 10 1803の新機能「PWA」とは?PWAのUWPアプリ化を試してみる)ですので、そちらも試していきたいと思います。
- 投稿日:2019-12-14T16:30:50+09:00
Vue.jsで作ったマークダウンエディタをElectronでデスクトップアプリにする
はじめに
前回、Auth0を使ってプライベートなマークダウンエディタを作る(クライアントサイド編)で、ブラウザで動作するマークダウンエディタのプロトタイプを作成しました。
本記事では、Electronを使ってデスクトップアプリ化しようと思います。システム構成図
本記事では、前回記事で作成したクライアントをElectronを使ってデスクトップアプリにしていきます。
Vue.jsプロジェクトをElectron化する
Vue.jsのプロジェクトは2コマンド実行するだけでElectron化できます。
前準備:Vue.jsプロジェクトを作成する
Auth0を使ってプライベートなマークダウンエディタを作る(クライアントサイド編)の手順を一通り行い、http://localhost:3000/にアクセスすると以下のような画面が表示される状態にします。
Electronのコマンドを実行する
vue-cliプラグイン
のelectron-builder
を使うことでElectron化できます。
詳細はこちら→https://nklayman.github.io/vue-cli-plugin-electron-builder/以下のコマンドを実行します。
$ npm i -g @vue/cli $ vue add electron-builder途中で、使用するElectronのバージョンを聞かれるので、最新の
6.0.0
を選択します。$ npm i -g @vue/cli $ vue add electron-builder �? Installing vue-cli-plugin-electron-builder... > electron-chromedriver@5.0.1 install C:\Users\taka\.ghq\github.com\mono0423\p-mark-down-editor\node_modules\electron-chromedriver > node ./download-chromedriver.js + vue-cli-plugin-electron-builder@1.4.3 added 222 packages from 157 contributors and audited 30705 packages in 22.803s found 7 moderate severity vulnerabilities run `npm audit fix` to fix them, or `npm audit` for details ✔ Successfully installed plugin: vue-cli-plugin-electron-builder ? Choose Electron Version ^6.0.0 <-- バージョンはとりあえず最新の6.0.0を選択(他のでも大丈夫です) ~~~~~~~~~~~~~~~~~~~~~~~~略~~~~~~~~~~~~~~~~~~~~~~~~~~ ✔ Successfully invoked generator for plugin: vue-cli-plugin-electron-builder The following files have been updated / added: src/background.js .gitignore package-lock.json package.json You should review these changes with git diff and commit them.お、なんだかコマンドが成功したようです。
electron-builder
によって、以下4ファイルが追加・更新されたようなので、バージョン管理している場合は忘れずにコミット・プッシュしておきましょう。
- src/background.js
- .gitignore
- package-lock.json
- package.json
いざ動作確認
package.json
のscripts
を見るといくつかエイリアスが追加されており、$ npm run electron:serve
で起動できそうなので、実行してみます。$ npm run electron:serve npm WARN lifecycle The node binary used for scripts is C:\Program Files (x86)\Nodist\bin\node.exe but npm is using C:\Program Files (x86)\Nodist\v-x64\12.11.1\node.exe itself. Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed with. DONE Compiled successfully in 7536ms 15:58:18 App running at: - Local: http://localhost:3000/ - Network: http://192.168.100.47:3000/ Note that the development build is not optimized. To create a production build, run npm run build. - Bundling main process... DONE Compiled successfully in 5384ms 15:58:23 File Size Gzipped dist_electron\index.js 651.00 KiB 148.88 KiB Images and other types of assets omitted. INFO Launching Electron...開発者ツールが表示されているので、×ボタンで閉じるとWebと同じ見た目になりました。
Auth0を使ったGoogleログインもでき、マークダウンエディタの機能も壊れることなくそのままデスクトップアプリとして動かすことができました。
まとめ
Vue.jsで作成したWebアプリは、vue-cliプラグインの
electron-builder
の力を借りることで、2コマンドだけでデスクトップアプリにすることができました。(うち1つはvue-cliのインストールだったので、vue-cliがインストールされていれば、1コマンドのみですね)デスクトップアプリとして動かした場合でも、Webで動いていた機能が壊れることなく動いたのは驚きました。(Electronの内部ではChromiumが使われているとのことなので、当たり前っちゃ当たり前ですが)
最近はPWAを使ってもデスクトップアプリっぽく見せることができるよう(Windows 10 1803の新機能「PWA」とは?PWAのUWPアプリ化を試してみる)ですので、そちらも試していきたいと思います。
- 投稿日:2019-12-14T16:30:20+09:00
thisでインスタンス内のデータにアクセス! ❏Vue.js❏
メソッドを作る際に、インスタンス内のデータを利用したい時があります。
そこで使うのがthis
です。
開発環境はJSFiddleです。
https://qiita.com/ITmanbow/items/9ae48d37aa5b847f1b3b使い方
this.
インスタンス内のプロパティ
sayHi
メソッドでインスタンス内のmessage
を返します。html<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <div id="app"> <p>{{ sayHi() }}</p> </div>javascriptnew Vue({ el: "#app", data: { message: "hello world!" }, methods: { sayHi: function() { return this.message; } } })【出力結果】
hello world!
this
はこれからたくさん使いそうだ。。。
ではまた!
- 投稿日:2019-12-14T15:57:00+09:00
初心者が Googleアシスタント と Vue.js を使ってスマートディスプレイアプリを作ってみた
はじめに
いちあき(@ichiaki_kazu)と言います。初めてのQiita記事です。
僕はこれまでアセンブラやHTMLくらいしか触ってこなかった(ほぼ)ノンプロフリーランスです。
それをフリーランスというのか置いといて…11月に行った「Google Nest Hub対応スマートディスプレイスキルを作ろう!【Vue.js】」というハンズオンが楽しかったので、勉強も兼ねてハンズオン内容を参考にしつつ自分で作ってみました。
この記事の目的
- 自身のやったことを整理して定着させる
- 共に音声アプリの概要を掴んでもらえたらな
もしかしたらこの通りやっても動かないかもしれないので鵜呑みにしないでください。
※知識不足により誤っている部分がある可能性があります。その時は優しく指摘してください…参考資料
・「Google Nest Hub対応スマートディスプレイスキルを作ろう!【Vue.js】」(2020/1に大阪でも開催)
・Qiita「滑舌チェックスキルをGoogle Nest Hubで実装してみた」完成物
【Google Nest Hub対応アプリつくった】
— いちあき@ぷちスマーティスト (@ichiaki_kazu) December 6, 2019
出てきた果物を英語で呼んであげるクイズアプリを作ってみました!
がおまるさん(@gaomar)が作られた滑舌チェックスキルを参考にさせていただいてます。元ネタは続きに…#GoogleNestHub #駆け出しエンジニアとつながりたい pic.twitter.com/87ZMpjG2DX音声でアプリを呼び出して、出てきたくだものの画像を見て名前を当てるゲームです。
幼児になら使ってもらえるかなって…今回使ったもの
- Google Nest Hub:実機動作確認のため
- Dialogflow:自然言語処理
- Vue.js(VueCLI):アプリの画面生成
- Azure Functions(CLI):内部の処理(クイズ部分とか入出力とか)
- Vuetify:Vueで使えるフレームワーク
- IntaractiveCanvas:(重要)Googleアシスタントアプリで画面描写に必要なライブラリ
実際に作ってみる
作るに当たってポイント部分だけを解説していきます。
実際に作ってみたい人は「滑舌チェックスキルをGoogle Nest Hubで実装してみた」をまず確認すると良いと思います。1.Dialogflow(言語処理をする)
Googleアシスタントによって入力された音声の処理を行います。
Intent(インテント)を作成することで、言葉に反応して何かを返します。1-1.インテント作成
Intent名 内容 Default Welcome Intent アプリ起動の言葉 EndIntent アプリ終了の言葉 MainIntent アプリ起動中の言葉 StartIntent アプリ起動後に開始する言葉 1-2.実際にインテントを作る(例:StartIntent)
- Training phrases:反応する言葉を登録
- Fulfillment:webhookでやりとりするためチェックを入れる
その他のインテントも同じように作ります。
ちなみに「Response」には反応時返すメッセージを登録できます。
詳しくは参考記事をご覧ください。2.Vue.js(アプリ画面を作る)
アプリの画面描写部分を作っていきます。
ざっくりとやることは以下の通り
- プロジェクト作成(vuetifyも入れる・今回はrouterも)
- index.htmlにIntaractivCanvasのAPI追加
- App.vueの修正
- Home.vueの修正
- HelloWorld.vueの修正
2-1.プロジェクト作成
プロジェクトを作ってvuetifyを入れます。
vue create kudamonoquiz-app vue add vuetify今回はプロジェクト作成時にrouterも入れておきました。
2-2.index.htmlにIntaractiveCanvasのAPI追加
画面描写のキモである「InteractiveCanvas」のAPIを引っ張ってきます。
public/index.html(12行目あたり)<script type="text/javascript" src="https://www.gstatic.com/assistant/interactivecanvas/api/interactive_canvas.min.js"></script>2-3.App.vueの修正
ここではヘッダー部分とrouterへの連携をしています。
routerは初期状態でHome.vueに流れます。src/App.vue<template> <v-app> <v-app-bar app> <v-toolbar-title class="headline text-uppercase"> <span>くだものくいず</span> </v-toolbar-title> </v-app-bar> <v-content> <router-view /> </v-content> </v-app> </template>2-4.Home.vueの修正
コンポーネント「HelloWorld.Vue」を表示するようにします。
src/Home.vue<template> <HelloWorld /> </template> <script> import HelloWorld from '../components/HelloWorld.vue' export default { name: 'home', components: { HelloWorld } } </script>2-5.HelloWorld.vueを修正(ポイント)
app側のメインコンテンツです。
src/Home.vue<template> <v-container> <!-- スタートページ --> <v-layout text-center wrap v-show="target === 'top'" > <v-flex xs12 md-10 > <h3 class="display-3 font-weight-bold mb-10"> </h3> </v-flex> <v-flex xs12> <h1 class="display-2 font-weight-bold mb-10"> くだものえいご </h1> </v-flex> <v-flex xs12 mb-4> <v-btn color="success" @click="start">スタート</v-btn> </v-flex> </v-layout> <!-- 問題表示ページ --> <v-layout text-center wrap v-show="target === 'kudamono'" > <v-flex xs12 md-10 > <h3 class="display-3 font-weight-bold mb-10"></h3> </v-flex> <v-flex xs12 mb-4> <h1 class="display-1 font-weight-bold mb-3"> 【くだものえいご】 </h1> </v-flex> <v-flex xs12 mb-4> <img class="img" :src="imgurl" alt="くだもの画像"> </v-flex> <v-flex xs12 mb-4> <h5 class="display-1 font-weight-bold mb-3"> このくだものはなーんだ? </h5> </v-flex> </v-layout> <!-- 正解後の表示ページ --> <v-layout text-center column align-center v-show="target === 'congratulation'" > <v-flex xs12 mb-10 > <h3 class="display-3 font-weight-bold"> </h3> </v-flex> <v-flex xs12 mb-4 > <img class="img" alt="congratulation" src="../assets/congratulation.png"> </v-flex> <v-flex xs12 mb-4> <img class="img" :src="imgurl" alt="くだもの画像"> </v-flex> <v-flex xs12 mb-4> <h2>だいせいかい!<br>これは{{kotae}}({{tango}})だよ!</h2> </v-flex> <v-flex xs12 mb-10 > <v-btn large color="success" @click="start">スタート</v-btn> </v-flex> </v-layout> </v-container> </template> <style scoped> .img{ width: 300px; } .endimg{ width: 200px; } </style> <script> export default { data () { return { target: 'top' } }, created(){ var me = this const callbacks = { onUpdate(data){ if('kudamono' in data){ me.kotae = data.kudamono.kotae, me.tango = data.kudamono.tango, me.imgurl = data.kudamono.imgurl, me.target = data.kudamono.target } }, } interactiveCanvas.ready(callbacks) }, methods: { start(){ interactiveCanvas.sendTextQuery('スタート'); } } }; </script>ざっくり解説するとこのファイルでは3つがポイントだと思います。
- targetの状態で表示するコンテンツ(開始時・ゲーム時・正解時)を分ける
- functionから送られてくるdata(画像URLや問題)をセットし、使用する
- 最初にスタートボタンを押した時
interactiveCanvas.sendTextQuery('スタート');
でDialogflowに「スタート」という文字列を送る特に文字列を送る昨日はinteractiveCanvasならではなので、ポイントかと思います。
2-6.package.json確認
後々動かす際にnpmインストールするのでpackage.jsonの確認をします。
{ "name": "kudamonoquiz-app", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build" }, "dependencies": { "core-js": "^3.4.3", "vue": "^2.6.10", "vue-router": "^3.1.3", "vuetify": "^2.1.0", "vuex": "^3.1.2" }, "devDependencies": { "@vue/cli-plugin-babel": "^4.1.0", "@vue/cli-plugin-router": "^4.1.0", "@vue/cli-plugin-vuex": "^4.1.0", "@vue/cli-service": "^4.1.0", "sass": "^1.19.0", "sass-loader": "^8.0.0", "vue-cli-plugin-vuetify": "^2.0.2", "vue-template-compiler": "^2.6.10", "vuetify-loader": "^1.3.0" } }おそらくこの状態だと思いますが、念のための確認です。
3.Azure Functions(内部のプログラムを作る)
3-1.CLIツールをインストールする
とりあえずCLIツールをインストールします
npm install -g azure-functions-core-tools3-2.プロジェクトを作成する
このあたりは完全にこの記事と同じです。
$ mkdir kudamonoquiz-app-functions $ cd kudamonoquiz-app-functions $ func init # 選択肢が出てくるのでnodeとjavascriptを選ぶ $ func new # Http triggerを選択し、kudamonoquiz-appという名前で作成する $ npm init -y $ npm i -s actions-on-google@2.10.0 # 2.10.0を入れる $ npm i -s azure-function-express # azure-function-expressを入れる $ npm i -s express $ npm i -s firebase-admin3-3.functions.jsonを編集
完全にこの記事と同(ry
GETのみに変更して、どこからでもアクセスできるようanonymousにします。kudamonoquiz-app/functions.json{ "bindings": [ { "authLevel": "anonymous", // anonymousにしておく "type": "httpTrigger", "direction": "in", "name": "req", "methods": [ "post" // getは使わないので消しておく ] }, { "type": "http", "direction": "out", "name": "res" } ] }3-4. index.jsを編集
ワードが入力された際にインテントに応じて処理を変えます。
kudamonoquiz-app/index.jsconst createHandler = require("azure-function-express").createHandler; const express = require("express"); const {dialogflow, HtmlResponse} = require('actions-on-google'); const app = dialogflow({debug: false}); //スタート処理 app.intent('StartIntent', async (conv) =>{ //問題の生成 const kudamonoquiz = [ {"imgurl": "/img/banana.jpg", "kotae": "バナナ", "tango": "banana"}, {"imgurl": "/img/cherry.jpg", "kotae": "チェリー", "tango": "cherry"}, {"imgurl": "/img/grape.jpg", "kotae": "グレープ", "tango": "grape"}, {"imgurl": "/img/melon.jpg", "kotae": "メロン", "tango": "melon"}, {"imgurl": "/img/orange.jpg", "kotae": "オレンジ", "tango": "orange"}, {"imgurl": "/img/peach.jpg", "kotae": "ピーチ", "tango": "peach"}, {"imgurl": "/img/remon.jpg", "kotae": "レモン", "tango": "remon"}, {"imgurl": "/img/strawberry.jpg", "kotae": "ストロベリー", "tango": "strawberry"} ]; const wordIndex = Math.floor(Math.random() * kudamonoquiz.length); const selKudamono = kudamonoquiz[wordIndex]; conv.contexts.set('game', 5, selKudamono); conv.ask('このくだものを英語で言ってみてね'); selKudamono["target"] = "kudamono"; conv.ask(new HtmlResponse({ data: { kudamono: selKudamono } })); }); //ゲーム処理 app.intent('MainIntent', async (conv, {any}) => { const context = conv.contexts.get('game'); if(context.parameters.kotae === any){ conv.contexts.delete('game'); conv.ask(`大正解。答えは ${context.parameters.kotae} でした! もう一度クイズをするなら「する」終了するなら「終了」と言ってください。`); context.parameters["target"] = "congratulation" }else{ conv.ask('よくわかりませんでした。もういちど言ってみてね。'); } conv.ask(new HtmlResponse({ data: { kudamono: context.parameters } })); }); //起動時 app.intent('Default Welcome Intent', (conv) => { conv.ask('果物英語をはじめるには、スタートボタンを押してください。'); conv.ask(new HtmlResponse({ url: 'https://{表示させたいホームページのURL}', supperss: true })); }); //functionの名前を一致させておく const expressApp = express(); expressApp.post('/api/kudamonoquiz-app', app); module.exports = createHandler(expressApp);実際に動かしてみる
プログラムを動かしていきます。
Vue.jsの起動
npmインストール
kudamonoquiz-appでnpmインストールします。
cd ./kudamonoquiz-app npm installプログラム実行
npm run serveこれでhttp://localhost:8080にアクセスできます。
トップ画面が表示されます。
僕はこのあとngrokを使いましたがとりあえずこれでも動くと思います。Azurefunctionsの起動
npmインストール
kudamonoquiz-functionでnpmインストールします。
cd ./kudamonoquiz-function npm installindex.js内にアプリのURLを記述する
kudamonoquiz-function/kudamonoquiz-app/index.js(抜粋)//起動時 app.intent('Default Welcome Intent', (conv) => { conv.ask('果物英語をはじめるには、スタートボタンを押してください。'); conv.ask(new HtmlResponse({ url: 'http://localhost:8080', supperss: true })); });ローカルサーバを起動する
func host starthttp://localhost:8080/でアプリが起動します。
Dialogfrowの設定
FulfillmentのwebhookURLを指定する
テストする
IntegrationからGoogle Assistantを選択
出てきたポップアップで「Auto-preview changes」にチェック入れて「TEST」
Deployで「category」を「Games&fun」にする。※InteractiveCanvasに必須
Testから実際に動作を確認する。
Androidスマホがある方はそちらからでも確認できます。
今回はテストまでなのでデブロイはなしで、ここまでとなります。
ここまで実施すれば実機でテストバージョンとして動作確認もできます。以上です。
もしかしたら手順漏れなどあるかもしれませんが大体こんな流れです。
興味ある方は是非是非試してください〜僕も答えられるかわかりませんが、不明点がありましたらお願いします!
宣伝みたいな
https://atlabo.connpass.com/event/157824/
2020年の1/23(木)に僕がスマートディスプレイにハマったきっかけのハンズオンが大阪でも開催されます。
興味ある方はおすすめですのでぜひ!※僕もスタッフとして行きます
- 投稿日:2019-12-14T14:38:57+09:00
element-ui のTextAreaのスタイルをカスタムする
既存のコンポーネントを流用しつつ、スタイルなど一部を変更して使いたい場合に、どうするのか。
vue 2.6.10
課題
拡張したいコンポーネントはElementUI TextAreaです。
本当はゼロから自作しようかと思ったのですが、autosizeを使いたかったので、やむなく拡張することに。NG
TextAreaコンポ―ネントは、type属性に応じてinputだったり、textareaだったりの要素を自動生成し、さらに
<div class="el-textarea"></div>
というラッパーをかましているせいか、下記のような感じでは無理でした。<template lang='pug'> el-input.my-comp(type='textarea' :autosize="{ minRows: 3, maxRows: 6 }" placeholder='hoge') </template> <style lang='sass' scoped> .my-comp /deep/ textarea color: red </style>※ 関係ないところは省いてます
※/deep/
については、こちらを参照してくださいextendsを使ったパターンも試しましたが、なんか駄目(やり方間違った可能性はあり、そのうちちゃんと調べます)。あとは、 attrsを使うとか?でも、もっとシンプルで良いなと思って試してません。
現時点でのシンプルな解決方法
is を使う方法です。パット見ちょっと変ですが、ほぼ理想的なシンプルさで実現できました。
<template lang='pug'> div(is='el-input' type='textarea' :autosize="{ minRows: 3, maxRows: 6 }" placeholder='hoge') </template> <style lang='sass' scoped> div /deep/ textarea color: red </style>
- 投稿日:2019-12-14T13:37:47+09:00
RailsアプリをNuxt.jsに移行する際のTIPSいろいろ
この記事は Nuxt.js Advent Calendar 2019 16日目の記事です。
この記事では、Ruby on Railsアプリのview部分をNuxt.jsに移行した話を元に、Nuxt.jsでの開発全般の知見を紹介します。
※Railsの話はほぼ出てきません!移行前の状況
フリーランスとしてジョインしたWebサービスが、以下の状況でした。
- 全体的に6年前くらいの技術スタックのRailsアプリ(Rails4.0, svn, jQuery...など)
- サーバー側はModel, View, Controllerとどこもコードの量が多く煩雑で、リファクタリングが辛い。テストもほぼない。
- フロントエンドも適切にファイル分割されておらず、1つのcssファイルが一万行あったり、jsがRailsのテンプレートにベタ書きされている
- ちょうど一部ページのフルリニューアルの計画がある(!)
移行のモチベーション
「今後の開発効率が上がり、様々な機能を今後開発しやすくなるのがメリット」
ということを、会社の経営陣などステークホルダーにお伝えして、承認を得ました。
具体的には以下をお伝えしました。開発上のメリット
- Hot Module Replacementによるコーディングの即時反映
- コンポーネント開発が強制され、jsやcssの見通しが良くなる
- 既存の状態では誤ったcss変更によるレイアウト崩れがしばしば発生していた
- また、cssを変更できる人が限られていた
- 非同期処理やアニメーションのロジックが簡潔に見通しよく書ける
- jQueryで頑張るのはもうつらい
ユーザー側のメリット
- アプリのようなリッチなUXを提供しやすい。
- 開発効率が上がる分、ユーザーに本当に提供すべきことに開発を集中できる
個人的に、ある程度リッチなUIを作るのであれば、もはやRailsでフロントエンドをやる時代ではないと思っています。
(そして、ある程度リッチなUIはもはや現代のWebサービスでは必須と考えています)この辺の話は、以下スライドが参考になるかと思います
私たちはなぜ SPA で開発するのか / Why you choose SPA
移行後の構成
- リニューアルするページ: Nuxt.js(SSR) + Ruby on Rails(API)
- 旧ページ: Ruby on Rails
という構成で、もともとのRoRアプリにAPIを生やしつつ、Nuxt.jsを別サーバーとして立ち上げることにします。
旧ページも今後すべてNuxtに移行し、RoRはAPIのみとする予定ですが、全てを一度にリニューアルするのはボリュームが大きすぎるため、一旦一部ページのみとしました。ちなみに、Nuxt移行の前に、開発環境を整える作業を1ヶ月で完了しました。
(Rails4.0->6.0, svn->git, EC2->GAE, MySQL on EC2 -> Cloud SQL, もろもろのリファクタリングなど)移行のTIPS
ルーティングについて
今回は一部ページのみrailsで動き続けるため、リクエストを適切にnuxtかrailsに振り分ける必要があります。
今回はこれを「GAEによるdispatch」「Nuxtによるリダイレクト」の2つで移行を実現します。GAEのdispatch
GAEは以下のような
disaptch.yml
を書くだけでルーティングを変えることが可能で、非常に楽なのでオススメです。before
dispatch: - url: "*www.example.com/*" service: railsafter
dispatch: - url: "*www.example.com/*" service: nuxt - url: "*www.example.com/admin/*" service: rails - url: "*www.example.com/api/*" service: railsこれだけで済めば万歳だったのですが、GAEでは
*
がURLの最初か末尾にしか使えず、複雑な正規表現などは使えないため、これだけでは要件を満たせませんでした。Nuxtのリダイレクト
GAEレイヤでのルーティングで対応できない箇所は、Nuxtにきたリクエストをリダイレクトすることにします。
Nuxtでこれを行いたい場合 @nuxtjs/redirect-module を使うと良いでしょう。
// nuxt.config.js { modules: [ '@nuxtjs/redirect-module' ], redirect: [ { from: '^/hoge', to: 'https://www.external.com/hoge', } ], }モジュールの中では、
addServerMiddleware
を使ってリダイレクトの処理を行なってくれます。
外部へのリダイレクトを行いたい場合、vue-router
を使うのではなく、serverMiddleware
の機構を使ってリダイレクトをすべきであることに注意しましょう。APIとのつなぎ込み
RoRからSPA+APIに移行する際のオーバーヘッドとして、APIとのつなぎ込みが頭に浮かぶかもしれません。
ここに関してはnuxt-resource-based-apiというライブラリを使っているため、ほぼオーバーヘッドはありません。
例えば、Pageコンポーネントは以下のように書くだけです。<script> import createComponent from '@/lib/create_component' export default createComponent([ { resource: 'task', action: 'index' }, // APIのコントローラー、アクションを指定 ]) </script> <template> <div> <div class="task" v-for="task in tasks"> {{ task.name }} </div> </div> </template>詳細は以下をご覧ください。
爆速でnuxtとAPIを繋げるnuxt-resource-based-apiの紹介ディレクトリ構成
pages
ディレクトリNuxt.jsではpagesディレクトリ配下の構成が、そのままルーティングになります。
Railsでリソースベースでルーティングを行なっていると、
/users/123/tasks/456
のようなパスを作ることがあると思いますが、
この場合はpages/users/_id/tasks/_taskId/index.vue
というコンポーネントを作成すると、正しくルーティングされます。
idにはroute.params.id
route.param.taskId
のような形でアクセスできます。
ということで、無事Railsのパスをそのまま使うことができます。注意として
_id.vue
ではなく_id/index.vue
を作ることを推奨します。
pages/users/_id.vue
が存在する状態で、pages/users/_id/hoge.vue
というコンポーネントを作成し、users/123/hoge
にアクセスすると、期待通りの挙動をしません。
この辺は以下を参照にしてください。Nuxt.jsのネストした動的ルーティングで困ったので調べてみた
componentsディレクトリ
Nuxt.jsは他の同様のフレームワークと比べると、ディレクトリ構成の制約が強いフレームワークですが、RoRに比べると弱いですよね。
特にcomponentsディレクトリ以下について、構成のベストプラクティスは特に定まっていない認識です。ここについて、少なくともRailsアプリ開発経験者には、Railsと同じくパスベース+
shared
ディレクトリを使った、以下のような構成が分かりやすいと考えています。(components/以下のディレクトリ構成例) . ├── users │ ├── articles │ │ └── A.vue │ └── shared │ ├── B.vue │ ├── C.vue ├── articles │ └── D.vue ├── layouts │ ├── Footer.vue │ ├── Header.vue │ └── header │ ├── Logo.vue └── shared ├── E.vue ├── card │ ├── ArticleCard.vue └── icon ├── FacebookIcon.vue ├── LineIcon.vueより具体的には、以下のルールを
README.md
に明記しています。
- コンポーネント Foo が、1 つの Page コンポーネントでしか使われない場合 -> pages に対応するディレクトリに格納する
- (例)
pages/users/index.vue
のみで使うコンポーネントのパスはcomponents/users/Foo.vue
- コンポーネント Foo が、複数の Page コンポーネントで使われる場合 -> 共通する名前空間として最大の名前空間となるディレクトリに
shared
を作って格納する
- (例)
pages/users/a/index.vue
pages/users/b/index.vue
で使うコンポーネントのパスはcomponents/users/shared/Foo.vue
shared/
以下のディレクトリ構成は、できる限り意味のあるまとまりごとに格納する。(共通認識を得るのが難しいため、厳密に管理しない)
- (例)
shared/icon/TwitterIcon.vue
shared/card/ArticleCard.vue
など。shared/bar/
ディレクトリのコンポーネント名は*Bar.vue
を推奨。- コンポーネントの数が増えてきた場合、atomic design などを取り入れつつ、UIのグルーピングの単位をチーム内でしっかりと共通認識を揃えて、ディレクトリを作成する
components/layouts/
のみ特殊で、layouts/*.vue
で使われるコンポーネントを格納するOGPやタグ
SSRしてNuxtを使うのは初めてだったので、OGPがちゃんと生成できるかドキドキでしたが、ちゃんと
fetch()
で取得してきたAPIレスポンスを元にタイトルやOGPを生成することができました!すごい(小並)
OGPはheadメソッドを使うことで設定できます。また、GoogleAnalyticsやGoogleTagManagerなどは、既にモジュールがあるので、RoRよりもむしろ簡単に導入できるかと思います。
Google アナリティクスを使うには?エラーハンドリング
もともとbugsnagを使っていたので、nuxt-bugsnagを入れました。
これにより、サーバー/クライアント両方でのエラー通知が可能になります。便利なモジュールがたくさん公開されているのも、Nuxtの良いところですね。
ページネーション
レコードを全件fetchしてよければvuejs-paginateのようなライブラリを使うのが良さそうですが、
総レコード数が多い場合は、フロント側だけでページネーションの機構を作るのは難しいです。そのため、結局ページネーションに必要な情報はサーバーサイドで全て算出して、レスポンスとして渡すことにしました。
RoRを使ってれば、ページネーションはkaminariを使うと思いますが、kaminariのメソッドのレスポンスをそのままAPIにのせる感じ。なんかイマイチ納得いってないので、知見あれば教えてください# Rubyのコードです class Api::BaseController < ApplicationController private def paginate(relation, page, per_page, includes: []) paged_relation = relation.page(page).per(per_page) { page_meta: { total_pages: paged_relation.total_pages, total_count: paged_relation.total_count, current_page: paged_relation.current_page, current_cursor_start: (page - 1) * per_page + 1, current_cursor_end: [page * per_page, paged_relation.total_count].min }, records: paged_relation.includes(includes) } end end総括
RailsアプリをNuxtにリニューアルすることで、サーバー側はシンプルなAPIの実装で見通しよく、フロントもコンポーネント化によりjsとcssが非常に見通しよくなりました!
また、マークアップを担当していただいているエンジニアさんにも好評でした。この辺は、Reactだと難しい部分だと個人的に考えています。移行に迷っている方の参考になれば幸いです!同様の知見やご意見などお気軽にコメントください。
- 投稿日:2019-12-14T13:17:21+09:00
aタグのhref属性に、コンポーネントの特定のプロパティを指定する
v-bind:属性名="プロパティ名"
- taskコンポーネントが、無効な位置(invalid_location)というプロパティを持つとする。
task.vue<a v-bind:href="task.invalid_location">無効な位置</a>これで
task.invalid_location
をhref属性に指定したリンクが生成される。
- 投稿日:2019-12-14T13:05:47+09:00
vuetifyをlaravelに入れる
vuetifyとは
公式サイトでは、全てのユーザーにリッチなWeb開発体験をお届けする、「one of the most popular JavaScript frameworks in the world」であると謳っている。
定期的なupdateやサポートも行っている模様。他のvue.jsフレームワークとの比較の表が下記。なかなか魅力的なライブラリであるように思えます。
まんまと公式の謳い文句にのせられた感じはしますが、それじゃ使ってみようじゃないかと思い、自身の勉強用に作成したwebアプリに導入してみました。(laravel+vue+docker)
導入方法
閑話休題、本題の導入方法ですが、基本公式ページのやり方に沿えば簡単にインストールできますが、備忘録も兼ねて下記に手順を記載します。
(*ちなみに私の環境はlaravel6.6.2です。)1.npmでvuetifyをダウンロード
npm install vuetify npm install sass sass-loader fibers deepmerge -D2.src/resources/js/app.jsに下記を追加
src/resources/js/app.jsimport Vuetify from 'vuetify'; import 'vuetify/dist/vuetify.min.css'; Vue.use(Vuetify); const app = new Vue({ el: '#app', vuetify: new Vuetify() });これだけです。
試しに、Example-component内に、公式にある[cards]コンポーネントをサンプルのまま導入してみます。
src/resources/js/components/ExampleComponent.vue<v-app> <template> <v-card class="mx-auto" max-width="400" > <v-img class="white--text align-end" height="200px" src="https://cdn.vuetifyjs.com/images/cards/docks.jpg" > <v-card-title>Top 10 Australian beaches</v-card-title> </v-img> <v-card-subtitle class="pb-0">Number 10</v-card-subtitle> <v-card-text class="text--primary"> <div>Whitehaven Beach</div> <div>Whitsunday Island, Whitsunday Islands</div> </v-card-text> <v-card-actions> <v-btn color="orange" text > Share </v-btn> <v-btn color="orange" text > Explore </v-btn> </v-card-actions> </v-card> </v-app> </template>
- 投稿日:2019-12-14T11:56:17+09:00
UnityでVue.js風のリアクティブシステムを作ってみる
はじめに
こんにちは、QualiArts Advent Calendar 2019、14日目の記事になります。
直近はUnityの開発していますが、その前はずっとウェブのフロントをやっていたため、本記事はVue.jsの(個人的に)目玉機能であるリアクティブシステムをUnityで再現してみるという話について書きます。
※日本語がネイティブではなくて、文書が読みづい可能性がありますので、御了承してください
![]()
Vue.jsのリアクティブシステム
Vue.jsの事が知らない方に簡単の例↓を出します。詳しく知りたい方はVue.js公式でどうぞ
var app = new Vue({ el: '#app', data: { familyName: "佐藤", givenName: "太郎" }, computed: { fullName() { console.log('[実行されたよ]'); return this.familyName + this.givenName; } } }); console.log(app.fullName); // [実行されたよ] // 佐藤太郎 app.familyName = '鈴木'; console.log(app.fullName); // [実行されたよ] // 鈴木太郎 console.log(app.fullName); // 鈴木太郎
data
に入る物は全てリアクティブプロパティであり、computed
に入る物は算出プロパティでありますリアクティブプロパティ:アクティブデータの源泉であり、値の更新が監視できます
算出プロパティ:基本的に呼び出さない限り実行されない、かつ依存しているリアクティブプロパティが変わらない限り再実行もされない仕様であります裏には遅延処理とキャッシュが自動的に行われるため、使う側は直感的なコードが書けて、無駄の再実行も自動的に管理してくれる素晴らしい機能です
Unityのプロパティ周り(C#)
C#言語にはプロパティというのあります、上記の機能をざっくり再現してみましょう
public class App { public string familyName = "佐藤"; public string givenName = "太郎"; string _fullName; public string FullName { get { var value = familyName + givenName; if (value != _fullName) { _fullName = value; } return _fullName; } } }厳密に一緒ではないですが、
familyName + givenName
の再実行は避けれない事になります。例えば、
familyName
とgivenName
の更新フラグを追加し、それぞれの更新チェックをする処理をすれば実現できますが、コード量はおそらく2倍になるでしょう。その他のやり方
- 上記の比較・キャッシュ処理をメソッド化し、コードを簡略する
- INotifyPropertyChanged プロパティ更新のイベント発火で検知する
- UniRxのReactiveProperty プロパティをObservable化して、更新購読する
- など
欲望
色んなやり方は既にありますが、せっかくなのでVue.jsに近い形で再現できないかというのはきっかけです。例えば↓のような感じです。
public class App { public string familyName = "佐藤"; public string givenName = "太郎"; public string FullName => familyName + giveName; }IL修正 との出会い
色々調べていた所、
INotifyPropertyChanged
のinterface実装を自動化したライブラリー (Fody/PropertyChanged)を見つけました。
Fody
自体はMono.cecil
のラッパーで、アセンブリのILを修正したりできるライブラリーです。
これを使えばできるじゃないかと思ってissues調べたら、どうやらUnityは未対応のようです。むむ、もうちょっとググったり、github内検索したりしてみたら、Unityに対応するライブラリー (ByronMayne/Weaver)が出てきました。ちゃんとUnityのコンパイル後にフックで実行されますので、これを採用する事にしました。
プロトタイプを作ってみる
IL周りを実装する前に、Vue.jsのリアクティブ実装を参考しながら、C#のベースクラスを作成してみました。
ざっくりの原理
- あるWatcher(A君)がいます。
- このA君があるWatcher(B君)に依存して、B君の値を取得します。
- この時、B君が依存する他のWatcher(C君)がいれば、A君もC君に依存するような関係性を持たせるようにします。
- 依存がなくなるまで再帰的に続きます。
この原理に基づいてプロトタイプを作成しました。
IL修正 でコード簡略化する
その後、
[Reactive]
と[Computed]
2種類のAttributeを用意し、AttributeをつけたらWatcherのインスタンスの生成処理やGetter/Setterの処理委託をILで自動挿入するようにしました。public class App { [Reactive] public string FamilyName { get; set; } = "佐藤"; [Reactive] public string GivenName { get; set; } = "太郎"; [Computed] public string FullName => FamilyName + GivenName; }↑のコードはIL挿入後、↓という風に展開されます
public class App { Wacther<string> _familyName; public string FamilyName { get { if (_familyName == null) { _familyName = new Watcher<string>(); } return _familyName.Get(); } set { if (_familyName == null) { _familyName = new Watcher<string>(); } _familyName.Set(); } } Wacther<string> _givenName; public string GivenName { get { if (_givenName == null) { _givenName = new Watcher<string>(); } return _givenName.Get(); } set { if (_givenName == null) { _givenName = new Watcher<string>(); } _givenName.Set(); } } Wacther<string> _fullName; public string FullName { get { if (_fullName == null) { _fullName = new Watcher<string>(() => { return FamilyName.Get() + GivenName.Get(); }); } return _fullName.Get(); } } }これで動ける最低限のプロトタイプは完成しましたが、実用までにはまだ改善と追加機能が必要です。必須的な機能例:
・ Setterの通知をまとめて次のTickで行う(重複実行を防ぐ、循環参照の検知)
・ WatcherにIDをつけて、通知はID順で実行する(実行順番の保証)終わりに
Viewにバインディングする機能はまだないので、実質全然使えないプロトタイプですが、UnityでもVue.js風の書き方ができそうという事が証明できたじゃないかなと思っています。
C#初心者だったので、IL周りの試行錯誤を通してC#の裏はこんな感じなんだを知れたし、コンパイラはいかに便利かも知れたので、個人的に面白い体験と思いました。
上記のプロトタイプ実装はgithubに公開していますので、興味ある方ぜひこちらどうぞ。
https://github.com/thammin/kaki-watcher以上、14日目の記事でした。引き続き今後のカレンダー投稿を宜しくお願いします。
- 投稿日:2019-12-14T10:55:43+09:00
[勉強用](随時更新)Vueを勉強しようとおもったら久々にWebにてお出すことになった話(応援歓迎)
開発に合わせたGithubはこちら
https://github.com/KeiMae/vue-study何をやってみようかと
普段データサイエンスをしていているだけど、簡単なレポートはBIツールではなくWebでも見せられるようにしてみようかなぁと思い立って。
データサイエンスらしく普段はpythonしか描かないのですが、さすがにモノスキルは良くないと思い、Golangにいまさら着手しようと。
詳しいコンテンツはPageTopのgithubを見てくださいさて
普通のpythonだと、インフラは大体こんな感じじゃないかと
- パッケージマネージャ
- pyenv
- 仮想環境
- virualenv
サイエンスならcondaなのでしょうけど。
確かにこれでパッケージのバージョン管理とかもできるし、(今回はやらないけど)検証・本番環境へのリリースもある程度簡単にできる。しかし、いま(2019年)はコンテナ全盛期。 ちょっとコンテナ開発を試みます。自分の思うコンテナ
きっと仮想環境を作ってくれる、、、つまり、ミドル層でのバージョン管理なんかは扶養。。コンテナ内にGlobal installして構わないはず!
間違ってたら教えてください・・・
開発環境
とりあえず、自分用Macのローカルに構築
- OS
- macOS Catalina 10.15
- Docker
- 2.1.0.5
GOAのコンテナの用意
GolangでAPIを作るときWebServerをどうするか。フレームワークはどうするか。が早速ハマる(Golangマジでおっかけなかった)。
調べたらgoaが結構ヒットするので、とりあえず、日本語文献が多そうなgoaからスタート
https://qiita.com/loftkun/items/1a9951d1864bebdc51e1
を見るとgoaよりgo-kitとかの方がグローバルスタンダードっぽいけど、、参考文献
「Go言語Webフレームワークランキング」 view at 2019/12/07
https://qiita.com/loftkun/items/1a9951d1864bebdc51e1
- 投稿日:2019-12-14T08:53:32+09:00
【Vue.js】【ESLint】スコープ付きCSSの利用されていないセレクターを検出するESLintプラグイン作った
この記事は、Vue Advent Calendar 2019 #1 の14日目の記事です。
こんにちは。社内ではLintおじさんという二つ名を襲名していて、@ota-meshiという冷やかし感満載なアカウントで割と真面目にやってるつもりの者です。
本記事では、社内でこそこそ作っていたVue.jsのスコープ付きCSS用のESLintの拡張ルールの一部を先日npmで公開したので、その紹介とどんな感じで作ったのかを書こうと思います。
まず、公開したものは以下です。
- ドキュメント eslint-plugin-vue-scoped-css
- npm - eslint-plugin-vue-scoped-css
- GitHub - future-architect/eslint-plugin-vue-scoped-css
下記リンクからブラウザ上で試すことができます。
https://future-architect.github.io/eslint-plugin-vue-scoped-css/playground/特徴
Vue.jsの単一ファイルコンポーネントで利用できる、スコープ付CSS関連のLintルールを提供しています。
動機
eslint-plugin-vueという
.vue
ファイルをLintできる素晴らしいESLintプラグインがありますが、これはCSS(<style>
ブロック)関連の情報は関知しません。
また、.vue
ファイルでも利用できるCSSのリンターである、stylelintという素晴らしいツールもありますが、こちらはCSS以外の情報(例えば<template>
ブロック)は関知しません。
これらのツールで実現できない、Vue.jsのスコープ付CSS特有の静的検証を行いたかったというのが、このESLintプラグインを作成した動機です。(stylelintプラグインで作成しても良かったんじゃないの?というのはありますが、
.vue
ファイル用の神パーサーであるvue-eslint-parserを使いたかったのもありESLintプラグインで作成しました。)使用方法
インストール
npmでインストールします。
npm install --save-dev eslint eslint-plugin-vue-scoped-css設定
詳しくは以下を参照してください。
https://future-architect.github.io/eslint-plugin-vue-scoped-css/user-guide/#usage
このプラグインは設定構成を提供しているので、ESLint - Using the configuration from a pluginにある方法を利用して、(例えば
.eslintrc.js
であれば)以下のように設定して利用できます。.eslintrc.jsmodule.exports = { extends: [ // ... // ... 既にあなたの利用している設定 // ... // 下記を追加 'plugin:vue-scoped-css/recommended' // ルールを自分で個別に設定したい場合は下記を追加 // 'plugin:vue-scoped-css/base' ], rules: { // 個別にルールを設定する場合は下記のように追加。 // 'vue-scoped-css/no-unused-selector': 'error' } }提供するLintルール
vue-scoped-css/no-unused-selector
このプラグインの目玉機能です。(というかこのルールを作りたくてこのプラグインを作り始めました。)
<style scoped>
内のCSSセレクターの内、<template>
ブロックで利用されていないCSSセレクターを検出します。このルールによって、リファクタリングしていくうちに使われなくなったのにウッカリ残してしまって、利用していない無駄なCSSをスッキリ消していくのに役立ちます。
<template> <div id="foo"> <input class="bar"> </div> </template> <style scoped> /* ✗ BAD */ ul, .foo, #bar { } /* ✓ GOOD */ div, #foo, .bar { } </style>vue-scoped-css/no-unused-keyframes
こちらは、利用していない
@keyframes
を検出します。たまに勘違いする人がいると思うのですが(自分も勘違いしていました)、
<style scoped>
内で宣言した@keyframes
は、その<style scoped>
内でしか利用できません。
なので、<style scoped>
内で宣言した@keyframes
は、その<style scoped>
内で利用すべきです。利用しない場合は、無駄なデッドコードとなります。<style scoped> .item { animation-name: slidein; } /* ✗ BAD */ @keyframes fadein { } /* ✓ GOOD */ @keyframes slidein { } </style>vue-scoped-css/require-scoped
scoped
が付与されていない、<style>
タグを検出します。私のような、スコープ付CSS大好き人間が
<style scoped>
を強制したい場合に利用できます。
唯一App.vue
にだけscoped
無しの<style>
を許可したいような場合は、.eslintrc.**
をうまいこと構成・配置するか、<script>
内にeslintの構成コメントを利用して除外するといいと思います。<!-- ✗ BAD --> <style> </style> <!-- ✓ GOOD --> <style scoped> </style>このルールはESLint v6.7で追加されたSuggestions APIに対応していまして、Suggestionから
scoped
属性を追加できるようにしています。VSCodeが対応したらクイックフィックスから選択できるようになるかもしれません。vue-scoped-css/require-selector-used-inside
先に紹介したvue-scoped-css/no-unused-selectorのもっと強制する版です。
<style scoped>
内のCSSセレクターの内、<template>
ブロックで利用されていないCSSセレクターを検出します。
こちらのルールは定義したCSSセレクターの各セレクター要素が全て<template>
ブロックで利用されていない場合、レポートされます。どういうことかと言いますと、
<template> <div> <input class="foo"> </div> </template> <style scoped> .theme .foo {} </style>とあった場合、
.theme .foo
の.foo
は利用できる場合があります。
theme
クラスをページのルート要素に付与して.foo
のスタイルを変更するみたいなことができます。(この使い方が想定されているかどうかは知らないです。)
そのため、vue-scoped-css/no-unused-selectorでは検出しません。
いや、そんな使い方しないよ!やめてよ!という人はvue-scoped-css/require-selector-used-insideを有効にすると、全てのセレクター要素が<template>
ブロックで利用されていない場合にエラーになります。vue-scoped-css/no-parsing-error
CSSのパースエラーを報告します。
パースエラーはstylelintとか使っておけば確認できるので必要ないんですけど、このプラグインのルール達がパースエラーで動作しないとき、「パースエラーだから動かないんだよ」ってことに気がつくためのルールです。通常は不要なルールです。
構文サポート
CSSとSCSSとStylusをサポートしています。
他は要望があればやるかもしれません。lessは全く知らないので誰か助けて。作り
機能的な紹介は以上です。ここから先は作った時の思い出です。
CSSとセレクターのパース
CSSのパースにはPostCSSを利用しています。
そして、SCSSのパースにはpostcss-scssを使っています。
そして、セレクターのパースにはpostcss-selector-parserを使っています。
PostCSSファミリー万歳です。あと、Stylusのパースには「VueファイルのStylusをstylelintしたかった話」の時に作った、postcss-stylを使っています。
セレクターのネストの解決
自作しました。車輪の再発明感ハンパないのですが、正しい箇所にエラーをレポートしたかったので、ここは自作に踏み切りました。
CSSの&
セレクター・@nest
・SCSSのネストに対応しているはずです。一応Stylusのセレクターも一部対応してます。CSSセレクターと
<template>
のマッピングvue-scoped-css/no-unused-selectorとかやるために
<template>
内のタグを走査します。
これがなかなか大変でした。全部書くと長いので(既に長いですが)class
属性についてだけ思い出を残します。静的
class
属性のマッピングこれは余裕です。普通のHTML的に探し出すだけです。
<template> <div class="foo" /> <!-- そのまま書いてあるので比較的簡単 --> </template>
v-bind:class
のインライン式のマッピングちょっと大変でしたが、神パーサーvue-eslint-parserがいい感じのASTを返してくれるのでまだなんとかなります。
<template> <div v-bind:class="['foo', {'bar': true}]" /> <!-- ASTの解析が必要 --> </template>
v-bind:class
へのプロパティのマッピングそろそろきついです。Vueオブジェクト探し出して、ASTを走査し、
data
とcomputed
のプロパティを探して、return
部分を解析して頑張りました。<template> <div v-bind:class="classes1"> <!-- dataを見ると`foo`が入る --> <div v-bind:class="classes2" /> <!-- computedを見ると`bar`が入る --> </div> </template> <script> export default { data () { return { classes1: ['foo'] } }, computed: { classes2 () { return [{'bar': true}] } } } </script>
v-bind:class
への文字列結合のマッピングきついです。テンプレートリテラルや、文字列結合があった場合は、部分一致で検証しています。
<template> <div v-bind:class="`foo-${kind}`" /> <!-- `foo-bar`は一致するとみなす --> </template> <style scoped> .foo-bar {} </style>
classList.add
のマッピングきついです。classListによるclass操作は
$el
や$refs
経由での操作を探し出してマッピングしました。<template> <div> <!-- $elには`classList.add('foo')`されることがある --> <div ref="div1"> <!-- ref名`div1`には`classList.add('bar')`されることがある --> <div> </template> <script> export default { mounted () { this.$el.classList.add('foo') }, methods: { onClick() { this.$refs.div1.classList.add('bar') } } } </script>複雑な
v-bind:class
へのプロパティのマッピング無理でした。
data
やcomputed
のプロパティの、return
部分だけで解決できない情報で、さらにeslint-utilsのgetStaticValue
で解決できない情報は諦めて、検出対象から除外しています。<template> <div v-bind:class="classes" /> <!-- 何が入るかわからないので全てのクラスが一致するとみなす。 --> </template> <script> import CONST from './const-data' export default { computed: { classes () { return [{[CONST.CLASS_FOO]: true}] } } } </script>まとめ
と、色々頑張った結果、概ね未使用CSSセレクターを検出することができたと思います。
結構頑張ったのでスコープ付CSS使っている方はぜひ使ってみて欲しいです。
もし使ってみて良かったらGitHubでつけてくれると励みになりますm(_ _)m
あとがき
このESLint拡張ルールですが、台風で流れたVue Fesのランチ(弊社はランチスポンサーする予定でした)で少し紹介する予定でしたが、行き場を失ったのでこの場で紹介させていただきしました。
来年もVue Fesあるといいなー。あと。そうそう。僕、今年、Vue.jsのメンバーになりました
関わってくださった皆様ありがとうござます!これからもお宜しくお願いします!
- 投稿日:2019-12-14T08:46:39+09:00
Vue.js + AWS S3でのHTTPS対応とCircle CIでの自動デプロイ手順
Vue.js で作ったSPAを静的サイトとしてAWS S3(+ CloudFront)からホスティングすれば、ランニングコストを低く抑えられるのでオススメです。アクセス数にもよりますが、個人のウェブサイトなどであれば100円程度/月で済んだりします。
今回はそんな Vue.js の AWS S3 へのデプロイ手順と、CloudFront を使った独自ドメイン + HTTPS 対応方法、さらに CircleCI を使った CI/CD の導入までザッと整理します。
最終的な本番環境イメージ
最終的な本番環境の構成イメージは次のとおりです。
Vue.js アプリの準備 (前提)
前提として、ここでは Vue.js アプリのデプロイ手順を扱います。
Vue.js の基礎知識については、過去の記事をご覧ください。
1. AWS S3 へのデプロイ手順
まずは単純な AWS S3 へのデプロイ手順です。
S3の準備
本番サーバとなるS3のセットアップです。
バケット新規作成
S3 のコンソールからバケットを新規作成します。
「バケット」はドメインと同じようなネーミングにしておくと分かりやすいです。「ブロックパブリックアクセス」は静的サイトとして公開するので全てオフにします。
静的サイトとして使う
バケット作成後、プロパティタブから「静的ウェブサイトホスティング」を選択して、静的サイトとして使えるようにします。
設定項目は次のとおりです。
- インデックスドキュメント(必須):
index.html
- エラードキュメント(必須):
error.html
バケットポリシーの追加
アクセス権限タブから「バケットポリシー」を選択し、次のようにJSONコードでポリシーを追加します。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadGetObject", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*" } ] }S3へのデプロイ
これで最低限の本番環境は整ったので、開発コンソールで本番用コードをビルドします。
npm run build生成される
dist
ディレクトリを先ほど作った S3 にアップします。そしてブラウザから次のURLを叩けば、 Vue.js の静的サイトが表示されます。
<bucket-name>.s3-website-<AWS-region>.amazonaws.com/dist例えば、バケット名が「mybucket」、リージョンが「東京(ap-northeast-1)」の場合は、S3のURLは次のようになります。
mybucket.s3-website-ap-northeast-1.amazonaws.com/distこれでデプロイ自体は完了です。
2. CloudFront での独自ドメイン + HTTPS化手順
次にデプロイしたS3の静的サイトを、独自ドメイン + HTTPS でアクセスできるようにします。
これは Route53 + AWS Certificate Manager + CloudFront で実現できます。
Route53 独自ドメイン登録
ケース1: Route53 で新規取得
コンソールの登録済みドメインページの「ドメインの登録」から独自ドメインを購入します。
ケース2: Route53 のサブドメインを使う
Route53 に登録済のドメインのサブドメインを使う場合は、必要な追加設定はありません。
ケース3: 他社ドメインを Route 53 に登録
他社で取得したドメインを使う場合、「Route53」と「他社ネームサーバ」のそれぞれで設定が必要です。
Route 53 での設定
コンソールのホストゾーンページの「ホストゾーンの作成」から他社取得ドメインを登録します。
タイプ「NS」と「SOA」がデフォルトで自動生成されます。「NS」は他社ネームサーバに入力する情報です(次のような4つの値)。
ns-2xx.awsdns-26.com. ns-1xxx.awsdns-36.org. ns-1xxx.awsdns-33.co.uk. ns-8xx.awsdns-40.net.他社ネームサーバでの設定
Route53 で自動生成された NS レコードの値を、他社ネームサーバ1〜4として登録します。
AWS Certificate Manager で証明書登録
AWS Certificate Manager のコンソールにて、Route53 に登録したドメインの証明書を取得します。
(注意)CloudFront で HTTPS 化をする場合、必ず「US EAST」で登録しないといけません。東京リージョンを選ばないように注意しましょう。
*.独自ドメイン
と設定すれば、サブドメイン分もまとめて登録できます。証明書の発行が完了すると、DNS 設定が書かれたCSVファイルをダウンロードできます。
Route53 に CNAME 追加
ダウンロードした DNS 設定の内容にしたがって、Route53 に CNAME を追加します。
ドメインのホストゾーン画面から「レコード設定の作成」を選択し、次のように設定します。
- 名前: DNS 設定の
Record Name
(例_3560a31fe01xxxxxxxxxx
)- タイプ:
CNAME
- 値: DNS 設定の
Record Value
(例_xxx.acm-validations.aws.
)(僕の環境下では、追加した CNAME はすぐに反映されました。)
CloudFront の設定
ここまで来て、やっと CloudFront の設定ができます。
CloudFront のコンソールからディストリビューションを追加します。「Web」を選択して、次のように設定していきます。
Origin Settings
- Origin Domain Name: 本番 S3 バケットを選択
- 以外、デフォルト
Default Cache Behavior Settings
- Viewer Protocol Policy: Redirect HTTP to HTTPS
- 以外、デフォルト
Distribution Settings
- Alternate Domain Names (CNAMEs): 本番で利用するドメイン
- SSL Certificat: Custom SSL Certificate(ACM で作った証明書を選択)
- Default Root Object:
dist/index.html
- 以外、デフォルト
以上の内容でディストリビューションを作成します。
作成後、CloudFront ディストリビューションの「Domain Name」をコピーします。
Route53 に CloudFront のエンドポイントを追加
Route53 ホストゾーンの「レコードセットの追加」から、先ほどコピーした「Domain Name」をAレコードとして追加します。
- 名前: 独自ドメイン(もしくはサブドメイン)
- タイプ: A
- 値: CloudFront ディストリビューションの「Domain Name」
ブラウザから独自ドメインを叩いて、S3の静的サイトがHTTPSで表示できればOKです。
403, 404エラーに対応
Failed to load resource: the server responded with a status of 403
vue-router
などで URL が動的に変わる場合、何も設定をしていないとブラウザのコンソール上でこういったエラーが出てしまいます。例えば、コンタクトページ
/contact
を Vue.js SPA 内で作った場合、S3上にdist/contact.html
という実際のファイルがないので、このようなエラーが出ると考えられます。解決方法
CloudFront でエラーページの設定すれば、このエラーを解決できます。
CloudFronnt ディストリビューションの「Error Pages」タブからカスタムエラーレスポンスを作成します。
- HTTP Error Code: 403, 404(2回に分けて作成)
- Customize Error Response: Yes
- Response Page Path:
/
- HTTP Response Code: 200 OK
3. CircleCI での CI/CD 導入手順
コードを更新するたびに手作業でテストして、ビルドして、デプロイするのは大変なので、CircleCI で一連の作業を全て自動化します。
事前準備: アカウント連携
アカウントを持っていない場合は、CircleCI のホームページからサインアップして、自身の GitHub アカウントと連携させます。
IAM ポリシーの追加
CircleCI に付与する S3 のファイル更新権限を準備します。
IAM のコンソール から「ポリシーの作成」を選択し、次の JSON でポリシー内容を規定します。(
<bucketname>
は各自のバケット名){ "Version": "2012-10-17", "Statement":[ { "Effect":"Allow", "Action":[ "s3:ListBucket", "s3:GetBucketLocation" ], "Resource":"arn:aws:s3:::<bucketname>" }, { "Effect":"Allow", "Action":[ "s3:PutObject", "s3:PutObjectAcl", "s3:DeleteObject" ], "Resource":"arn:aws:s3:::<bucketname>/*" } ] }ポリシー作成後に表示される「アクセスキーID」と「シークレットアクセスキー」をコピーしておきます。
CircleCI の権限設定
CircleCI プロジェクトの設定画面「AWS Permissions」で、先ほど生成した「アクセスキーID」と「シークレットアクセスキー」を登録します。
CircleCI の CI/CD 設定
プロジェクトのルートパスに
.circleci/config.yml
を作成し、CircleCI の CI/CD 設定を書きます。AWS S3 に自動デプロイする設定は次のとおりです。
(注意)YAMLファイルではインデントが重要です
.circleci/config.ymlversion: 2.1 orbs: aws-s3: circleci/aws-s3@1.0.11 executors: default: docker: - image: circleci/node:10.17.0 - image: circleci/python:2.7 commands: npm_install: steps: - restore_cache: key: dependency-cache-{{ checksum "package.json" }} - run: npm install - save_cache: key: dependency-cache-{{ checksum "package.json" }} paths: - node_modules jobs: build: executor: default working_directory: ~/repo steps: - checkout - npm_install - run: npm run test deploy: executor: default working_directory: ~/repo steps: - checkout - npm_install - run: name: build command: API_ID=$API_ID API_TOKEN=$API_TOKEN npm run build - aws-s3/sync: from: dist to: s3://mybucket/dist overwrite: true workflows: version: 2 continuous-deploy: jobs: - build - deploy: requires: - build filters: branches: only: master全般
orbs
は、デプロイに必要なコマンドなどの設定がまとめられたパッケージで、ここではcircleci/aws-s3@1.0.11
を使っています。コマンドの実行環境には
docker
のcircleci/node:10.17.0
とcircleci/python:2.7
を使っています。
jobs
でbuild
プロセス(CI)とdeploy
プロセス(CD)を定義し、workflows
で処理の順番を指定しています。
commands
では、各プロセスで実行するコマンドの実行内容を定義しています。CI (継続的インデグレーション)
build
プロセスの部分です。npm_install
してnpm run test
するだけの単純な設定です。これで GitHub に push されたコードに対して自動テストが実行されます。(マージ前のブランチ画面でテストが成功したか確認できます。)
CD (継続的デリバリー)
deploy
プロセスの部分です。まず、
npm_install
し、npm run build
で本番用コードdist
を生成します。そのビルドしたコードを
aws-s3/sync
で S3 にアップロードします。
workflows
でfilters: branches: only: master
を指定しているので、master ブランチへのマージのみをトリガーとして、このdeploy
プロセスが走ります。環境変数の設定
.circleci/config.yml
で使う環境変数は、CircleCI の管理画面(プロジェクトページ > Environment Variables)で定義します。.env ファイルの取り扱い
Vue.js アプリで使う環境変数は、
.env
ファイル内にVUE_APP_
の接頭辞で定義します(公式ドキュメントはこちら)。この
.env
ファイルには API キーなどの機密情報が含まれるので、通常は.gitignore
で git の管理対象から外します。一方、CircleCI では GitHub から本番用コードをビルドするので、
.env
がないと問題が発生してしまいます。これを回避するために、
npm run build
実行時に CircleCI の管理画面で定義した環境変数を渡してあげます。例えば、
API_ID
とAPI_TOKEN
という環境変数を定義した場合は、次のようになります。command: API_ID=$API_ID API_TOKEN=$API_TOKEN npm run buildなお、渡された環境変数を Vue.js 内で使うには、
webpack.config.js
にwebpack.DefinePlugin
を追加しないといけません。(これについての詳細は、今後別記事にまとめます。)※ もっと効率的な環境変数の渡し方があれば教えていただきたいです!
デプロイ動作確認
コードを変更して GitHub にプッシュし、master ブランチにマージさせます。
そのマージをトリガーに処理が走れば、CircleCI の管理画面にステータスが表示されます。
deploy
プロセスまで完了したら、変更が本番環境に反映されます。本番ファイル更新がすぐに反映されない?
S3 の本番ファイルを更新しても、CloudFront のエッジサーバ上にキャッシュが残っていると、その更新は即座に反映されません。
CloudFront の Invalidation を実行すれば、このキャッシュをリセットできます。より詳しい内容は以下の記事でまとめています。
References
- 投稿日:2019-12-14T03:21:53+09:00
実践 Composition API
はじめに
Vue Advent Calendar 2019 9日目の記事です。
担当は@yktm31です。Composition APIとは、Vue3から導入予定の新しいAPIです。
海外Vueカンファレンスでは、今や必ずトピックに上がるほど注目されています。本記事では、そんなComposition APIでモリモリ開発する際の実装方針やTipsについて書いていきます。
扱う内容は、以下の6つです。
1. ディレクトリ構成 / 設計方針
2. Router
3. 状態管理
4. ComposableなRepositoryFactory
5. ComposableなPolling
6. テスト今回、上記の要素を含んだ簡易なサンプルを作成しましたので、宜しければ参考にしてください。
GithubMain Contents
1. ディレクトリ構成 / 設計方針
まずは、ディレクトリ構成について。
基本はContainer/Presentationalをベースにするとスッキリまとめられそうです。
下記がディレクトリ構成例になります。configファイル等は省略しています。src ├-- views ① ページに相当する ├-- presentationals ② 見た目/デザインに責務を持つ ボタンなど ├-- containers ③ ユーザのアクションに対して、ロジックを実行しレスポンスする責務を持つ ├-- repositories ④ REST APIなど、外部リソースへのアクセスに責務を持つ ├-- compositions ⑤ Composition Function ├-- store ├-- router ├-- App.vue ├-- main.ts ├-- shims-tsx.d.ts └-- shims-vue.d.ts①〜③ コンポーネント分割方針
Composition APIで書く = コードが構造的になる
ではありません。RFCでは、こう表現されています。More Flexibility Requires More Discipline
Composition APIの恩恵で、より柔軟にロジックを組めるようになります。
しかし、だからこそちゃんと設計しないと、ロジックが散乱しカオスな状況に陥りかねません。そこで、プロジェクトで通底する構造を作っていく必要があります。
本記事では、Container/Presentational/view の3つを基本としたコンポーネント分割をしていきます。
それぞれの責務は以下になります。
・ container: ロジックを実行し、状態を操作する。
・ presentational: containerを親に持ち、Propsで受け取ったデータに基づきHTMLを返す。
・ view: containerを組み合わせてページを作るベースになっているのは、Container/Presentationalという、redeux開発者のDan Abramov氏が提唱した考えです。
こちらの記事が元記事です。実はこの考え、2013年に出されたもので少し古いものです。
元記事の中でも、今ではHookがあるので、この考えである必要はないと言っています。
しかし、この考えに基づき、コンポーネントの責務を明確にすることは有用だと考え、採用しています。
全体を把握しやすく変更・拡張が容易な構造を作るため、
シンプルなルール・わかりやすい関係性を意識しています。④ Repository
こちらは、RepositoryFactoryという考えに基づき、REST APIなど外部リソースへのアクセスを隠蔽します。
この考え方は、Vue evangelistのJorge氏が2018年にmediumで出した記事(日本語訳)で提唱されています。そこで、この考えをベースに、Composition APIライクなRepositoryFactoryを実装します。
詳細は、この先で触れます。⑤ Composition Function
コンポーネントから抽出したComposition Functionを置きます。
さてここで、「Composition Functionに切り出す出さないの判断基準は?」という疑問が湧いてくるかと思います。自分としては
⑴ コンポーネント固有のロジックはコンポーネントファイルに記述
⑵ 複数のコンポーネント共通で再利用するロジックはComposition Functionとして抽出
という基準で行なっています。ベースとしているのは
・SFCの考えを踏襲する
・ロジックの関心ごとに分ける
という二つの観点です。本記事では、Polling処理をComposition Functionとして切り出す例を紹介します。
詳細は後述します。2. Router
ここまで、考え方的な部分に触れてきました。
ここからは、実装よりの話になっていきます。まずは、Routerについてです。早速実装を見ます。
containers/LoginForm.vue<script lang="ts"> import { createComponent, SetupContext, ref, onMounted } from "@vue/composition-api"; import Button from "@/presentationals/Button.vue"; export default createComponent({ components: { Button }, setup(_, context: SetupContext) { // ページ遷移を行う。 function moveToNextPage() { context.root.$router.push({ name: "home" }); } return { moveToNextPage }; } }); </script>Vue2で、Vueインスタンス内部からrouterを呼ぶ際には、 this.$routerを使っていました。
this.$router.push({ name: 'user', params: { userId: 123 }})Vue3では、thisは廃止され、代わりにsetup()の第二引数、SetupContextからアクセスできます。
3. 状態管理
Composition APIを使った状態管理については、これまでいくつか詳しい記事が出ています。
・Vue Composition API を使ったストアパターンと TypeScript の組み合わせはどのくらいスケールするか?
・【Composition API】StoreパターンでVuexを使わずに状態管理をする
・Vue Composition APIのコラムっぽいもの集#Vuexはいらなくなる?加えて、2020年2月に行われる、vuejs.amsterdamで、
vueコアチームの方が、「Vuexいらないかも?」というテーマで登壇されるようです。"You might not need VueX", according to #vue Core Team Member @N_Tepluhina?.
— vuejs.amsterdam (@vuejsamsterdam) December 13, 2019
Natalia is a Senior Frontend Engineer @gitlab and a?️@GoogleDevExpert.
See you in Feb 2020 @N_Tepluhina ? pic.twitter.com/5jpUOHqxDo個人的には、いまいまグローバルに状態管理したいときはVuexを使っています。
理由としては、Composition APIを使った実装だと、DIしたりと実装が重くなると考えているからです。Vue3/Composition APIでの状態管理、
Vuexが追従するのか、新しいライブラリが出てくるのか、要チェックなところです。4. ComposableなRepositoryFactory
上で書いた通り、RepositoryFactoryという考えに基づき、REST APIなど外部リソースへのアクセスを隠蔽します。
このRepositoryFactoryを、Composition APIを利用して、実装する例を紹介します。Composition APIを使って実装するメリットとしては、コンポーネントのなかで、asyncを使ったRepository呼び出しが必要がない点です。
レスポンスやローディング中かどうかなど、状態も含めてComposition Functionの中に押し込められます。これにより、よりシンプルにAPI呼び出しがかけるようになります。
以下、実装例を見ていきます。バックエンドとして、flaskで簡単なAPIサーバを立てています。
app.pyimport logging import time from datetime import datetime from flask import Flask, jsonify, request from flask_cors import CORS app = Flask(__name__) CORS(app) @app.route('/api/sample/get', methods=['GET']) def get(): now = datetime.now().isoformat() return jsonify({'time': now}) @app.route('/api/sample/get2', methods=['GET']) def get2(): msg = request.args.get('msg') return jsonify({'msg': msg}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)まずはaxiosをラップしてRepositoryを作ります。
Repository.tsimport axios, { AxiosInstance } from "axios"; const baseDomain = "http://localhost:5000"; const baseURL = `${baseDomain}/api`; let Repository: AxiosInstance = axios.create({ baseURL: baseURL }); export default Repository;次に、ファクトリを作ります。
RepositoryFactory.tsimport useSampleRepository from "./SampleRepository" import { Ref } from '@vue/composition-api'; interface Repositories { } const repositories = { sample: useSampleRepository } as Repositories; export const RepositoryFactory = { create: (name: string): any => { return repositories[name]; } };こちらが、Composition APIを使ったRepositoryになります。
SampleRepository.tsimport Repository from "./Repository"; import { ref } from "@vue/composition-api"; const resource = "/sample"; export default function useSampleRepository() { let response1 = ref(); async function getSample() { const { data } = await Repository.get(`${resource}/get`); // レスポンスをrefで包んだ変数に格納。 response1.value = data; return data; } let response2 = ref(); async function getSample2(msg: string) { const { data } = await Repository.get(`${resource}/get2`, { params: { msg: msg } }); response2.value = data; return data; } return { getSample, getSample2, response1, response2 }; }コンポーネントでの使用例はこちらです。
<script lang="ts"> import { createComponent, SetupContext, ref } from "@vue/composition-api"; import Button from "@/presentationals/Button.vue"; import TextField from "@/presentationals/TextField.vue"; import { RepositoryFactory } from "@/repositories/RepositoryFactory"; export default createComponent({ components: { Button, TextField }, setup() { // Composition FunctionとしてのAPI呼び出しメソッド、およびそれらのレスポンスを受け取る変数を準備する。 const { getSample, getSample2, response1, response2 } = RepositoryFactory.create("sample"); // templete内で参照・呼び出しできるように、returnする。 return { getSample, getSample2, response1, response2, value }; } }); </script>5. Polling
Pollingについても、Composition APIを使うと、簡単に実装できます。
利点としては、コンポーネントでポーリング自体のロジックを組む必要がなくなることです。
また、ポーリングに関する状態もComposition Functionが持っているので、コンポーネントから制御することができます。例えば、ページを離れる際にポーリングを終了したい時は以下のようにできます。
<script lang="ts"> // ・・・略・・・ const { polling, pollingDisable } = usePolling(); // コンポーネントマウント前にポーリングを開始する。 onBeforeMount(() => { polling(someFunction); }); // コンポーネント切り替え時に、ポーリングを停止させる。 onBeforeUnmount(() => { pollingDisable.value = true; }); // ・・・略・・・ </script>実際の実装はこちらになります。
compositions/polling.tsimport { ref } from "@vue/composition-api"; // delayミリ秒待機する。任意の第二引数を結果として返す。 async function sleep(delay: number, result?: any) { return new Promise(resolve => { setTimeout(() => resolve(result), delay); }); } export default function usePolling() { // ポーリング制御用のフラグ let pollingDisable = ref<Boolean>(false); // ポーリングで実行する関数と、ポーリング間隔時間を引数として受け取る。 async function polling( fn: Function, intervalTimeMsec: number = 3000 ) { // 無限ループを回し、ポーリングする。 // pollingDisableの値がtrueになれば、ポーリングを終了する。 for (;;) { await sleep(intervalTimeMsec).then(status => { fn(); }); if (pollingDisable.value) { break; } } } return { polling, pollingDisable }; }6. テスト
テストについては、以下の2つの記事が参考になります。
・Vue Testing HandBoook
・CompositionAPIを使ってcompositionを分離した状態でテストする (@daikids)とはいえ、丸投げにするわけには行かないので、上で取り上げたRepositoryFactoryのユニットテストを書いていきます。
SampleRepositoryTest.spec.tsimport { createLocalVue, mount, shallowMount } from "@vue/test-utils"; import VueCompositionApi from "@vue/composition-api"; import useSampleRepository from "@/repositories/SampleRepository"; import Repository from "@/repositories/Repository" // composition APIを有効にする。 const localVue = createLocalVue(); localVue.use(VueCompositionApi); // Repositoryをモック化する。 const mockedRepository = Repository as jest.Mocked<typeof Repository> jest.mock('../../../src/repositories/Repository'); beforeEach(() => { mockedRepository.get.mockReset(); }) describe("SampleRepository", () => { it("getSampleTest", () => { mockedRepository.get.mockResolvedValue({data: "test"}); const { getSample, response1 } = useSampleRepository(); return getSample().then((res: string) => { expect(res).toEqual("test"); expect(response1.value).toEqual("test"); }); }); it("getSampleTest2", () => { mockedRepository.get.mockResolvedValueOnce({data: "no"}); const { getSample2, response2 } = useSampleRepository(); return getSample2("-").then((res: string) => { expect(res).toEqual("no"); expect(response2.value).toEqual("no"); }); }); });ポイントとしては、
・Repositoryをモック化する。
・レスポンスを受け取るrefで包んんだ変数の値をチェックする
の2点です。最後に
最後まで読んでいただきありがとうございます。
長くなりましたが、これまでComposition APIを使い、試行錯誤して考えてきたことを紹介させていただきました。尚、私の個人的な経験ベースでの話が多くなっています。
もし、もっといいやり方がある・それは良くない、という点があればコメントいただければ嬉しいです。また、開発規模の大小によって適切な構成があると思いますので、ひとつの参考になればと思います。
一応、本記事では、小〜中規模くらい(ページ数が3〜7程度)を想定しています。
Composition APIはまだ成熟しきっていないと思います。
しかし、Vueにとって新しいパラダイムとなるのは確実だと思っています。何より、Compoosition APIめちゃくちゃ楽しいのです!
どんどん普及して、色々なプラクティスが出てくるといいなと思い、本記事を執筆しました。それでは、楽しいVue Lifeを!
- 投稿日:2019-12-14T00:05:47+09:00
【Vue.js】トランジションクラスの発動タイミングまとめ【デモ付き】
はじめに
Vue.jsのトランジションクラスが発動するタイミングをまとめました。
この記事が役に立つ方
- Vue.js初心者
この記事のメリット
- トランジションクラスがいつ発動するのかがわかる。
環境
- OS: macOS Catalina 10.15.1 - Vue: 2.6.10transitionとは?
Vue.jsの機能で、CSSトランジション/アニメーションをより使いやすくサポートしてくれます。
基本構文
index.html...略 <div id="app"> <transition> トランジションさせたい要素 </transition> </div> ...略main.jsnew Vue ({ el: '#app' })シンプルに
<transition>
で囲むだけなので簡単です。トランジションクラス一覧
※画像の例は
opacity
をいじった場合です。Enter系
トランジションクラス タイミング .v-enter 開始 .v-enter-to 終了 .v-enter-active 変化している間 Leave系
トランジションクラス タイミング .v-leave 開始 .v-leave-to 終了 .v-leave-active 変化している間 ※画像は公式ドキュメントから拝借しました。
タイミング確認用デモ
See the Pen rNaMNgo by terufumi (@terufumi1122) on CodePen.
おわりに
最後まで読んで頂きありがとうございました
余談にはなりますが、初めてCODEPENを埋め込んでみました!
コピペだけで出来るとは思っておらず、かなりハードル低かったです参考にさせて頂いたサイト(いつもありがとうございます)