アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

Vue.js + vuexによるToDoアプリケーションの実装

前々回の記事でLaravel 5.4 + Vue.jsの開発環境を構築し、前回の記事でLaravel 5.4によるWeb APIを作成しました。

今回は、作成したWeb APIを使用したToDoリストアプリケーションを、Vue.jsを使って作成します。

PHP側の積み残し



はじめに、昨日のコントローラーを修正します。
もともと、ItemControllerのindex()メソッドは「Item::all()」メソッドで全てのアイテムを返していました。しかし、画面上に表示するのは未完了のアイテムだけです。そこで、index()メソッドを以下のように変更します。



<?php

    public function index()
    {
        return response(Item::query()->where('checked', false)->get());
    }


これで、未完了(checkd=false)のアイテムだけを取得できるようになりました。tests/Feature/ItemTest.php にも機能テストを追加しておきましょう。



<?php

    public function testIndexReturnsOnlyUncheckedItems()
    {
        $item = Item::query()->find(1);
        $item->checked = 1;
        $item->save();

        $response = $this->get('/api/items');

        $response->assertStatus(200);
        $this->assertCount(0, $response->json());
    }


次に、ルーティングを変更し、新しいビューテンプレートを作成します。
routes/web.phpは以下のように、'/'へのアクセスでindexというテンプレートを表示するようにします。



<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('index');
});


次に、resources/views/index.blade.phpに以下の内容でファイルを追加します。



<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>ToDo App with Laravel 5.4 + Vue.js</title>
    <link rel="stylesheet" href="css/app.css"/>
    <script type="text/javascript">
        window.Laravel = window.Laravel || {};
        window.Laravel.csrfToken = "{{csrf_token()}}";
    </script>
</head>
<body>
<div id="app"></div>
<script src="js/app.js"></script>
</body>
</html>


以上でバックエンド側の準備は完了です。

完成図



Vue.jsアプリケーションの完成図を示します。



シンプルなToDoリストで、アイテムの一覧・追加・完了・削除の機能を持っています。最終的なファイル構造は以下のようになります。



resources/assets/js
├── api
│   └── items.js
├── app.js
├── bootstrap.js
├── components
│   ├── App.vue
│   ├── Item.vue
│   ├── ItemList.vue
│   └── NewItem.vue
└── store
    ├── action-types.js
    ├── index.js
    └── mutation-types.js

3 directories, 10 files


コンポーネントの構成



このアプリケーションのコンポーネントの構成は以下のようになっています。



新しいアイテムを追加するための「NewItem」と、アイテムのリストである「ItemList」が並列になっています。
ItemListの中にはItemが入れ子になっています。
これら全体を包含するのが「App」コンポーネントです。

ここで問題になるのが、NewItemからItemListへのデータの受け渡しです。
新しいアイテムを追加したら、一覧の末尾に追加したいのですが、Vue.jsが提供する機能(propsによるデータの受け渡し)では、コンポーネントに親子関係がある場合にしかできません。
Appコンポーネントが全ての親になっているので、Appコンポーネントにデータを管理する役割をもたせれば何とかなりそうですが…。

と、このようにコンポーネント間通信が必要になった場合には、状態管理のためのライブラリであるvuexの導入を検討するとよいでしょう。

vuexは、以下のコマンドでインストールできます。



$ npm install --save-dev vuex


このアプリケーションでは、状態管理にはvuexを使用します。

アプリケーションのセットアップ



はじめに、不要なコンポーネントを削除します。
resources/assets/js/components から、Example.vue(と、Hello.vue)を削除しましょう。

次に、Vue.jsでvuexを使えるようにする設定を行います。
resources/assets/js/bootstrap.js で、「window.Vue」の定義の直後に以下のコードを追加します。



window.Vue.use(require('vuex'));


これでVue.jsがvuexを使えるようになります。次に、アプリケーションの初期化処理を記述したresources/assets/js/app.jsを見てみます。



/**
 * First we will load all of this project's JavaScript dependencies which
 * includes Vue and other libraries. It is a great starting point when
 * building robust, powerful web applications using Vue and Laravel.
 */

require('./bootstrap');

/**
 * Next, we will create a fresh Vue application instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */

const App = require('./components/App.vue');
const store = require('./store/').default;

const app = new Vue({
    el: '#app',
    store,
    render: h => h(App)
});


ここでは、以下の仕事を行っています。

1. Appコンポーネントの読み込み
2. Storeオブジェクトの読み込み
3. Vueアプリケーションの初期化
4. Appコンポーネントの描画

AppコンポーネントとStoreについては後述します。
Vueアプリケーションの初期化時に、Appコンポーネントの描画を実行させているのがポイントです。

Storeの実装



Fluxの仕組みを解説をすると長くなってしまうので、解説は省略します。
vuexのドキュメントがわかりやすいので、こちらを参照してください。

vuexでは、Vuex.Storeオブジェクトに、アプリケーションの横断的な状態(state)を閉じ込めます。
また、stateの変更(mutation)はミューテーターと呼ばれる関数を介して行う必要があります。
このように、情報の一元化 + アクセス方法の制限によって、アプリケーションがもつデータの管理を行いやすくなるのがvuexの利点です。

依存関係の少ないところから見ていくとわかりやすいので、まずはWeb APIとの通信を行う機能の実装から紹介します。

resources/assets/js/api/items.js



const axios = require('axios');
const API_URI = '/api/items';

export const ItemsAPI = {
    getAllUnchecked(callback) {
        axios.get(API_URI)
            .then((response) => {
                callback(response.data);
            })
            .catch((error) => {
                console.log(error);
            });
    },
    create(content, callback) {
        axios.post(API_URI, {content: content})
            .then((response) => {
                callback(response.data);
            })
            .catch((error) => {
                console.error(error);
            });
    },
    check(id, callback) {
        axios.patch(API_URI + '/' + id, {checked: true})
            .then((response) => {
                callback(response.data);
            })
            .catch((error) => {
                console.error(error);
            });
    },
    delete(id, callback) {
        axios.delete(API_URI + '/' + id)
            .then((response) => {
                callback(response.data);
            })
            .catch((error) => {
                console.error(error);
            });
    }
};


実行している処理がほとんど同じなので、冗長な感じですが、やってることは簡単で、Web APIへ問合せて、結果をコールバック関数に渡しているだけです。

次に、Storeオブジェクトの実装を示します。



const Vuex = require('vuex');
const {MUTATION} = require('./mutation-types');
const {ACTION} = require('./action-types');
const {ItemsAPI} = require('../api/items');

const state = {
    items: []
};

const getters = {
    uncheckedItems: (state) => state.items.filter(item => !item.checked)
};

