Trello+αでCSとのバグ対応を効率化する

f:id:yusuke_kuwa:20171212112715g:plain

株式会社SCOUTERの鍬(id:yusuke_kuwa) です。

今回は、Trelloを使用して、バグ対応の際のビジネスチームとのコミュニケーションを円滑にした方法を紹介します。

こちらはAtlassian(JIRA , Confuence, Trello, Bitbucket)のTips Advent Calendar 2017 - Qiita 12日目の記事となります。

背景

今回想定している対応フローは以下の通りです。

  1. バグ発生

  2. ユーザが弊社CSチームに報告

  3. CSチームがエンジニアに報告

  4. エンジニアが対応

この中で今回は、エンジニアとビジネスチームの間でコミュニケーションコストが発生していた「2. CSチームがエンジニアに報告」の部分を取り上げます。

課題感

業態や取り扱うサービス内容によって様々だとは思いますが、弊社では

  • 報告内容が断片的でバグの対象ユーザを特定できない。

    • 例:「ユーザーからログインできないと報告がありました」
  • Slackで情報が流れる・交錯する

    • 例:大型のアップデートを行った直後で、複数箇所にバグがあった時にSlackが炎上
  • バグ対応の進捗確認のコミュニケーションコストが勿体無い

    • 例:「昨日の◯◯のバグ、どうなっていますか?」

などに課題がありました。

今回はこれらを2つの仕組みで解消していきます。

解決策1:Trelloを使おう

GithubのIssueなど、エンジニアのツールを押し付けてはダメです。

ビジネスサイドが使い慣れているツールでないと使ってくれません。報告が上がらなくなるか、Slackに何でも流れてしまいます。

ということで、Trelloに「バグ対応ボード」を作成し、Trello + IFTTT の運用をはじめました。

Trelloのリストには、

  • 報告リスト

  • 再度ユーザに問い合わせ中

  • 開発修正中

  • Won't Fix

  • Done

を用意し、これにより

  • Slackで情報が流れる・交錯する

  • バグ対応の進捗確認のコミュニケーションコストが勿体無い

を解決できました。

解決策2:欲しい情報を必ず入力してもらおう

残る課題は

  • 報告内容が断片的でバグの対象ユーザを特定できない。

ですね。

Trelloのラベルにユーザ種別や対象サイトURLを用意していたのですが、カードが汚くなるのと、使いにくさもあり浸透しませんでした。

また、テンプレートカードも用意してみましたが、テンプレートをコピペしてからカードを記入するのが煩雑で、同じく浸透しませんでした。

そこで、PowerUpを探しまわって(笑)、欲しい情報を必ず入力できる仕組みを作りました。

最終的に採用したPowerUpはこちらです。

※PowerUpについてはこちらをご覧ください。Get Connected with Trello Power-Ups

このCustomFieldは、"Trelloカードに任意のフィールドを追加できる" というだけのPowerUpです。

それだけの機能といえばそうなのですが、シンプル故に使いやすく、結局社内ではこれが浸透しました。

設定が完了すると、カードの作成画面が以下のようになります。

CustomFieldの入力内容がリストに表示される点も見やすいですね!

f:id:yusuke_kuwa:20171212112715g:plain

現在弊社では、

  • 発覚日時

  • ユーザID

  • 利用ブラウザ

  • 当該URL

  • IntercomURL(ユーザとの会話履歴を辿りたい為)

を入力してもらっています。

おまけ:社内発表してみた時の様子

ということで、最後になりましたが、CustomFieldを導入した後に、社内slackに投稿した時の様子がこちらです。

f:id:yusuke_kuwa:20171211160413j:plain

まとめ

今回はAtlassianのアドベントカレンダーということで、Trelloを活用したバグ報告フローの効率化について紹介しました。

最終的なバグ報告〜対応のフローは以下の通りです。

  1. Trelloを書く(エンジニアが入力して欲しい項目が抜け漏れなく入力される)

  2. Slackに自動で通知される

  3. Trelloのリストでバグ対応の進捗が分かる

かなりスムーズになりましたね!

CSとエンジニアが協力しつつ、お互いの職務に集中できる仕組みづくり、非常に大切です。

Trello等の便利なツールを活用してどんどん行っていきましょう!

Vuexを使って絞り込み機能を実装してみた

こちらはVue.js #4 Advent Calendar 2017 - Qiita 12日目の記事です。

株式会社SCOUTERの小平(id:ryotakodaira) です。

今回はVue.js用の状態管理パターン+ライブラリの「Vuex」を使って、一般的なサービスでよく見かける絞り込み機能を例に実際の実装方法を紹介していきます。

Vuexを導入することでアプリケーションの状態を集中的に管理でき、状態の変更を特定の場所からのみ許可することで予期しない状態変更が起きにくいなどのメリットがあると思います。

