20191214のvue.jsに関する記事は17件です。

Express + Vue.jsでhelloを出してみよう

おはようございますこんにちはこんばんは!

最近Front-endのフレームワークが色々出て流行ってるらしくて、
友達となんかやってみよー!になってはじめました。
最初、どのフレームワークを使おうかと、
'react / vue が人気らしいー何が違う?'とざっと検索してみたら

  • Vue
    • テンプレート形式でアプリの制作したいなら
    • 簡単で「一旦動作」ができるのが好きなら
    • 早くて軽量のアプリが作りたいなら
  • React
    • 大きい規模のアプリを作るなら
    • もっと大きい情報が欲しいなら

こんな差がありました。
簡単なプロジェクトだから、VueにしようーでVueを選びました。
(実は韓国で何もわからずVueの本を買ってきたので、、最初からvueにしようと決めたこともあり、、笑)

back-endはnode.jsのexpress フレームワークを使います(なんでもjsでやってみよう感)

やってみましょう^0^

  1. node.js install
    https://nodejs.org/
    installします。ltsの方が安定的らしくてltsの方インストールしました。

  2. コマンドラインでインストる確認
    $ node -v
    バージョンが出力されたらok

  3. package.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.js
const 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

console
Server is running at: 8000

表示されたら localhost:8000に接続すると

스크린샷 2019-12-14 오후 9.52.52.png

こういう画面が出て、下の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/ 韓国語です (汗)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ミニマムな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>

実際にブラウザに表示させてみると以下のようになります。
極限にシンプルですね。
sample1.png

実行結果

ではコンソール画面を開いてリロードをかけてみましょう。
結果は...
console.png

なんと!孫コンポーネントである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さんの投稿です!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js テーブルの対象行を抜き出して詳細表示・編集する方法

概要

Vue.jsを使って、テーブルの選択した行のデータを編集フォームに表示し、
フォームで編集したデータをテーブルの対象行に戻す(更新する)といった機能の実装をしたので、
その時のメモ。

環境

  • Vue.js 2.6.10

実装機能概要

編集ボタンを押すと、
table1.png

対象行のデータを抽出し、
table2.png

保存ボタンを押すと、
table3.png

抽出してきた行のデータを入力内容で更新する。
table4.png

詳細

実装したコードのサンプルは以下のコードになります。
ポイントとしては、

  • 編集開始時に、編集対象のインデックスを保持
  • 編集フォームのデータとテーブルデータのオブジェクトの参照を分離するため、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>

まとめ

そんなに複雑な実装でもないし、あまり使いどころはないかもしれないけど、
実際に作った機能なのでメモとして残しておこうと思う。

複数のデータを一括で登録・編集するといった機能を実装するときには使えるかもしれない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsで作ったマークダウンエディタアプリをElectronでデスクトップアプリにする

はじめに

前回、Auth0を使ってプライベートなマークダウンエディタを作る(クライアントサイド編)で、ブラウザで動作するマークダウンエディタのプロトタイプを作成しました。
本記事では、Electronを使ってデスクトップアプリ化しようと思います。

システム構成図

本記事では、前回記事で作成したクライアントをElectronを使ってデスクトップアプリにしていきます。
part.png

Vue.jsプロジェクトをElectron化する

Vue.jsのプロジェクトは2コマンド実行するだけでElectron化できます。

前準備:Vue.jsプロジェクトを作成する

Auth0を使ってプライベートなマークダウンエディタを作る(クライアントサイド編)の手順を一通り行い、http://localhost:3000/にアクセスすると以下のような画面が表示される状態にします。
image.png

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.jsonscriptsを見るといくつかエイリアスが追加されており、$ 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...

するとこんな画面が出てきます。
image.png

開発者ツールが表示されているので、×ボタンで閉じるとWebと同じ見た目になりました。
image.png

Auth0を使ったGoogleログインもでき、マークダウンエディタの機能も壊れることなくそのままデスクトップアプリとして動かすことができました。
2019-12-14_16h01_31.gif