const actions = {
    [ACTION.GET_ITEMS] ({commit}) {
        ItemsAPI.getAllUnchecked(items => {
            commit(MUTATION.SET_ITEMS, items);
        });
    },
    [ACTION.CREATE_ITEM] ({commit}, content) {
        ItemsAPI.create(content, (item) => {
            commit(MUTATION.ADD_ITEM, item);
        });
    },
    [ACTION.CHECK_ITEM] ({commit}, id) {
        ItemsAPI.check(id, () => {
            // チェックされたアイテムをリストから削除
            commit(MUTATION.REMOVE_ITEM_BY_ID, id);
        });
    },
    [ACTION.DELETE_ITEM] ({commit}, id) {
        ItemsAPI.delete(id, () => {
            commit(MUTATION.REMOVE_ITEM_BY_ID, id);
        });
    }
};

const mutations = {
    [MUTATION.SET_ITEMS] (state, items) {
        state.items = items;
    },
    [MUTATION.ADD_ITEM] (state, item) {
        state.items.push(item);
    },
    [MUTATION.REMOVE_ITEM_BY_ID] (state, id) {
        state.items = state.items.filter(item => item.id !== id);
    }
};

export default new Vuex.Store({
    state,
    getters,
    actions,
    mutations
});


stateの中に、このアプリケーションが管理するデータを格納します。
gettersはデータの取得、mutationsはデータの変更を行う関数です。

ミューテーション(mutations)は、必ず同期処理にする必要があります。ミューテションで、HTTPリクエスト等の非同期処理を実行してはいけません。

非同期処理は、アクション(actions)の役割です。アクションは、(1) 非同期処理を実行する (2) ミューテーションを実行する という仕事を行います。外部からデータを取得して、それをstateに格納する際は、アクションを使用します。

アクション/ミューテーションは直接呼び出すことはできません。
外部から呼び出す際は、アクションなら「store.dispatch('アクションの名前')」、ミューテーションなら「store.commit('ミューテーションの名前')」という方式で呼び出す必要があります。
アクション/ミューテーションの名前に「ACTION.GET_ITEMS」といった定数を使用しているのは、文字列による呼び出しが必要だからです。
たとえば、GET_ITEMSというアクションでは、以下のようにしてミューテーションを呼び出しています。



[ACTION.GET_ITEMS] ({commit}) {
    ItemsAPI.getAllUnchecked(items => {
        commit(MUTATION.SET_ITEMS, items);
    });
},


データをサーバに送信する際に実行される流れは以下のようになります。

1. コンポーネントでイベントが発火
2. コンポーネントがStoreのアクションを呼び出す(dispatch)
3. アクションがAPIへ問合せを行う
4. アクションがリクエストの結果に応じてミューテーションを呼び出す(commit)
5. ミューテーションがstateを書き換える
6. 画面上に変更が反映される

コンポーネントの実装



次に、Storeを利用するコンポーネントの側を見ていきます。
まずは、全てのコンポーネントのルートであるAppコンポーネント(resources/assets/js/components/App.vue)です。



<template>
    <div class="container">
        <new-item></new-item>
        <item-list></item-list>
    </div>
</template>

<script>
    const NewItem = require('./NewItem.vue');
    const ItemList = require('./ItemList.vue');

    export default {
        components: {NewItem, ItemList}
    }
</script>


ここでは、NewItemとItemListを読み込んで、これらをコンテナとなるdiv要素に加えています。

NewItemコンポーネント



NewItemコンポーネント(resources/assets/js/components/NewItem.vue)は以下のようになります。



<template>
    <div class="row new-item">
        <label>
            新しいタスク
            <input type="text" name="content" v-model="content" @keydown.enter="addItem"
                   @compositionstart="composing=true" @compositionend="composing=false">
        </label>
        <input type="submit" value="追加" class="btn btn-sm btn-primary"
               @click="addItem">
    </div>
</template>

<script>
    const store = require('../store/').default;
    const {CREATE_ITEM} = require('../store/action-types');

    export default {
        data() {
            return {
                content: '',
                composing: false // IMEによる入力中か否かのフラグ
            }
        },
        methods: {
            addItem(event) {
                if (!this.content) return;
                store.dispatch(CREATE_ITEM, this.content);
                this.content = '';
            }
        }
    }
</script>

<style scoped>
    .new-item {
        margin: 10px 0;
        width: 100%;
        display: flex;
    }

    label {
        justify-content: flex-start;
        flex-grow: 1;
    }

    input[name=content] {
        width: 80%;
    }

    button {
        justify-content: flex-end;
    }
</style>


単純な入力ボックスです。入力ボックス上でEnterキーを押すと送信されるようにしています。注意点は、IMEの状態を管理する必要があるということです。変換モード時のEnterキーで送信されると非常に不便です。ここではcomposition(start/end)というイベントを利用して、IMEの変換モードでは送信を行わないようにしています。

また、前述したように、新しいアイテムを追加したら、ItemListに新しい要素が追加されるようにする必要があります。
ここでは以下の流れで処理を実行しています。

1. NewItemがCREATE_ITEMアクションをdispatch
2. CREATE_ITEMアクションがADD_ITEMミューテーションをcommitしてstate.itemsを書き換え
3. state.itemsが書き換えられたのでItemListにも変更が伝播する

CREATE_ITEMアクションの実装は以下のようになっています。



[ACTION.CREATE_ITEM] ({commit}, content) {
    ItemsAPI.create(content, (item) => {
        commit(MUTATION.ADD_ITEM, item);
    });
},


CREATE_ITEMアクションが呼び出しているADD_ITEMミューテーションの実装は以下のようになっています。
ここでstate.itemsに新しい要素が追加されると、ItemListは新しいItemを描画します。



[MUTATION.ADD_ITEM] (state, item) {
    state.items.push(item);
},


ItemListコンポーネント



次はItemListです。



<template>
    <div class="row">
        <ul class="list-group">
            <item v-for="item in items" v-bind:item="item"></item>
        </ul>
    </div>
</template>

<script>
    const Item = require('./Item.vue');
    const store = require('../store/').default;
    const {GET_ITEMS} = require('../store/action-types');

    export default {
        components: {Item},
        computed: {
            items: () => store.getters.uncheckedItems
        },
        created() {
            store.dispatch(GET_ITEMS);
        }
    }
</script>


ItemListのポイントは、リストが作成されたタイミング(created())で、GET_ITEMSアクションをdispatchしている点です。
GET_ITEMSアクションは、APIに問合せを行って、アイテムの一覧を取得し、そのデータをstateに格納します。

ItemListは、itemsというcomputedプロパティでstore.getters.uncheckedItemsという関数を使用しています。
この関数は、以下のようにstate.itemsにフィルタリングを行って返します。



const getters = {
    uncheckedItems: (state) => state.items.filter(item => !item.checked)
};


また、ItemListのテンプレートでは、以下のようにitemsプロパティを使用してItemを描画しています。



<item v-for="item in items" v-bind:item="item"></item>


APIからデータが返ってきて、state.itemsが変更されると、以下の流れで情報がでんぱして画面が更新されます。