また、Vueコンポーネントを細分化していくとコンポーネント間の状態のやり取りにsync修飾子などを使って状態の受け渡しを行うようになるのですが、徐々に状態の受け渡しのコードが複雑になってしまいます。通常このような場合、状態管理パターンを導入して状態遷移を簡単にすることができます。

今回はサンプルとしてQiitaのAPIを使用して簡単に記事のページングや絞り込みを行うことのできるページを作成しました。

完成例

f:id:ryotakodaira:20171211234935g:plain

 

検証実施環境

  • vue: ^2.5.2

  • vuex: ^3.0.0

  • QiitaAPI v2

初期設定

今回はvue-cliを使ってサクッとプロジェクトを立ち上げました。(vue-cliのインストールはこちらを参照)

vue init webpack qiita-time-line

追加で入れたパッケージは以下です。

{
    "axios": "^0.16.1",
    "element-ui": "^2.0.7",
    "lodash-es": "^4.17.4",
    "moment": "^2.19.4",
    "vuex": "^3.0.0"
}

package.json の変更が完了したら、 yarn install を実行しましょう。

コンポーネント

コンポーネントは以下の3つに分割しました。

  • QiitaTimeLine.vue

  • PageSearchOption.vue

    • ページを移動するためのフォームを実装
  • FilterSearchOption.vue

    • 記事をフィルタリングするためのフォームを実装

f:id:ryotakodaira:20171211234239p:plain

Store

itemという名前をモジュールを作成しています。 そこまでコード量が多くなかったため、1つのファイルに action, getter, mutaion を記述しています。 (mutationなどの細かい実装はこちらを御覧ください)

作成したactionは3種類です。

  • setQueryParams

    • APIに送信する検索条件を更新する
  • execGetItems

    • 検索条件と共にAPIにリクエストを送信する
  • setItems

    • 返ってきたデータをstateに格納する
import {cloneDeep} from 'lodash-es'

import types from '../mutation-types'
import initialState from '../initialState/itemList'
import qiitaApi from '../../api/qiitaApi'

const namespaced = true

const state = cloneDeep(initialState)

const getters = {
  state: state => state,
  queryParams: state => state.queryParams
}

const actions = {
  setQueryParams ({commit, state}, queryParams) {
    commit(types.SET_ITEM_LIST_SORT_DATA, {queryParams})
  },

  execGetItems ({commit, state}, queryParams) {
    commit(types.SEND_ITEM_LIST_REQUEST)

    return qiitaApi.getItems(queryParams)
  },

  setItems ({commit, state}, payload) {
    commit(types.RECEIVE_ITEM_LIST_RESPONSE, payload)
  }
}

const mutations = {
  [types.SET_ITEM_LIST_SORT_DATA] (state, {queryParams}) {
    Object.assign(state.queryParams, queryParams)
  },

  [types.SEND_ITEM_LIST_REQUEST] (state) {
    state.isLoading = true
  },

  [types.RECEIVE_ITEM_LIST_RESPONSE] (state, payload) {
    state.isLoading = false
    Object.assign(state, {items: payload})
  }
}

export default {
  namespaced,
  state,
  getters,
  actions,
  mutations
}

検索フォームの実装

まずはJS部分。 mapActions, mapGetters を使って、先程、storeで定義したactions, gettersを読み込みます。

次にフィルタリングを条件の変更を検知するためのwatcherを定義します。 ここでは、 computed で定義されている this.queryParams を監視の対象にしています。

この時に deep: true をつけるのを忘れないようにしましょう。これによりネストしたオブジェクトの中身の変更まで検知してくれるため、 this.queryParams の中身を一つずつwatchする必要がなくなります。 (watcherはこちらを参照)

後はAPIにリクエストを送信するためのメソッドと検索条件を更新するためのメソッドを用意します。

  • load ()

    • APIにリクエストを送信して記事の一覧を更新する
  • handleSelectFilterCondition ()

    • FilterSearchOptionコンポーネントに渡してフォームが変更されるたびに状態を更新する
  • handlePageChange ()

    • PageSearchOptionコンポーネントに渡してフォームが変更されるたびに状態を更新する
import {mapGetters, mapActions} from 'vuex'
import PageSearchOption from './PageSearchOption'
import FilterSearchOption from './FilterSearchOption'

export default {
  name: 'QiitaTimeLine',

  components: {
    PageSearchOption,
    FilterSearchOption
  },

  data () {
    return {}
  },

  mounted () {
    this.load()
  },

  computed: {
    ...mapGetters({
      state: 'item/state',
      queryParams: 'item/queryParams'
    }),
  },

  watch: {
    queryParams: {
      handler: function () {
        this.load()
      },
      deep: true
    }
  },

  methods: {
    ...mapActions({
      execGetItems: 'item/execGetItems',
      setQueryParams: 'item/setQueryParams',
      setItems: 'item/setItems'
    }),

    load () {
      this.execGetItems(this.queryParams).then(res => {
        this.setItems(res.data)
      }).catch(err => {
        console.log(err)
      })
    },

    handleSelectFilterCondition (val) {
      this.setQueryParams({query: val})
    },

    handlePageChange (num) {
      this.setQueryParams({page: num})
    }

  },
}