まとめ

Vue.jsで作成したWebアプリは、vue-cliプラグインのelectron-builderの力を借りることで、2コマンドだけでデスクトップアプリにすることができました。(うち1つはvue-cliのインストールだったので、vue-cliがインストールされていれば、1コマンドのみですね)

デスクトップアプリとして動かした場合でも、Webで動いていた機能が壊れることなく動いたのは驚きました。(Electronの内部ではChromiumが使われているとのことなので、当たり前っちゃ当たり前ですが)

最近はPWAを使ってもデスクトップアプリっぽく見せることができるよう(Windows 10 1803の新機能「PWA」とは?PWAのUWPアプリ化を試してみる)ですので、そちらも試していきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsで作ったマークダウンエディタをElectronでデスクトップアプリにする

はじめに

前回、Auth0を使ってプライベートなマークダウンエディタを作る(クライアントサイド編)で、ブラウザで動作するマークダウンエディタのプロトタイプを作成しました。
本記事では、Electronを使ってデスクトップアプリ化しようと思います。

システム構成図

本記事では、前回記事で作成したクライアントをElectronを使ってデスクトップアプリにしていきます。
part.png

Vue.jsプロジェクトをElectron化する

Vue.jsのプロジェクトは2コマンド実行するだけでElectron化できます。

前準備:Vue.jsプロジェクトを作成する

Auth0を使ってプライベートなマークダウンエディタを作る(クライアントサイド編)の手順を一通り行い、http://localhost:3000/にアクセスすると以下のような画面が表示される状態にします。
image.png

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.jsonscriptsを見るといくつかエイリアスが追加されており、$ 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...

するとこんな画面が出てきます。
image.png

開発者ツールが表示されているので、×ボタンで閉じるとWebと同じ見た目になりました。
image.png

Auth0を使ったGoogleログインもでき、マークダウンエディタの機能も壊れることなくそのままデスクトップアプリとして動かすことができました。
2019-12-14_16h01_31.gif

まとめ

Vue.jsで作成したWebアプリは、vue-cliプラグインのelectron-builderの力を借りることで、2コマンドだけでデスクトップアプリにすることができました。(うち1つはvue-cliのインストールだったので、vue-cliがインストールされていれば、1コマンドのみですね)

デスクトップアプリとして動かした場合でも、Webで動いていた機能が壊れることなく動いたのは驚きました。(Electronの内部ではChromiumが使われているとのことなので、当たり前っちゃ当たり前ですが)

最近はPWAを使ってもデスクトップアプリっぽく見せることができるよう(Windows 10 1803の新機能「PWA」とは?PWAのUWPアプリ化を試してみる)ですので、そちらも試していきたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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>
javascript
new Vue({
  el: "#app",
  data: {
    message: "hello world!"
  },
  methods: {
    sayHi: function() {
      return this.message;
    }
  }
})

【出力結果】
hello world!



thisはこれからたくさん使いそうだ。。。



ではまた!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者が 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:実機動作確認のため
  • Dialogflow:自然言語処理
  • Vue.js(VueCLI):アプリの画面生成
  • Azure Functions(CLI):内部の処理(クイズ部分とか入出力とか)
  • Vuetify:Vueで使えるフレームワーク
  • IntaractiveCanvas:(重要)Googleアシスタントアプリで画面描写に必要なライブラリ

実際に作ってみる

作るに当たってポイント部分だけを解説していきます。
実際に作ってみたい人は「滑舌チェックスキルをGoogle Nest Hubで実装してみた」をまず確認すると良いと思います。

1.Dialogflow(言語処理をする)

Googleアシスタントによって入力された音声の処理を行います。
Intent(インテント)を作成することで、言葉に反応して何かを返します。

1-1.インテント作成

20191213_056_dialogflow.cloud.google.com.png
作成するIntentは4つ

Intent名 内容 
Default Welcome Intent アプリ起動の言葉
EndIntent アプリ終了の言葉
MainIntent アプリ起動中の言葉
StartIntent アプリ起動後に開始する言葉