1. uncheckedItems()が返す値が変わる
2. ItemListのitemsプロパティが返す値が変わる
3. ビューに変更が反映される

Itemコンポーネント



最後がItemコンポーネントです。リストの要素をコンポーネントにするかは意見の分かれるところでしょうが、Itemコンポーネントは、(1) 完了済みのチェックをつける (2) アイテムを削除する という独自の機能を持つため、別コンポーネントとして切り出しています。



<template>
    <li class="list-group-item">
        <input type="checkbox" name="checked" @click="checkItem" v-model="item.checked">
        <span class="content">{{item.content}}</span>
        <button class="btn btn-sm remove-button" @click="deleteItem">
            <i class="glyphicon glyphicon-remove"></i>
        </button>
    </li>
</template>

<script>
    const store = require('../store/').default;
    const {CHECK_ITEM, DELETE_ITEM} = require('../store/action-types');

    export default {
        props: ['item'],
        methods: {
            checkItem() {
                store.dispatch(CHECK_ITEM, this.item.id);
            },
            deleteItem() {
                if (!confirm("削除しますか?")) return;
                store.dispatch(DELETE_ITEM, this.item.id);
            }
        }
    }
</script>

<style scoped>
    li {
        display: flex;
    }

    input[name=checked] {
        cursor: pointer;
        margin-right: 10px;
    }

    .content {
        flex-grow: 1;
    }

    .remove-button {
        align-items: flex-end;
        width: 34px;
        height: 30px;
    }
</style>


ここでもやっていることは単純で、CHECK_ITEMアクションないしDELETE_ITEMアクションをdispatchしているだけです。
このように、Storeに機能を寄せて作ると、肥大化しがちなコンポーネントの実装をシンプルに保つことができます。

まとめ



vuexを使うのは初めてでしたが、責務が分かれてきれいに書ける反面、コード量はどうしても多くなりますね。
今回のアプリケーションくらいなら、Appコンポーネントでデータを一元管理するような実装でも十分かもしれません。

本当はOAuth 2.0による認証機能の実装もやる予定だったのですが、予想以上に分量が膨らんでしまったので、認証機能の実装は次回にします。
コードの全体はGitHubでも公開しているので、参考にしてください。

参考



Vue.js
vuex

Laravel 5.4で Vue.js開発環境を手軽に作る

こんにちは。宇都宮です。
最近はアシアル社内でもLaravelを使うことが増えてきました。また、フロントエンドも、ReactやVue.jsを使ったプロジェクトをちらほら見かけるようになってきました。
今回は、Laravelのインストール方法と、フロントエンド開発環境のセットアップ、簡単なVueコンポーネントの作り方を解説します。

Laravelとフロントエンド



Laravelでは、5.3から、Vue.jsが標準のJavaScriptフレームワークになりました。
さらに、5.4では、フロントエンドのビルドツールが、gulpベースのElixirから、WebpackベースのMixに変わりました。
Laravelをインストールすれば、Web APIはLaravelで作って、SPA(Single Page Application)をVue.jsで組む、といったことが簡単にできるようになっています。
なお、LaravelとVue.jsが密結合しているわけではないので、Vue.jsではなくReactやAngularを使うこともできます。

インストール



本記事では、Laravel 5.4.15を使用します。
また、Laravel 5.4の動作には、PHP 5.6.4以上と、いくつかのPHP拡張モジュールが必要です。

インストールにはいくつかの方法がありますが、以下ではcomposerを使用します。



composer create-project --prefer-dist laravel/laravel sample


上記コマンドを使用すると、現在のディレクトリに「sample」という名前のディレクトリが追加され、その中にLaravelアプリケーションが作成されます。
作成直後のディレクトリ構造は以下のようになります。



$ tree -L 1
.
├── app
├── artisan
├── bootstrap
├── composer.json
├── composer.lock
├── config
├── database
├── package.json
├── phpunit.xml
├── public
├── readme.md
├── resources
├── routes
├── server.php
├── storage
├── tests
├── vendor
└── webpack.mix.js

10 directories, 8 files


まずは、Laravelアプリケーションが正常に動くか確認するため、開発用Webサーバを起動してみましょう。



$ php artisan serve
Laravel development server started: <http://127.0.0.1:8000>


次に、ブラウザで「 http://127.0.0.1:8000 」にアクセスしてみましょう。
以下のような画面が表示されれば正常に動作しています。



フロントエンドのセットアップ



Laravel 5.4をインストールすると、以下の内容のpackage.jsonが付いてきます。



{
  "private": true,
  "scripts": {
  // 省略
  },
  "devDependencies": {
    "axios": "^0.15.3",
    "bootstrap-sass": "^3.3.7",
    "jquery": "^3.1.1",
    "laravel-mix": "^0.8.1",
    "lodash": "^4.17.4",
    "vue": "^2.1.10"
  }
}


Laravelをインストールしたディレクトリで「npm install」を実行すれば、必要なライブラリが入ります。
npmのために、事前にNode.jsをインストールしておきましょう。

インストールが完了すると、以下の環境が作成されます。



さきほど省略したpackage.jsonには、npmで実行できるスクリプトが定義されています。たとえば、上記コンパイル処理を一括で実行する際は「npm run dev」を使用します。

注意点として、2017年3月15日現在、「npm run dev」はcross-env.jsのパスの問題で失敗する可能性があります。
GitHubIssueが挙がっているので、将来的には直っているはずです。
取り急ぎの修正方法としては、cross-env.jsのパスを修正して、以下のようにしましょう。



  "scripts": {
    "dev": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "watch": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "watch-poll": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --watch-poll --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "hot": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
    "production": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
  },


scriptを修正して「npm run dev」を実行するとコンパイルが成功するはずです。

これで、public/js/app.jsにコンパイル済みのJavaScript、public/css/app.cssコンパイル済みのCSSが出力されるようになります。
また、「npm run watch」を走らせておくと、JavaScriptCSSを追加・変更した際に自動的にコンパイルが行われるようになります。

まずはVue.jsが使えるようになっているか確認しましょう。
resources/views/welcome.blade.php を以下の内容で置き換えます。



<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Hello Vue</title>
    <link rel="stylesheet" href="css/app.css"/>
    <script type="text/javascript">
        window.Laravel = window.Laravel || {};
        window.Laravel.csrfToken = "{{csrf_token()}}";
    </script>
</head>
<body>
<div id="app">
    <example></example>
</div>
<script src="js/app.js"></script>
</body>
</html>


以下のように表示されればOKです。



ここでは、Laravelに付属するExampleという名前のVueコンポーネントを使用しています。
また、resources/assets/js/bootstrap.jsで参照している「window.Laravel.csrfToken」という変数の定義も行っています。この変数の使いみちについては後で説明します。

JavaScriptアプリケーションのブートストラップ



resources/assets/js/bootstrap.js に、JavaScriptアプリケーションを動作させるのに必要な初期化処理を記述します。