template部分も実装していきます。 PageSearchOption, FilterSearchOption コンポネントに検索条件を更新するための action(methodsで定義されているhandleSelectFilterCondition, handlePageChange)と現在選択している項目を選択済みにするために queryParams から該当する値を active-item-key として渡してあげます。

PageSearchOption, FilterSearchOption コンポーネントの詳細な実装はこちらを御覧ください!

<template>
  <div>
    <h2>Qiita Time Line</h2>
    <div class="filter">
      <PageSearchOption
        :active-item-key="this.queryParams.page"
        :action="handlePageChange"
      />
      <FilterSearchOption
        :active-item-key="this.queryParams.query"
        :action="handleSelectFilterCondition"
      />
    </div>
    <ul>
      <template v-if="this.state.isLoading === false">
        <li v-for="(item, key) in this.state.items">
          <a :href="item.url" target="_blank">
            <p class="trim green" style="font-weight: bold">{{item.title}}</p>
            <p>公開日時:{{item.created_at | formatJaTime }}</p>
            <p>いいね:{{item.likes_count}}</p>
            <p>タグ:<span class="tag" v-for="(tag, key) in item.tags">{{tag.name}}</span></p>
          </a>
        </li>
      </template>
      <li v-else>
        <p><i class="el-icon-loading"></i> Loading...</p>
      </li>
    </ul>
  </div>
</template>

まとめ

f:id:ryotakodaira:20171211234935g:plain

QiitaTimeLineコンポーネント では queryParamswatch しているため、検索条件が変更されるたびにloadメソッドが呼ばれて、記事の一覧を自動で更新することができます。 そのおかげで、毎回のように明示的にloadメソッドを呼び出して一覧を取得し直す必要がなくなりました!検索条件を新しく追加したいときも queryParams を更新するメソッドを作って適切に呼び出してやるだけでOKです。 この様に状態の変更をフックにすることで、いちいち検索ボタンをクリックしなくても簡単に検索条件を反映することができます。

尚、今回実装したサンプルの完全版はGitHubに公開していますので良かったら触ってみてください!

github.com

Vue.jsでドロップダウンを作り込む

f:id:yusuke_kuwa:20171208145625p:plainこちらはVue.js #4 Advent Calendar 2017 - Qiita 8日目の記事です。

株式会社SCOUTERの鍬(id:yusuke_kuwa) です。

今回は、Vue.jsらしい美しいコードだなと思ったドロップダウンのコンポーネント作成に焦点を当てて、作っていく工程を振り返ってみました。

検証実施環境

  • Vue.js ^2.5.0

  • Element ^2.0.0

  • lodash-es ^4.17.4

  • yarn ^1.0.0

コンポーネントの要件

今回は、以下の要件で使用されるコンポーネントとして作成しています。

  • テーブルや検索結果の件数指定・並び替えなどに使用できる

  • 今なにを選択しているのか分かる

  • 選択した直後にリクエストが送信される

完成イメージはgithubのDropDownです。

f:id:yusuke_kuwa:20171208102852j:plain
github.com-PullRequests

変数とpropsの設計

まずは、コンポーネントとして受け取る値を決めていきます。 上の要件を満たすように実装していきます。

props: {
        label: {
            type: String,
            required: true,
        },
        listItems: {
            type: Object,
            required: true,
        },
        activeItemKey: {
            type: [String, Number],
            required: false,
        },
        action: {
            type: Function,
            required: true,
        },
    },

data() {
        return {
            isActive: false,
        };
    },
  • labelgithubの図でいう "sort"の部分

  • listItemsは選択肢をkey:valueで受け取るObject

  • activeItemKeyは選択中のlistItemのkeyの値

  • actionはドロップダウンの中身のlistItemを選択した時の実行関数

となっています。

今回作成するドロップダウンはあくまでもパーツなので、実行関数は親コンポーネントに持たせます。

コンポーネント側のactionの役割は、

  • リクエストを送る

  • activeItemKeyの値を更新する

などを想定しています。

実装

コンポーネントから渡ってくる値が決まったので、ガシガシと作っていきます。

templateタグ周り

<template>
    <div>
        <div class="all-wrapper">
            <div class="dropdown-wrapper" @click="isActive = !isActive">
                <div class="dropdown-text">
                    {{label}}
                </div>
                <i class="el-icon-caret-bottom"></i>
            </div>
            <transition>
                <div class="list-items" v-if="isActive">
                    <template v-if="existsListItems">
                        <template v-for="(value, key) in listItems">
                            <div class="list-item"
                                 :class="[key == activeItemKey ? 'active' : '' ]"
                                 @click="handleClickItem(key)"
                            >
                                {{value.name}}
                            </div>
                        </template>
                    </template>
                    <template v-else>
                        <div class="list-item">
                            選択肢がありません
                        </div>
                    </template>
                </div>
            </transition>
        </div>
        <div class="dropdown-bg" @click="isActive = false" v-if="isActive"></div>
    </div>