1-2.実際にインテントを作る(例:StartIntent)

アプリ起動後にゲームを開始するための言葉を登録します。
20191213_061_dialogflow.cloud.google.com.png

  • 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-tools 

3-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-admin

3-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.js
const 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 install

index.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 start

http://localhost:8080/でアプリが起動します。

Dialogfrowの設定

FulfillmentのwebhookURLを指定する

20191214_062_dialogflow_cloud_google_com.png

テストする

IntegrationからGoogle Assistantを選択
Dialogflow.png

出てきたポップアップで「Auto-preview changes」にチェック入れて「TEST」
Dialogflow2.png

Developから名前(アプリ呼び出しの呼び名)
Develop.png

Deployで「category」を「Games&fun」にする。※InteractiveCanvasに必須
Deploy2.png

InteractiveCanvasをYesにする。
Deploy3.png

Testから実際に動作を確認する。
Androidスマホがある方はそちらからでも確認できます。
Test.png

今回はテストまでなのでデブロイはなしで、ここまでとなります。
ここまで実施すれば実機でテストバージョンとして動作確認もできます。

以上です。
もしかしたら手順漏れなどあるかもしれませんが大体こんな流れです。
興味ある方は是非是非試してください〜

僕も答えられるかわかりませんが、不明点がありましたらお願いします!

宣伝みたいな

【大阪】Google_Nest_Hub対応スマートディスプレイスキルを作ろう!【Vue_js】_-_connpass.png
https://atlabo.connpass.com/event/157824/
2020年の1/23(木)に僕がスマートディスプレイにハマったきっかけのハンズオンが大阪でも開催されます。
興味ある方はおすすめですのでぜひ!※僕もスタッフとして行きます

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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: rails

after

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にのせる感じ。なんかイマイチ納得いってないので、知見あれば教えてください:pray:

# 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だと難しい部分だと個人的に考えています。

移行に迷っている方の参考になれば幸いです!同様の知見やご意見などお気軽にコメントください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

aタグのhref属性に、コンポーネントの特定のプロパティを指定する

v-bind:属性名="プロパティ名"
  • taskコンポーネントが、無効な位置(invalid_location)というプロパティを持つとする。
task.vue
<a v-bind:href="task.invalid_location">無効な位置</a>

これでtask.invalid_locationをhref属性に指定したリンクが生成される。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vuetifyをlaravelに入れる

vuetifyとは

公式サイトでは、全てのユーザーにリッチなWeb開発体験をお届けする、「one of the most popular JavaScript frameworks in the world」であると謳っている。
定期的なupdateやサポートも行っている模様。

他のvue.jsフレームワークとの比較の表が下記。なかなか魅力的なライブラリであるように思えます。

image.png

まんまと公式の謳い文句にのせられた感じはしますが、それじゃ使ってみようじゃないかと思い、自身の勉強用に作成したwebアプリに導入してみました。(laravel+vue+docker)

導入方法

閑話休題、本題の導入方法ですが、基本公式ページのやり方に沿えば簡単にインストールできますが、備忘録も兼ねて下記に手順を記載します。
(*ちなみに私の環境はlaravel6.6.2です。)

1.npmでvuetifyをダウンロード

npm install vuetify

npm install sass sass-loader fibers deepmerge -D

2.src/resources/js/app.jsに下記を追加

src/resources/js/app.js
import 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>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityでVue.js風のリアクティブシステムを作ってみる

はじめに

こんにちは、QualiArts Advent Calendar 2019、14日目の記事になります。

直近はUnityの開発していますが、その前はずっとウェブのフロントをやっていたため、本記事はVue.jsの(個人的に)目玉機能であるリアクティブシステムをUnityで再現してみるという話について書きます。

※日本語がネイティブではなくて、文書が読みづい可能性がありますので、御了承してください :smiley:

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に入る物は算出プロパティであります