window._ = require('lodash');

/**
 * We'll load jQuery and the Bootstrap jQuery plugin which provides support
 * for JavaScript based Bootstrap features such as modals and tabs. This
 * code may be modified to fit the specific needs of your application.
 */

window.$ = window.jQuery = require('jquery');

require('bootstrap-sass');

/**
 * Vue is a modern JavaScript library for building interactive web interfaces
 * using reactive data binding and reusable components. Vue's API is clean
 * and simple, leaving you to focus on building your next great project.
 */

window.Vue = require('vue');

/**
 * We'll load the axios HTTP library which allows us to easily issue requests
 * to our Laravel back-end. This library automatically handles sending the
 * CSRF token as a header based on the value of the "XSRF" token cookie.
 */

window.axios = require('axios');

window.axios.defaults.headers.common = {
    'X-CSRF-TOKEN': window.Laravel.csrfToken,
    'X-Requested-With': 'XMLHttpRequest'
};

/**
 * Echo exposes an expressive API for subscribing to channels and listening
 * for events that are broadcast by Laravel. Echo and event broadcasting
 * allows your team to easily build robust real-time web applications.
 */

// import Echo from "laravel-echo"

// window.Echo = new Echo({
//     broadcaster: 'pusher',
//     key: 'your-pusher-key'
// });



ほとんどライブラリを読み込んでいるだけですが、1つ興味深い処理が行われています。



window.axios.defaults.headers.common = {
    'X-CSRF-TOKEN': window.Laravel.csrfToken,
    'X-Requested-With': 'XMLHttpRequest'
};


axiosは、HTTPクライアントのライブラリです。
ここでは、axoisの設定を変更し、リクエストヘッダにCSRFトークンと、XMLHttpRequestの目印を付けるようにしています。
このようにすることで、axiosを使って送られたリクエストに自動的にCSRFトークンが付与されるようになります。

Vue.jsアプリケーションの起動



アプリケーションの起動処理等は resources/assets/js/app.js に記述します。
ここは以下のようになっています。




/**
 * First we will load all of this project's JavaScript dependencies which
 * includes Vue and other libraries. It is a great starting point when
 * building robust, powerful web applications using Vue and Laravel.
 */

require('./bootstrap');

/**
 * Next, we will create a fresh Vue application instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */

Vue.component('example', require('./components/Example.vue'));

const app = new Vue({
    el: '#app'
});


(1) bootstrap.jsを読み込んで初期化処理を実行
(2) Example.vueというVueコンポーネントを読み込み
(3) Vueアプリケーションを起動

という流れです。

Vue.jsアプリケーションを起動する際には、どの要素をVue.jsアプリケーションのルートとするかを指定します。ここでは、idが"app"である要素がVue.jsアプリケーションのルートになるように指定しています。

Vue.jsでは、「コンポーネント」という単位で、再利用可能なパーツを定義することができます。
resources/assets/js/components/Example.vueは以下の内容になっています。



<template>
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Example Component</div>

                    <div class="panel-body">
                        I'm an example component!
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        mounted() {
            console.log('Component mounted.')
        }
    }
</script>


templateタグの中には、コンポーネントのHTMLを記述します。同様に、scriptタグの中には、コンポーネントの動作を定義します。ここでは使用されていませんが、styleタグを使用して、コンポーネントに適用するCSSを定義することもできます。

独自コンポーネントの定義



次に、独自のVueコンポーネントを定義してみましょう。
resources/assets/js/components/Hello.vueというファイルを、以下の内容で追加します。



<template>
    <h2>Hello <span class="name">{{name}}</h2>!</div>
</template>

<script>
    export default {
        props: ['name']
    }
</script>

<style scoped>
    .name {
        font-weight: bold;
    }
</style>


ここでは、Vue.jsのコンポーネントに、外部から値を渡せるように実装しています。

はじめに、コンポーネントがもつプロパティを、propsプロパティの中に配列で定義します。
次に、コンポーネントを利用する箇所で「<hello name="Laravel"></hello>」のように、コンポーネントに渡したい値を記述します。
propsに定義したプロパティはコンポーネント内から参照できるので、{{name}} のように記述すると文字列としてプロパティの値を出力することができます。

※{{}}という記法はLaravelのテンプレートエンジン(Blade)でも値を出力する際に使用します。Vueコンポーネント内で{{}}を使用する際は気にする必要はありませんが、Bladeテンプレートの中でVue.jsの{{}}を使いたい場合「@{{}}」という風に先頭に@をつけると、Bladeはこの部分を無視します。

style要素にscoped属性をつけている点にも注目です。scoped属性を使うと、style要素内で定義されたスタイルが適用される範囲を限定することができます。ここでは、Vueコンポーネントのテンプレート内でのみ適用可能な「name」クラスを定義しています。テンプレートの外部で「name」クラスを使った場合には、このスタイルは適用されません。

次に、app.jsにHelloコンポーネントを登録します。



Vue.component('hello', require('./components/Hello.vue'));


最後に、welcome.blade.phpのbodyの中を以下のように書き換えます。



<div id="app">
    <hello></hello>
    <hello name="Laravel"></hello>
    <hello name="Vue.js"></hello>
</div>


以下のように表示されればOKです。



参考



Onsen UIが生まれたきっかけ



本記事はOnsen UI Advent Calendar 2016のエントリーです。Onsen UIが生まれたきっかけについて、簡単に紹介したいと思います。

当時PhoneGapといわれていたCordovaに足りなかったもの



2009年頃からWebViewを用いた、今でいうHTML5ハイブリッドアプリ開発をしていた訳ですが、当時のデバイスはパフォーマンスも低く、ブラウザーの機能が貧弱だったことも相まって、満足のいくアプリをHTML5「だけ」で作ることがほぼ不可能と言ってもいい状況でした。

思い返すとiOS 4Android 1.6の時代です。まだposition:fixedも使えず、CSSトランジションも怪しい動きをしていました。

一方で、コンテンツをHTML5で記述することのメリットは多く、アプリのマルチプラットフォーム対応や、サーバーからの動的配信で内容をアップデートすることができるなど、この分野の可能性を感じていました。

それがPhoneGap/Cordova/Monacaにつながっていくわけですが、とはいえUIWebViewだけでは、iOSAndroidのネイティブUIにかなう表現ができなかったのは事実です。

そこでネイティブUIフレームワーク



そこで2010年からMonacaを開発するに当たり、どのようにハイブリッドアプリでUI部分でブレークスルーを実現するかが特に大きな課題だと考えていました。その結果、私たちのチームが開発したのは「ネイティブUIとUIWebViewを組み合わせる」というアプローチです。

具体的には、コンテンツの中身はUIWebViewで記述しつつも、ナビゲーターやタブといったネイティブなトランジションが圧倒的に有利な部分はネイティブで描画する、という仕組みでした。