</template>

propsから受け取る値の表示・制御はtemplateタグの中で済んでしまいます。

選択中の要素かどうかのactive判定も、propsで受け取ったactiveItemKeyを使ってテンプレート内で処理してしまいます。

最終的にscriptタグに残る記述は、

computed: {
    existsListItems() {
        return !isEmpty(this.listItems);
    },
},

methods: {
    handleClickItem(key) {
        if (key == this.activeItemKey) {
            return;
        }

        this.isActive = false;
        this.action(key);
    },
},

たったこれだけになりました。良いですね!

疑似背景

また、背景押下時にドロップダウンを閉じる挙動を

<div class="dropdown-bg" @click="isActive = false" v-if="isActive"></div>

が扱っており、CSSで疑似背景を全画面に展開しています。

.dropdown-bg {
    width: 100vw;
    height: 100vh;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 2;
}

スタイリング

ではいよいよ、list-itemにスタイルを当てていきます。

.all-wrapper {
    position: relative;

    .dropdown-wrapper {
        color: #666666;

        display: flex;
        align-items: center;

        &:hover {
            cursor: pointer;
        }

        .dropdown-text {
            font-size: 14px;
        }

        i {
            font-size: 10px;
            margin-left: 6px;
        }
    }

    .list-items {
        width: 260px;
        max-height: 300px;
        background-color: #fff;
        border-radius: 2px;
        border: 1px solid #B9BFC9;
        box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12), 0 0 6px 0 rgba(0, 0, 0, 0.04);
        position: absolute;
        right: 0;
        overflow-y: scroll;
        z-index: 3;
        padding: 0.5rem 0;

        .list-item {
            color: #333;
            font-size: 14px;
            line-height: 16px;
            padding: 0.75rem 1rem;

            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;

            position: relative;

            &:not(.active):hover {
                background-color: #F3F4F6;
                cursor: pointer;
            }

            &.active {
                color: #fff;
                background-color: #182A4B;
            }
        }
    }
}

背景の上にlits-itemが乗るので、こちらはz-index:3となります。

仕上げのtransition

最後にtransition に、ヌルっと表れるcssを当てて仕上がりです。

Vue.jsのtransactionコンポーネントを使用しているので、クラスの制御はVue.jsに任せて、CSSを書くだけで実装できます。

参考: Enter/Leave とトランジション一覧 — Vue.js

.v-enter-active, .v-leave-active {
    transition: all 0.3s
}

.v-enter {
    transform: translateY(-10px);
}

.v-enter, .v-leave-to {
    opacity: 0
}

完成品

f:id:yusuke_kuwa:20171208114531g:plain

まとめ

今回はVue.jsの習熟度的な観点で難しいことは特にしていませんが、props,template,cssの役割をうまく分散させる設計が肝でしたね。

jQuery等はもちろんですが、Reactでもここまで綺麗に書けないのではないかなと思います。笑

よきVue.jsライフを!  

PWAをNuxt.jsで簡単に体験する

こちらはVue.js #1 Advent Calendar 2017 - Qiita 7日目の記事です。

こんにちは、 id:kotamat です。 最近盛り上がっているPWAを試す際に、Nuxt.jsを使うと非常に簡単に実現できそうだったので、その方法を紹介しようと思います。

はじめに

PWAとは

Progressive Web Appの略で、Webの技術を使って、スマホアプリっぽい挙動を実現できる技術です。 厳密に言うと複数のWeb技術の集合体なのですが、導入としては下記がよく取り扱われます

  • オフライン対応
  • Web通知(ブラウザに通知)
  • ホーム画面への追加とスプラッシュ画像

上記は今までネイティブアプリやハイブリッドアプリ、側ネイティブ等iOSAndroidのプラットフォームに載せるアプリケーションとして開発し、展開していました。 これが、Web上でできることによって、オーガニック流入からシームレスにアプリケーションを起動、その後のリテンションまでWebの技術だけで実現できるようになります。

ネイティブアプリ、Webアプリそれぞれのいいとこ取りをしている技術ということで、いくつかの技術は前から実装されてきていましたが、モバイル系のブラウザが対応してきたり、Safariが実装段階に入ったりと、環境としても整い始め注目度が増しています。

Nuxt.jsとは

こちらの記事でも紹介させていただきましたが、Vue.jsで動くUniversalアプリケーションフレームワークです。 今回はNuxt.jsの特徴であるModuleを使い、PWAを触っていこうと思います。

導入する

Nuxt.jsはVue cliを使うとかんたんに導入できるので、今回はコチラを使おうかと思います。

$ yarn global install vue-cli
$ vue init nuxt-community/starter-template pwatest

とすると下記のようなファイルがpwatestに生成されます