リアクティブプロパティ:アクティブデータの源泉であり、値の更新が監視できます
算出プロパティ:基本的に呼び出さない限り実行されない、かつ依存しているリアクティブプロパティが変わらない限り再実行もされない仕様であります

裏には遅延処理とキャッシュが自動的に行われるため、使う側は直感的なコードが書けて、無駄の再実行も自動的に管理してくれる素晴らしい機能です:zap:

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の再実行は避けれない事になります。例えば、
familyNamegivenNameの更新フラグを追加し、それぞれの更新チェックをする処理をすれば実現できますが、コード量はおそらく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#のベースクラスを作成してみました。

ざっくりの原理

  1. あるWatcher(A君)がいます。
  2. このA君があるWatcher(B君)に依存して、B君の値を取得します。
  3. この時、B君が依存する他のWatcher(C君)がいれば、A君もC君に依存するような関係性を持たせるようにします。
  4. 依存がなくなるまで再帰的に続きます。

この原理に基づいてプロトタイプを作成しました。

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日目の記事でした。引き続き今後のカレンダー投稿を宜しくお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[勉強用](随時更新)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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】【ESLint】スコープ付きCSSの利用されていないセレクターを検出するESLintプラグイン作った

この記事は、Vue Advent Calendar 2019 #1 の14日目の記事です。


こんにちは。社内ではLintおじさんという二つ名を襲名していて、@ota-meshiという冷やかし感満載なアカウントで割と真面目にやってるつもりの者です。

本記事では、社内でこそこそ作っていたVue.jsのスコープ付きCSS用のESLintの拡張ルールの一部を先日npmで公開したので、その紹介とどんな感じで作ったのかを書こうと思います。

まず、公開したものは以下です。

下記リンクからブラウザ上で試すことができます。
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.js
module.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を走査し、datacomputedのプロパティを探して、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へのプロパティのマッピング

無理でした。datacomputedのプロパティの、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でStarつけてくれると励みになりますm(_ _)m

あとがき

このESLint拡張ルールですが、台風で流れたVue Fesのランチ(弊社はランチスポンサーする予定でした)で少し紹介する予定でしたが、行き場を失ったのでこの場で紹介させていただきしました。
来年もVue Fesあるといいなー。

あと。そうそう。僕、今年、Vue.jsのメンバーになりました:tada: 関わってくださった皆様ありがとうござます!これからもお宜しくお願いします!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js + AWS S3でのHTTPS対応とCircle CIでの自動デプロイ手順

Vue.js で作ったSPAを静的サイトとしてAWS S3(+ CloudFront)からホスティングすれば、ランニングコストを低く抑えられるのでオススメです。アクセス数にもよりますが、個人のウェブサイトなどであれば100円程度/月で済んだりします。

今回はそんな Vue.js の AWS S3 へのデプロイ手順と、CloudFront を使った独自ドメイン + HTTPS 対応方法、さらに CircleCI を使った CI/CD の導入までザッと整理します。

最終的な本番環境イメージ

最終的な本番環境の構成イメージは次のとおりです。

vue_s3_cloudfront_circleci.jpg

Vue.js アプリの準備 (前提)

前提として、ここでは Vue.js アプリのデプロイ手順を扱います。

Vue.js の基礎知識については、過去の記事をご覧ください。

1. AWS S3 へのデプロイ手順

まずは単純な AWS S3 へのデプロイ手順です。

S3の準備

本番サーバとなるS3のセットアップです。

バケット新規作成

S3 のコンソールからバケットを新規作成します。

「バケット」はドメインと同じようなネーミングにしておくと分かりやすいです。「ブロックパブリックアクセス」は静的サイトとして公開するので全てオフにします。

静的サイトとして使う

バケット作成後、プロパティタブから「静的ウェブサイトホスティング」を選択して、静的サイトとして使えるようにします。

スクリーンショット 2019-11-23 12.27.14.png

設定項目は次のとおりです。

  • インデックスドキュメント(必須): 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」と「シークレットアクセスキー」をコピーしておきます。

スクリーンショット 2019-11-22 21.39.28.png