PhoneGapの拡張エンジンとして、オープンソースでリリースしました(メンテナンスはしていませんが、いまでも公開は続けています)。



このフレームワークでは、複数ページをスムーズに遷移するため、マルチWebView構成になっていました。ようするに、次のページに遷移した場合には別のWebViewが作られ、ページスタックが作られます。

ネイティブUIフレームワークの課題からOnsen UIへ



ただしこの仕組みには一つ大きな問題がありました。それは、WebViewが複数に分かれるため、JavaScriptでスコープを管理するのが大変難しくなったということでした。新しいページが全く別のJavaScriptインスタンスとして表示されるため、同じアプリであるにもかかわらず、ページ間でリソースを共有することが難しかったのです。

また、JSON形式でUI定義を記述できる仕組みとしていましたが、その結果デザインのカスタマイズに弱く、多様な表現をしたいアプリにとって制約になってしまうことも問題でした。

一方、iOS 7やAndroid 4が登場しフラットデザインが主流になるなか、WebViewでフルにUIを実装することの現実味が帯びてきました。そこで、社内で議論を行った末、これまで作っていたネイティブUIフレームワークを捨て、Onsen UIに移行するという決断を行いました。

これからのOnsen UI



最初はAngular 1のディレクティブ機能を使って実装したOnsen UIですが、当初は日本語の文献も全くなく、Angularは難しすぎるのではないか?という危惧もありました。しかし一方で、Angular以上にうまく部品をコンポーネント化できる仕組みがなく、Angularを選定したという経緯があります。

昨今ではCustom Elementsも安定したことから、Onsen UI 2.0よりAngularへの依存をなくし、ピュアなWeb Componentsフレームワークとなりました。そのメリットを生かし、Angular 2やReactへの対応とともに、Vue 2といったメジャーなフレームワークのサポートを進めていきたいと考えています。

ぜひ進化するOnsen UIにご期待ください。そして、日本発の世界的フレームワークの挑戦に向けて、GitHubのスターで応援ください!

Monacaのプレビューを使う際のTips

最近Monacaをハンズオンで使っています。開発環境を用意せず、ブラウザさえあればすぐに動くものが作れるというのは大きなポイントになります。



しかし普段の開発環境とは異なる分、詰まってしまうこともあるようです。そこで今回はMonaca IDEを使った開発において注意して欲しいポイントを紹介します。



Webブラウザとスマートフォンアプリでのイベントの違い



WebブラウザでjQueryなどを使った開発を行っている時に、 $(function() {}) をよく使うかと思います。これはDOMの構築などが終わり、JavaScriptを安全に実行できるようになった状態で呼び出されます。しかしMonaca(Cordova)アプリの場合、さらにプラグインの読み込みなども完了しなければ安全に開始できません。それは deviceready というイベントになります。ただしこのイベントはWebブラウザでは実装されていないので、Monaca IDEのプレビューでは使えません。



そこで次のようにコードを書くと便利です。





function onDeviceReady() {
    alert("読み込まれました");
}
var event = typeof cordova === 'undefined' ? 
                              'DOMContentLoaded' : 'deviceready';
document.addEventListener(event, onDeviceReady, false);


これはWebブラウザの場合はDOMContentLoaded、スマートフォンアプリの場合はdevicereadyをイベントリスナーに設定する指定です。iPhone/iPad/Androidの区別だけではスマートフォンのWebブラウザ判別には使えませんので、Cordovaアプリに特有のグローバル変数であるcordovaがあるかどうかを判定に使っています。これでプレビューの場合も確認が容易になります。



このテクニックはclickイベントとtapstartイベントを分ける際にも使えます。スマートフォンにおけるclickイベントは若干の遅延があるので、tapstartを積極的に使っていくべきなのですが、Monaca IDEのプレビューでは反応しなくなってしまいます。そこで処理分けすることでどちらでも動作する、かつ最適な動作が期待できるようになります。



リモートファイルを読み込む場合の注意



同様にMonaca IDEのプレビューではリモートファイルを読み込むのに制限(CORS)があります。そのため、スマートフォンアプリとして外部のXMLJSONを読み込む場合、同じファイルをMonacaプロジェクト内にも用意して、プレビューの時にはそのファイルを読み込むようにしましょう。処理分けは上記と同じ仕組みが使えます。



自社のアプリで、サーバと通信するといった場合にはCORSの設定を行っておけば問題ないでしょう。



JavaScriptエラーの確認の方法



Monaca IDEにはコンソールが表示されているので、プレビューで起こっているエラーも確認できるかと思ってしまう方がたくさんいます。しかしこれはMonacaデバッガー用のエラー表示になるので、プレビューのエラーは出ません。





そこでエラーの確認方法なのですが、プレビューのアプリの画面相当部分を右クリックします。そして出てきたコンテクストメニューで要素の検証であったり、検証といったDevToolsが開くメニューを選択します。これ以外の方法ですと、プレビュー全体であったり、Monaca IDEを対象としたDevToolsが開いてしまいますので注意してください。





DevToolsが開けば、コンソールを見てエラーメッセージが確認できます。なお、時々保存忘れというケースもありましたので、ちゃんとファイル保存されているかも確認してください。





プレビューはHTMLファイルを開いている時以外には表示されません



プレビューはあくまでもHTMLファイルを編集している場合に限って右側に表示されます。JavaScriptCSSなどを編集中に確認する場合は別ウィンドウとして表示されるので注意してください。なお、別ウィンドウの場合でもファイル編集後のリロードは自動的に行われます。








こうした点に注意すると、初学者の方であってもMonaca IDEを使ったアプリ開発がスムーズに開始できるかと思います。特にプレビューをうまく使えばデザインや動作確認が素早く行えるようになります。ぜひお試しください!

動画を使ったアプリ利用解析を実現するReproはMonacaで簡単に利用できます

Monacaでは多くの外部サービスと連携できるようになっています。今回はアプリマーケティングで欠かせない利用者を理解するのに役立つReproを紹介します。





Reproについて



Reproはアプリの操作を記録し、Web上で動画として操作を確認できるサービスです。ユーザの利用状態を可視化することで、どこでユーザが迷っているのかであったり、アクティブ率に関わるアプリ上の問題を発見することができます。



数多くのプラットフォームに対応しており、Monacaにも対応しています。しかも無料ユーザから利用できるようになっています。





Reproでトークンを取得する



まずReproにてユーザ登録を行います。そうするとトークンが取得できますので、コピーしておきます。Repro側で必要な作業はそれくらいで、とても簡単にはじめられます。



Monacaでの使い方



Monacaで適当なアプリを作成した後、設定メニューの*を選択します。





出てきた一覧の中でReproを選択します。





詳細な説明が出ますのでセットアップをクリックします。





確認が出ますのでOKをクリックして実行します。





これでReproのSDKがインストールされました。





設定を行う



次に測定するための情報を記述します。単純に全操作を記録する場合は次のように書きます。YOUR_APP_TOKENはReproにて取得したトークンに書き換えてください。