pwatest/
├── README.md
├── assets
│   └── README.md
├── components
│   ├── Logo.vue
│   └── README.md
├── layouts
│   ├── README.md
│   └── default.vue
├── middleware
│   └── README.md
├── nuxt.config.js
├── package.json
├── pages
│   ├── README.md
│   └── index.vue
├── plugins
│   └── README.md
├── static
│   ├── README.md
│   └── favicon.ico
└── store
    └── README.md

次に、PWAモジュールをインストールします。

yarn add '@nuxtjs/pwa'

そして、nuxt.config.jsを下記のように設定します。

...
  build: {
    /*
    ** Run ESLint on save
    */
    extend (config, ctx) {
      if (ctx.dev && ctx.isClient) {
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/
        })
      }
    }
-  }
+  },
+  modules: [
+    '@nuxtjs/pwa'
+  ],
+  workbox: {
+    dev: true, //開発環境でもPWAできるように
+  }
}

あとはビルドして、サーバー立ち上げれば終わりです

nuxt build
nuxt start

f:id:kotamat:20171207015007j:plain

デベロッパーツールを見ると、ServiceWorkerが動いていることが確認できます。

このままオフラインにしても、正常に表示されます。

f:id:kotamat:20171207015021p:plain

なぜなにも設定せずにこのようにキャッシュがされるかというと PWAモジュールはデフォルトでNuxt.jsに最適化された下記のようなキャッシュ戦略を持っているためです。

  • nuxt buildで生成されるjsファイル: precache
  • /_nuxt/*パスのファイル: cacheFirstでキャッシュ
  • /*パスのファイル(staticにある静的ファイル等): networkFirstでキャッシュ

もちろんこれ以外に設定することも可能で、たとえば、tumblr.comの画像をcacheFirstでキャッシュしたい場合は下記の用にnuxt.config.jsに設定します。

+  workbox: {
...
+    runtimeCaching: [
+      {
+        urlPattern: 'https://*.media.tumblr.com/*',
+        handler: 'cacheFirst',
+        method: 'GET',
+      },
+    ],

webプッシュの導入

PWAモジュールにはwebプッシュももちろん導入されています。 webプッシュサーバーを自前で用意してもいいですが、お手軽導入ということでOneSignalを使って導入してみましょう。

OneSignal上でアプリケーションを登録後、

yarn add '@nuxtjs/onesignal'

をし、nuxt.config.jsを下記のように設定します。

  },
  modules: [
+    '@nuxtjs/onesignal', // pwaの前に定義
    '@nuxtjs/pwa'
  ],
  
+  oneSignal: {
+    init: {
+      appId: '設定したappId',
+      allowLocalhostAsSecureOrigin: true,
+      welcomeNotification: {
+        disable: true
+      }
+    }

再度ビルド、サーバー立ち上げを行います。 OneSignalのコンソールからメッセージを送信すると下記のようにwebプッシュが送信されます

f:id:kotamat:20171207015150p:plain

manifestの設定

Android等のモバイル端末で、ホーム画面に追加すると、アプリっぽい挙動になるmanifestの実装も下記のようにnuxt.config.jsに設定するだけです。

+  manifest: {
+    name: 'PWATest',
+    short_name: 'PWA',
+    title: 'PWATest',
+    'og:title': 'PWATest',
+    description: 'PWATest',
+    'og:description': 'PWATest',
+    lang: 'ja',
+    theme_color: '#ffffff',
+    background_color: '#ffffff'
+  },

ホーム画面に表示するアイコン画像はデフォルトだと/static/logo.pngを読みに行くので、正方形の画像を用意して置いておきましょう。

そうすると、下記のように、ホーム画面に設置すると、アイコンが表示され、タップするとスプラッシュ画面とともにアプリケーションが表示されます

f:id:kotamat:20171207015654j:plain f:id:kotamat:20171207015701j:plain f:id:kotamat:20171207015710j:plain

まとめ

今回はPWAの基本となる3点の機能を紹介させていただきました。 他にも機能はあるので、興味のある方は GoogleWebFundamentals等を見るといいかと思います。

Laravel, Nuxt.jsで構成した時のつまづきポイント

こんにちは、SCOUTERの id:kotamat です。 先日Laravel、Nuxt.jsの構成について記述させていただきました。
今回はその構成をするにあたって、躓いたポイントを紹介させてもらいます。

参考記事

techblog.scouter.co.jp

techblog.scouter.co.jp

Nuxt.jsのバージョン

Nuxt.js 1.0.0-rc11を前提としています。
1.0.0に上がったタイミングでこの記事の内容は適応されない可能性があります。

nuxt generateとnuxtコマンドの挙動のちがい

nuxt generateというのは、Nuxt.jsで作成されたプロジェクトを静的ファイル(html, js, css)として書き出すNust.jsが提供しているコマンドです。
こちらを使用するとS3やfirebase hostingなどにアップロードするだけでアプリケーションをデプロイできるようになります。

ただ、こちらはNode.jsランタイムを利用しないという特徴のため、nuxtコマンドでは動いたけどnuxt generateコマンドでは失敗するという不具合が発生することがあります。

  • nuxtコマンドだと、ファイル名の小文字大文字は気にしないが、nuxt generateだとそこでエラーになる
  • nuxt generateはnuxt外に対してはbabelが通らない
    • 自家製パッケージ等でES6を前提としたコードを読み込もうとした場合、nuxtだと正常に動くがnuxt generateだとビルドに失敗する

上記のようなミスは、nuxt generateコマンドをCIのチェックに入れておくことで事前に検出できます。

1.0.0-rc3での変更点とドキュメント

Nuxt.jsでは1.0.0-rc3にて大きく変更がありました。特にSSRまわりの影響の大きそうなところを紹介します。

Nuxt.js での大きな特徴としてSSRのエコシステムがありますが、例えば外部のUIフレームワーク等でwindowを前提としたフレームワークを使っている場合、SSR時に実行するとエラーになってしまいます。

そのエラーを回避するために、Nuxt.jsでは環境変数によってSSR時なのかクライアント時なのか判定する機能が搭載されています。

その環境変数が、

process.env.SERVER_BUILDからprocess.env.serverへ、
process.env.CLIENT_BUILDからprocess.env.clientへ、

双方変更されています。
公式ドキュメントの方にはすでに取り入れられていますが、開発系のスライドやブログ等1.0.0-rc3以前を前提としたものでは上記環境変数を使われている可能性があるので注意が必要です。

困ったら公式のドキュメント(できれば英語が望ましい)を見るようにしましょう。

Nuxt.jsの依存している依存パッケージ

前回の記事でも紹介させていただきましたが、 1.0.0-rc11だとまだvueのバージョンが2.4のままです。
例えば最近2系がリリースされたElementというUIフレームワーク(参考記事)では、vue:2.5を前提としており、アップデートしないとこちらのようなWarningがでてきます。

このようにNuxt.jsは便利な分、利用しているパッケージに大きく依存しているところがあるので、他のパッケージを使用する際は依存関係に注意する必要があります。

SSRがまだ必須

こちらは1.0.0に上がったタイミングで解消されるかもしれませんが、SSRを前提とした構成となっているため、SSR時にのみ発生するような不具合だと、ブラウザのデベロッパーツールで補足できません。

SSRが必要でない場合は

  • nuxt generateを使用し、静的ファイルとして生成する
  • <no-ssr></no-ssr>SSRしたくないところを囲い、クライアントサイドのmountedのタイミングでコンポネントが生成されるようにする

等の対応が必要となります。

axios moduleでのcredential対応、もしくはCookieの対応

若干Nuxt.jsとは離れますが、認証データ等データの持ち方は注意が必要です。

SSR時ではlocalStorageにアクセスすることはできないため、SSR前提でのアクセスの際はCookieにデータを保持しておく必要があります。

Laravel passportを使った認証

Laravel passportを使って認証を行う場合は、middlewareにて認証情報をstoreにセットし、認証前提のページにてaxiosにaccess_tokenをセットします。

構成に関しては、長くなりそうなので、別記事にて紹介させていただきます。

Cookieを使用した認証

Cookieを使用した認証を行う場合は、Laravel側にcorsを設定しcredentialをtrueにします。

CORSの対応でCredentialを使用する場合はallowedOriginを'*'ではなく、特定のドメインを指定するようにしてください。

laravel-corsを使用している場合は下記の用に設定します。

return [
-    'supportsCredentials' => false,
-    'allowedOrigins' => ['*'],
+    'supportsCredentials' => true,
+    'allowedOrigins' => [
+        env('APP_URL'),
+    ],
    'allowedHeaders' => ['*'],
    'allowedMethods' => ['*'],
    'exposedHeaders' => [],
    'maxAge' => 0,
];

まとめ

今回はLaravel, Nuxt.jsでの構成時のつまづきポイントについて紹介させていただきました。
現状のバージョン依存の問題もありますが、利用する際は注意しましょう。

勉強会

Laravel, Vue.jsの勉強会を開催致します!
LT枠、登壇枠はまだ残っているので
興味ある方はご参加よろしくお願いいたします。

laravue.connpass.com

Vue.js製のUIフレームワーク「Element」の使い方

株式会社SCOUTERの小平(id:ryotakodaira) です。

弊社では、フロントエンドの開発にVue.jsを導入しており、現場では主にVue.js製のUIフレームワークElement」を使用して、ページの見ためを表現しています。

先日、「Element」が2系にメジャーアップデートされ、npm trendsで他のVue.js製のUIフレームワークと比較しても注目されていることが明らかです。

www.npmtrends.com

また、1系で微妙だなと感じていた部分がだいぶ改善されていて、今後の発展が楽しみです!今回は「Element」の使い方についてまとめてみました。

f:id:ryotakodaira:20171114014815p:plain
Element

 

検証実施環境

  • Vue.js ^2.5.0

  • Element ^2.0.0

  • yarn ^1.0.0

インストール

今回は yarn を使ってインストールします。

yarn add element-ui

コンポーネントの使用方法

Elementの読み込み

import Vue from 'vue'
import App from './App'
+ import ElementUI from 'element-ui'
+ import 'element-ui/lib/theme-chalk/index.css'
import router from './router'

+ Vue.use(ElementUI)

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App }
})

上記の方法で読み込みを行うと、Element全体をインポートすることになるため、一部のコンポーネントを利用できるようにしてなるべくプロジェクトを小さく抑えたい場合には babel-plugin-component を用います。

yarn add -D babel-plugin-component

続けて、babel.rcを編集します。

"plugins": [["component", [
    {
      "libraryName": "element-ui",
      "styleLibraryName": "theme-chalk"
    }
  ]]]

もし、Buttonのみを使用したい場合には、以下のようにインポートします。

Vue.use(Button)

また、Elementはi18nの設定を行うことで日本語対応することが出来ます。

import Vue from 'vue'
import App from './App'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
+ import locale from 'element-ui/lib/locale/lang/ja'
import router from './router'

- Vue.use(ElementUI)
+ Vue.use(ElementUI, { locale })

これでElementを使うための準備は完了です。

コンポーネントの呼び出し

実際のコンポーネントの呼び出しは非常に簡単で、こちらのドキュメントを参考に呼び出すことが出来ます。

Elementはドキュメントが充実しているうえに、様々なpropsが用意されているため、カスタマイズもしやすくなっています。

<el-input placeholder="Please input" v-model="input1"></el-input>

<el-time-select
  v-model="input2"
  :picker-options="{
    start: '08:30',
    step: '00:15',
    end: '18:30'
  }"
  placeholder="Select time">
</el-time-select>

<script>
export default {
  data() {
    return {
      input1: '',
      input2: ''
    }
  }
}
</script>

一部のコンポーネントだけを使用したい場合にも、以下のように使いたいコンポーネントを直接指定してあげることで実現できます。

<el-switch v-model="value1">
</el-switch>

<script>
import { ElSwitch } from 'element-ui'

export default {
  components: { ElSwitch },
  data() {
    return {
      value1: ''
    }
  }
}
</script>

まとめ

今回紹介した「Element」は学習コストも低く、公式のドキュメントも豊富なため、あまり工数の取れないプロジェクトにもサクッと導入することができるので、プロダクトの初期段階には向いているなという印象です。(実際に弊社でも立ち上げでElementを導入している場合が多いです)

個人的には特にフォーム関連のコンポーネントが充実していて、自作するのはちょっとなー。。。みたいなコンポーネントが多数用意されているのでかなり助かっています。笑

 

さいごに

弊社では、Laravel・Vue.jsをテーマに勉強会を開催しています!もし興味が御座いましたらお気軽に参加してみてください。

laravue.connpass.com

Vue.jsで作られたフレームワーク「Nuxt.js」でできること

こんにちは、SCOUTERの id:kotamat です。

前回の記事では、PHPフレームワークのLaravelとNuxt.jsの連携に関して紹介させていただきましたが、それぞれの詳細、出来ることは述べておりませんでした。 そこで今回は、Nuxt.jsではなにができるのかをまとめてみたいと思います。

Nuxt.jsとは

f:id:kotamat:20171110210034g:plain

nuxtjs.org

トップの画像を見ていただけると分かる通り、Vue.jsをベースとしたフレームワークで、 React.jsベースのサーバーサイドレンダリング(以下SSR)フレームワークであるNext.jsの影響を受けております。 SSRに於けるサーバーサイドとクライアントサイドのUIレンダリングを主軸とし、プロジェクトの基軸となりうる柔軟な設計となっています。

  • Vue.js
  • VueRouter
  • Vuex
  • VueServerRenderer
  • VueMeta

を統合的に使いやすく一つのパッケージとして提供したというのが簡単なイメージです。

依存関係に関して

Nuxt.js自体は執筆時点では最新バージョンがv1.0.0-rc11、依存関係としては下記の状態となっております。

パッケージ バージョン
node >=6.11
npm >=3.10.0
vue ~2.4.2
vue-router ^2.7.0
vuex ^2.4.0
vue-server-renderer ~2.4.2
vue-meta ^1.1.0

ただし、Githubでも述べられている通り、近いうちに1.0が出る予定のため、devブランチでは対応可能バージョンは上げられており、devブランチで言うと

パッケージ バージョン
node >=6.11
npm >=3.10.0
vue ^2.5.3
vue-router ^2.7.0
vuex ^3.0.1
vue-server-renderer ^2.5.3
vue-meta ^1.3.1

と、Vue.jsのバージョンないし他に依存するパッケージも上がっています。 各種パッケージのバージョンに依存するパッケージの使用や実装を行っている場合は注意が必要です。

Nuxt.jsでできること

Nuxt.jsの公式サイトには下記のように書かれています

  • Vue ファイルで記述できること
  • コードを自動的に分割すること
  • サーバーサイドレンダリング
  • 非同期データをハンドリングするパワフルなルーティング
  • 静的ファイルの配信
  • ES6/ES7 のトランスパイレーション
  • JS と CSS のバンドル及びミニファイ
  • Head 要素の管理
  • 開発モードにおけるホットリローディング
  • SASS, LESS, Stylus などのプリプロセッサのサポート
  • HTTP/2 push headers ready
  • モジュール構造で拡張できること

今まで上記をVue.jsのみで実装しようとすると、各プロジェクトでwebpack.config.js等を用い細かく設定が必要だったり、プラグインのバージョン管理・結合管理等、アプリケーションロジックとは関係の無いところで実装工数が発生していました。

Nuxt.jsにより提供される上記機能を使用することで、コーディングスタイルを統一した開発、よりアプリケーション実装に注力した開発ができるようになります。

Nuxt.jsの大きな強み

上記のできることによってどのような恩恵を受けられるのか紹介したいと思います。

SSRを手軽にVue.jsベースで実装できる

まずNuxt.jsそもそもの原点としてこれは重要な強みになります。 上記引用でいうと

  • サーバーサイドレンダリング
  • 非同期データをハンドリングするパワフルなルーティング
  • Head 要素の管理

あたりがそれに当たります。

インストール後 nuxt start というコマンドを実行するだけでSSRができるという手軽さだけではなく、 htmlでいう<head>タグの管理がJS Objectベースで記述できる為、SEOクローラーに優しい実装が出来ます。

また、非同期データに関しては、初回アクセス時はSSRによりサーバーサイドから取得するため、Vue.jsの標準的なライフサイクルよりも前にHeader等を評価する必要がありますが、それをNuxt.jsはasyncDataというページ単位での前処理を行うことでこの問題を解決しています。

また、Node.jsを使用せず、静的サイトとして出力したい場合でも、 nuxt generateというコマンドを用いることで、ルーティングが自動解決するページはもちろん、商品の詳細ページ等、動的なパラメータを渡すページでも設定によってhtmlファイルに変換してくれます。

アプリケーション実装に集中できる環境

Nuxt.js には、SSRだけでなく、コンポネント志向のフレームワークやVue.jsの開発を行っているといずれ直面するような問題に対して、予め機能を搭載しております。

上記引用でいうと

  • 非同期データをハンドリングするパワフルなルーティング
  • ES6/ES7 のトランスパイレーション
  • 開発モードにおけるホットリローディング
  • SASS, LESS, Stylus などのプリプロセッサのサポート

あたりがそれに当たります。 ファイル名によるダイナミックなルーティング、ページ単位での非同期処理を考慮したデータ保持、ES6/7の最適な設定、実装がすぐに反映されるホットリローディング、プリプロセッサまわりの標準設定がインストールするだけで提供されている為、babelやwebpackの複雑な設定をすること無く、アプリケーションロジックに集中することが出来ます。

ルーティングにかんしては、規則どおりに実装することで、ルーティングファイルを設けることなく、ルーティングできるようになります。 またミドルウェアの概念もあるため、例えば認証によるアクセス制限やマスターデータの事前取得等他ページにまたがる処理も簡単に実装できるようになります。

また、プラグイン・モジュールという概念があり、公式コミュニティーがOSSとして提供している便利機能だったり、グローバルコンポネントの定義、UIフレームワークとの連携を、.jsファイル単位で簡素に管理できるようになっています。

通信の最適化

開発環境だけでなく、ユーザ体験を向上させるための通信の最適化もNuxt.jsは提供しています。

上記引用でいうと

  • コードを自動的に分割すること
  • JS と CSS のバンドル及びミニファイ
  • HTTP/2 push headers ready

あたりがそれに当たります。

そもそも Nuxt.js 自体がこれだけの機能がありつつ 57kB min+gzip で実装されているため、ユーザへの負荷がかかりづらいフレームワークです。

さらに単一ファイルへの出力を行うことでユーザの初回アクセス時に過度なダウンロードをさせることを防げます。 これは上記のルーティング規則と密接に結びついているからこそできることでしょう。 加えて分割されたコンテンツそれぞれも極力小さくすることで、容量を削減します

HTTP/2 push header readyはまだ検証していませんが、etag, gzipに加え、HTTP/2で提供されているサーバープッシュのHeaderを自動的に使用することができるようになり、プロトコルレイヤーでの通信最適化ができます。

まとめ

これまでNuxt.jsにてできることを紹介してきました。 現状Nuxt.jsがSSR前提での実装となっているのですが、1.0がリリースされたタイミングでSSR無しでも動作させるようになる予定なので、 Vue.jsでSSRしたい方はもちろん、管理画面等SSRが必要でない箇所でも上記メリットの享受のためNuxt.jsを導入されることを検討されてもいいのではないでしょうか。

勉強会

Laravel, Vue.jsの勉強会を開催致します! 興味ある方はご参加よろしくお願いいたします。

laravue.connpass.com