CircleCI の権限設定

CircleCI プロジェクトの設定画面「AWS Permissions」で、先ほど生成した「アクセスキーID」と「シークレットアクセスキー」を登録します。

スクリーンショット 2019-11-22 21.39.16.png

CircleCI の CI/CD 設定

プロジェクトのルートパスに .circleci/config.yml を作成し、CircleCI の CI/CD 設定を書きます。

AWS S3 に自動デプロイする設定は次のとおりです。

(注意)YAMLファイルではインデントが重要です

.circleci/config.yml
version: 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 を使っています。

コマンドの実行環境には dockercircleci/node:10.17.0circleci/python:2.7 を使っています。

jobsbuild プロセス(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 にアップロードします。

workflowsfilters: 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_IDAPI_TOKEN という環境変数を定義した場合は、次のようになります。

command: API_ID=$API_ID API_TOKEN=$API_TOKEN npm run build

なお、渡された環境変数を Vue.js 内で使うには、webpack.config.jswebpack.DefinePlugin を追加しないといけません。(これについての詳細は、今後別記事にまとめます。)

※ もっと効率的な環境変数の渡し方があれば教えていただきたいです!

デプロイ動作確認

コードを変更して GitHub にプッシュし、master ブランチにマージさせます。

そのマージをトリガーに処理が走れば、CircleCI の管理画面にステータスが表示されます。

deploy プロセスまで完了したら、変更が本番環境に反映されます。

本番ファイル更新がすぐに反映されない?

S3 の本番ファイルを更新しても、CloudFront のエッジサーバ上にキャッシュが残っていると、その更新は即座に反映されません。

CloudFront の Invalidation を実行すれば、このキャッシュをリセットできます。より詳しい内容は以下の記事でまとめています。

References

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

実践 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. テスト

今回、上記の要素を含んだ簡易なサンプルを作成しましたので、宜しければ参考にしてください。
Github

Main 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いらないかも?」というテーマで登壇されるようです。

個人的には、いまいまグローバルに状態管理したいときは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.py
import 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.ts
import axios, { AxiosInstance } from "axios";

const baseDomain = "http://localhost:5000";
const baseURL = `${baseDomain}/api`;

let Repository: AxiosInstance = axios.create({ baseURL: baseURL });

export default Repository;

次に、ファクトリを作ります。

RepositoryFactory.ts
import 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.ts
import 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.ts
import { 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.ts
import { 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を!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】トランジションクラスの発動タイミングまとめ【デモ付き】

はじめに

Vue.jsのトランジションクラスが発動するタイミングをまとめました。

公式サイトはこちら

この記事が役に立つ方

  • Vue.js初心者

この記事のメリット

  • トランジションクラスがいつ発動するのかがわかる。

環境

- OS: macOS Catalina 10.15.1
- Vue: 2.6.10

transitionとは?

Vue.jsの機能で、CSSトランジション/アニメーションをより使いやすくサポートしてくれます。

基本構文

index.html
...略
<div id="app">
  <transition>
    トランジションさせたい要素
  </transition>
</div>
...略
main.js
new Vue ({
  el: '#app'
})

シンプルに<transition>で囲むだけなので簡単です。

トランジションクラス一覧

※画像の例はopacityをいじった場合です。

Enter系

スクリーンショット 2019-12-13 22.35.49.png

トランジションクラス タイミング
.v-enter 開始
.v-enter-to 終了
.v-enter-active 変化している間

Leave系

スクリーンショット 2019-12-13 22.35.54.png

トランジションクラス タイミング
.v-leave 開始
.v-leave-to 終了
.v-leave-active 変化している間

※画像は公式ドキュメントから拝借しました。

タイミング確認用デモ

See the Pen rNaMNgo by terufumi (@terufumi1122) on CodePen.

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

余談にはなりますが、初めてCODEPENを埋め込んでみました!
コピペだけで出来るとは思っておらず、かなりハードル低かったです:grin:

参考にさせて頂いたサイト(いつもありがとうございます)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む