document.addEventListener("deviceready",onDeviceReady,false);
function onDeviceReady() {
    Repro.setup("YOUR_APP_TOKEN");
    Repro.startRecording();
}


さらに何かのイベント(クリックなど)を記録したい場合は任意の場所で次のように記述します。





Repro.track("App Launch");


また、ユーザを指定することもできます。





Repro.setUserID("foo@example.com");


これだけで使えますのでとても手軽です。



アプリをビルドする



Reproを使うためにはMonacaデバッガーではなくビルドしたアプリで使う必要があります。今回はiOSアプリとしてビルドしています。





ビルドが完了したら実機にインストールしてください。



Reproを試す



Reproを試す場合は、アプリを起動して適当に操作するだけでOKです。最後にホームボタンを押して終了です。アプリがバックグラウンドになると記録が停止します。動画がアップロードするまで管理画面には反映されませんが、ほとんど待ち時間なく反映されるはずです。





アップロードされた動画はWeb管理画面上でいつでも繰り返し再生ができます。イベントが記録がされていれば、動画の途中に印が入っています。








MonacaアプリにReproを組み込むのはとても簡単にできます。コード量も少ないので、すぐに使いこなせるでしょう。後はアプリの動線を解析し、より良いアプリ開発に取り組んでください。



Repro

Onsen UI 2.0の紹介と始め方

ハイブリッドによるスマートフォンアプリ開発を手軽なものにしてくれるUIフレームワークとして開発しているのがOnsen UIです。現在も開発が継続されており、間もなく2.0が正式リリースとなります(執筆時点でβ)。



特に大きな転換ポイントと言えるのが、AngularJSとの切り離しになります。そこで今回はOnsen UI 2.0の使い方を紹介します。



インストール方法は4つ



Onsen UIのインストール方法は主に4つ用意しています。いずれか使いやすいものを選んでください。



Node.js/npmを使う方法



Node.js/npmを使ってインストールする場合、次のようにコマンドを打ちます。





npm install onsenui@2.0.0-beta.7 --save // β版の現在の場合。
npm install onsenui --save // 2.0の正式版リリース後。現在は1.3系がインストールされます。


また、このままですと node_modules 以下に配置されて使いづらいので、browserifyをインストールします。





npm install -g browserify


そしてコードを書きます。例えばファイルを index.js とします。






require('onsenui');

// 自分のWebアプリケーションのコード


後は最後に browserify を実行します。





browserify index.js -o app.js


これで onsenuiが入った状態でJavaScriptコードが生成されます。JavaScript側ではapp.jsを読み込むようにすれば問題ありません。



Bowerを使う方法



BowerもNode.js/npm同様に進めることができます。Monaca IDEはBowerを使ったプロジェクトのインポートに対応していますので、さらに手軽と言えそうです。ただしBower自体はnpmを使ってインストールします。





npm instlal bower -g


そしてアプリを開発するプロジェクトのルートで bower initを実行します。





bower init


次にOnsen UIをインストールします。現在はバージョンを指定する必要があります。





bower install onsenui#2.0.0-beta.7 --save


ファイルは bower_components/onsenui/ 以下にインストールされます。



ファイルをダウンロードする



3つ目はJavaScript/スタイルシートファイルを直接ダウンロードする方法です。Releases · OnsenUI/OnsenUI-distから最新版がダウンロードできます。解凍したフォルダの中にあるjs/cssフォルダを好きな場所に配置してください。



Monacaのテンプレートを使う



最後にMonacaのテンプレートを使う方法です。Monacaのプロジェクトテンプレートの中に、Onsen 2.0 クイックスタートというテンプレートを用意していますので、これを選択するという方法になります。これは予めOnsen UI 2.0が組み込まれていますので手軽にはじめることができます。





こちらがOnsen UI 2.0クイックスタートプロジェクトです。



Reactと組み合わせたデモも内包されています。



実際に使ってみる



では実際にOnsen UI 2.0を使ってみたいと思います。例えばHTMLは次のようになります。ファイル名は index.html とします。





<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="mobile-web-app-capable" content="yes" />
    <title>Onsen UI 2.0 Quickstart</title>
    <script src="node_modules/onsenui/js/onsenui.min.js"></script>

    <link rel="stylesheet" href="node_modules/onsenui/css/onsenui.css" type="text/css" media="all" />
    <link rel="stylesheet" href="node_modules/onsenui/css/onsen-css-components.css" type="text/css" media="all" />
</head>

<body>
  <ons-page>
  </ons-page>
  <ons-tabbar>
  <ons-tab page="page1.html" label="Page 1" icon="square" active="true"></ons-tab>
    <ons-tab page="page2.html" label="Page 2" icon="square"></ons-tab>
    <ons-tab page="page3.html" label="Page 3" icon="square"></ons-tab>
    <ons-tab page="page4.html" label="Page 4" icon="square"></ons-tab>
    <ons-tab page="page5.html" label="Page 5" icon="square"></ons-tab>
  </ons-tabbar>
</body>


見て分かる通り、onsenui.min.jsしかJavaScript側では読み込んでいません。スタイルシートはベースになるonsenui.cssと、各種コンポーネント用のonsen-css-components.cssを読み込んでいます。



bodyタグの中身はを使ったOnsen UIの定義になります。さらにpage1.htmlを次のような内容で作成します。





<h3 class = "title-h3">iOS Switch</h3>
<p>
  <ons-switch></ons-switch>
</p>
<h3 class = "title-h3">Material Switch</h3>
<p>
  <ons-switch modifier="material" checked></ons-switch>
</p>


こちらはヘッダーもなく、いきなりbodyタグ内の内容を記述します。さらにpage2.htmlを次のように定義します。





<ons-list>
  <ons-list-header>Page 2</ons-list-header>
  <ons-list-item>Item</ons-list-item>
  <ons-list-item>Item</ons-list-item>
</ons-list>


こちらはを使っています。いずれのファイルもJavaScriptの定義であったり、AngularJSの記述は一切ありません。



このファイルを開くと、次のように表示されます。





まさにスマートフォンアプリ風のUIになっているのが分かるかと思います。さらにons-tabbarの一番左のページがデフォルト表示になります。今回はpage2.htmlまでしか作っていませんが、タブ1とタブ2がタップで表示切り替えできる点も確認できるかと思います。






このように外部ライブラリへの依存性が減ったことで、自由度が高まったり、すでに多数あるJavaScriptフレームワークと組み合わせた開発ができるようになっています。Onsen UI 2.0は間もなく正式リリースになります。ぜひお試しください!



Onsen: HTML5 Hybrid App Framework & UI Components

Lebabを使ってECMAScript6を体験しよう

新しいJavaScriptであるECMAScript6(以下ES6)はすでにnode.jsをはじめとする幾つかの環境で使えるようになっています。クラスやimport、Promise、テンプレート文字列など便利な機能がたくさんありますが、なかなか使う機会に恵まれないのではないでしょうか。



そこでよく使われているのはBabelで、ES6で書いたコードを従来のJavaScriptであるECMAScript5(以下ES5)に変換してくれるソフトウェアです。Babelを使うことによって、コーディングは先進的なES6で行いつつ、実際の利用はES5で幅広いブラウザに対応させると言った利用ができます。



今回はその逆で、すでにあるES5のコードをES6に変換してくれるLebabというソフトウェアを紹介します。LebabはBabelを逆から読んだ名前になっています。



Lebabの使い方



Lebabはnpmを使ってインストールができます。





$ npm install -g lebab


コマンドオプションは次のようになっています。





$ lebab --help

  Usage: lebab [options] <file>

  Turn your ES5 code into readable ES6

  Available transforms:

    + class .......... prototype assignments to class declaration
    + template ....... string concatenation to template string
    + arrow .......... callback to arrow function
    + let ............ var to let/const
    + default-param .. use of || to default parameters
    + obj-method ..... function values in objects to methods
    + obj-shorthand .. {foo: foo} to {foo}
    + no-strict ...... remove "use strict" directives
    + commonjs ....... CommonJS module loading to import/export

  Options:

    -h, --help            output usage information
    -V, --version         output the version number
    -o, --out-file <out>  compile into a single file
    --enable <a,b,c>      enable only specified transforms
    --disable <a,b,c>     disable specified transforms


後はES5のJavaScriptファイルと、出力先のJavaScriptファイルを指定すればOKです。





$ lebab es5.js -o es6.js


利用するライブラリ、しないライブラリを指定することもできます。





$ lebab es5.js -o es6.js --enable let,arrow,commonjs


他にもWeb上でライブデモを使って試すこともできます。





サンプル



例えば変数の定義と、その後での上書きによってconst/letを使い分けてくれます。





// Let/const
var name = 'Bob', time = 'yesterday';
time = 'today';
  ↓
const name = 'Bob';
let time = 'yesterday';
time = 'today';


nameは定義したまま使っていますのでconst、timeはその後で上書きしているのでletを使っています。



テンプレート文字列も使えます。





// Template string
console.log('Hello ' + name + ', how are you ' + time + '?');
  ↓
// Template string
console.log(`Hello ${name}, how are you ${time}?`);


オブジェクトメソッドはfunctionが省略されます。





var bob = {
  // Object shorthand
  name: name,
  // Object method
  sayMyName: function () {
    console.log(this.name);
  }
};
  ↓
const bob = {
  // Object shorthand
  name,
  // Object method
  sayMyName() {
    console.log(this.name);
  }
};


さらにクラスもきちんと認識されます。





var SkinnedMesh = function SkinnedMesh() {
};

SkinnedMesh.prototype.update = function (camera) {
  camera = camera || createCamera();
  this.camera = camera;
};

Object.defineProperty(SkinnedMesh.prototype, 'name', {
  set: function (geometry) {
    this.geometry = geometry;
  },
  get: function () {
    return this.geometry;
  }
});

  ↓
class SkinnedMesh {
  update(camera=createCamera()) {
    this.camera = camera;
  }
  set name(geometry) {
    this.geometry = geometry;
  }
  get name() {
    return this.geometry;
  }
}


後はNode.jsの場合requireを使ってきましたが、importに書き換わります。





var lebab = require('lebab');
module.exports = SkinnedMesh;
  ↓
import lebab from 'lebab';
export default SkinnedMesh;


最後にアローファンクションです。これもES6の特徴的な書き方だと思います。





var render = function () {
  requestAnimationFrame(render);
};
  ↓
const render = () => {
  requestAnimationFrame(render);
};


Lebabを使うメリット



普段の開発の中でLebabを使うことはあまり多くないかと思います。しかし今後ES6が広まっていく中で、ES6の書き方を覚える必要が出てくるでしょう。そんな時にLebabを使って軽く試せるとモダンな書き方を確認できるようになります。



さらに既存のES5で書かれた多くのリソースもLebabを使うことでES6に変換できるようになります。資産が多すぎるために乗り換えを躊躇してしまっている場合は特に便利ではないでしょうか。



ぜひLebabを試してみてください!



mohebifar/lebab: Turn your ES5 code into readable ES6. It does the opposite of what Babel does.

jQuery 3.0βリリース。2系と3系の違いについて

2016年に入り、jQuery 3.0βがリリースされました。はっきりとしたロードマップは出ていないものの、今年中には正式リリースするのではないでしょうか。



そこで今回はjQuery 2.xと3.xで何が変わるのか、紹介したいと思います。



jQuery Compatはなくなります



元々レガシーなブラウザをサポートするjQuery Compatも開発されていましたが、MicrosoftがIE8をサポートしなくなったのを受けて、jQueryIEを非サポートすることになりました。その結果、jQuery Compatの開発は中止となり、jQuery 3.x一本になります。



Alphaで実装したshow()、hide()のインライン操作の排除



元々実験的なものでしたが、スタイルシートなどで設定された場合に必ずしも正しく動かないことが分かったためとしています。メソッド自体は残りますのでこれまで通りとなります。



data()メソッドでのアクセス方法変更



HTML Standardに沿う形になり、単語を-でつなぐ kebab-case から 2つ目以降の単語の最初の文字を大文字化する camelCase に変更するとのことです。



jQuery.DeferredがPromises/A+互換になります。



ES2015 Promisesとも同じとのことなので安心して利用できるようになりそうです。この場合、





promise.then(function() {
  // 正常終了の場合
},
function() {
  // エラーの場合
});


となり、これまで使われていた .catch() が使えなくなります。そのため、.catch().then(null, function() {})エイリアスになります。



.width(), .height(), .css(“width”), and .css(“height”) が十進数を返すようになります



これまでは整数しか返さなかったと思いますが、今後は小数点以下も含むようになります。



古いイベント処理がなくなります



.load.unload.error はなくなります。今後は .on に統一されます。



アニメーション処理はrequestAnimationFrameで行います



requestAnimationFrameはパフォーマンスを意識したアニメーションを実装できるようになります。ただしIE9Android 4.4未満では実装されていません。



DOM操作を行う幾つかのメソッドが使えなくなります



jQuery.dirjQuery.siblingjQuery.buildFragmentjQuery.accessjQuery.swapはドキュメントにも記載されなくなり、アクセスできなくなります(内部では使うようです)。



カスタムセレクターの高速化



:visible の改善により、顕著な場合では17倍も高速になったとのことです。






なお、最新版では2.2.0はミニファイ版が85,589 byte、3.0βが86,071 byteとなっています。非サポートのブラウザは増えたものの、さほど大きさは変わらない模様です。常に進化しているとあって、今後のWeb/ハイブリッドアプリ開発でもjQueryは欠かせぬ存在になりそうです。



jQuery 3.0 Beta Released | Official jQuery Blog

MonacaアプリをECMAScript6で書いてみよう

今、JavaScriptの主流はECMAScript5となっています。色々と書きづらい点も多く、そのためにCoffeeScriptやTypeScriptのような代替言語が生み出されています。書き方の他、クラスや定数などの機能が不足している感も否めません。



それを解決してくれるのがECMAScript6になります。いち早く体験するにはBabelが良いのは以前も書きましたが、今回はMonacaアプリでBabelを利用する方法を紹介します。なお、利用に際してはMonaca Localkitを前提としています。また、Node.jsは予めインストールされていることとします。



LocalKitでプロジェクトを作成する



まず最初にMonaca LocalKitを使ってプロジェクトを作成します。今回はHello World Appを使っています。作成するとこのようなファイル構成になっているかと思います。





Gulpなどをインストールする



Babelを使う際にはGulpを使ってECMAScript6から通常のJavaScriptへの変換を自動化するのがお勧めです。そこでGulpなど必要なライブラリをインストールします。まず最初にnpm initを実行しておきます。





npm init


ではライブラリをインストールします。





npm install --save-dev gulp gulp-babel \
                       babel-preset-es2015 gulp-sourcemaps \
                       gulp-concat


インストールしているライブラリを解説します。



  • gulp : Node.js用のタスクランナーです
  • gulp-babel : BabelのGulp用プラグインです
  • babel-preset-es2015 : Babelを使うことを指定します
  • gulp-sourcemaps : ECMAScript6のソースマップを生成しています
  • gulp-concat : 変換されたJavaScriptファイルを連結しています


Gulpファイルを記述する



Babelを使うための設定を行います。今回はGulpの設定もECMAScript6で書きたいと思います。そこでまず package.json に下記の設定を追加します。





"babel": {
   "presets": ["es2015"]
}


この指定があることでGulpがデフォルトでECMAScript6の記法を理解してくれます。Babelを使う場合、ファイル名は gulpfile.babel.js になりますので注意してください。例えば次のような内容になります。





import gulp       from 'gulp'
import sourcemaps from 'gulp-sourcemaps';
import babel      from 'gulp-babel';
import concat     from 'gulp-concat';

const file_path ='./www/src/**/*.js';

gulp.task('js', () => {
	return gulp.src(file_path)
		.pipe(sourcemaps.init())
		.pipe(babel({
			presets: ['es2015']
		}))
		.pipe(concat('all.js'))
		.pipe(sourcemaps.write('.'))
		.pipe(gulp.dest('www/js'));
});

gulp.task('watch', () => {
  gulp.watch(file_path, ['js'])
});

gulp.task('build', ['js']);
gulp.task('default', ['build', 'watch']);


importが使えること、functionという記述が省略できるのがECMAScript6らしいところでしょうか。



試してみる



今回の設定の場合、www以下にsrcというフォルダを作成し、そこにJavaScriptを記述していきます。例えば次のようになります。functionの省略は先ほど同じで、クラスやテンプレートも使っています。





var onDeviceReady = () => {
  const hello = new Hello({name: "MOONGIFT"})
  hello.say();
};

class Hello {
  constructor(options) {
    this.options = options;
  }
  say() {
    console.log(`Hello World, ${this.options.name}`);
  }
}
var event = typeof cordova === 'undefined' ? 'DOMContentLoaded' : 'deviceready';

document.addEventListener(event, onDeviceReady, false);


そしてgulpコマンドを打つとwww/js/all.jsというファイルが生成されます。今後はファイルの追加、変更をウォッチしていますので気にせずプログラミングが続けられます。



後はこの生成されたファイルをHTMLから読むようにします。複数のJavaScriptファイルであっても連結されますので、この1ファイルだけ読み込めばOKです。





<script src="js/all.js"></script>


実際にアプリをプレビューで開くと、コンソールに次のように表示されるはずです。ECMAScript6がJavaScriptに変換され、テンプレートメッセージも使われています。





ソースマップファイルも出力しているので、ECMAScript6側のコードでブレークポイントの挿入が可能です。








Babelを使うことでよりモダンなJavaScriptの書き方できるようになります。これまで感じていた機能不足感、書きづらさも解消されてくるでしょう。ぜひMonacaアプリでも使ってみてください。



Monaca Localkit - ローカル型HTML5ハイブリッドアプリ開発ツール

Monacaアプリのライブラリをnpmで管理しよう

JavaScriptのライブラリ管理としてはBowerがよく使われてきましたが、最近ではnpmを使ってパッケージ管理することが増えています。数多くのライブラリがnpmにも対応しており、Node.jsがあればデフォルトでnpmが入ってくれるので使うのも容易です。



そこで今回はMonacaアプリ開発においてパッケージ管理をnpmで行う方法を紹介します。なお、利用に際してはMonaca Localkitを前提としています。また、Node.jsは予めインストールされていることとします。



LocalKitでプロジェクトを作成する



まず最初にMonaca LocalKitを使ってプロジェクトを作成します。今回はHello World Appを使っています。作成するとこのようなファイル構成になっているかと思います。





jQueryを使ってみる



今回は試しとしてjQueryをインストールしてみましょう。最初にMonacaアプリのルートディレクトリでnpm initを実行します。





npm init


色々聞かれますが、ウィザード方式に沿って進めていってください。ではjQueryをインストールします。





npm install jquery --save


インストールが終わったら、JavaScriptを作成します。 www/src/hello.js とします。





var $ = require('jquery');

var onDeviceReady = function() {1
  console.log($);
  console.log("Hello World");
}

event = (typeof cordova == 'undefined') ? 'DOMContentLoaded' : 'deviceready';

document.addEventListener(event, onDeviceReady, false);


ここで注意が必要なのは、require('jquery')となっている点です。もちろん、通常のJavaScriptのままではこれは実行できません。



Browserifyを使う



そこでパッケージの解決に使うのがBrowserifyです。こちらもnpmを使ってインストールします。





npm install -g browserify


これで準備できました。先ほど作成したJavaScriptを指定してコマンドを実行します。





browserify www/src/hello.js -o www/js/app.js


このように実行すると、www/js/app.jsというファイルが生成されます。このファイルは先ほどのrequireを解決した状態になっています。



実行してみる



ではこのapp.jsをHTMLから読み込んで、Monaca Localkitでプレビューを実行してみましょう。





<script src="js/app.js"></script>


プレビューを開いた時、以下の画像のようにjQueryオブジェクトが表示されるはずです。ちゃんとライブラリが読み込めているのが分かります。








今はBowerも使えますが、npmでパッケージを提供し、Node.jsとブラウザ両方で動作するライブラリも増えています。BowerはMonaca IDEでも使えますが、npmは使えません。もしnpmを使いたい場合はMonaca Localkitを使ってみてください。



Monaca Localkit - ローカル型HTML5ハイブリッドアプリ開発ツール