browserify + debowerify + vueifyを使ったvue.js component開発

この記事は[Vue.js Advent Calendar 2014](http://qiita.com/advent-calendar/2014/vue) 16日目の記事です。

みなさんはvueifyというツールをご存知でしょうか？

[vueify](https://github.com/vuejs/vueify)はvue.jsのコンポーネントをHTML/CSS/JSをコンポーネント単位で一つのファイルに記述して、モジュール管理できるbrowserifyを利用したビルドタスクです。

まだ日本ではあまり記事がなく普及していないかもしれませんが、試しに使ってみたら非常に生産性を高める可能性を秘めていることがわかりましたのでご紹介させていただきます。


今回の記事ではbrowserify + debowerify + vueifyも使ってVueコンポーネントの簡単なサンプルを作ります。

## ツール
まずはそれぞれのツールの紹介です。

### browserify
http://browserify.org/
browserifyは既にご存知の方が多いと思います。
元々はnodejsのモジュールの管理の仕組みとして使っていたrequireによるロードの仕組みをクライアント（ブラウザ）側のモジュールにも適応できるツールです。
コマンドラインだけでなくgruntやgulpでビルドタスクを実行することができます。

### debowerify
https://github.com/eugeneware/debowerify
bowerでインストールしたJSライブラリをbrowserifyでrequireできる形に変換するタスクです。

### vueify
https://github.com/vuejs/vueify
HTML+CSS+JSがセットにしたファイル（拡張子は.vue）でvue.jsのVMインスタンスのオプションを記述したものをbrowserifyで読み込み可能なHTML+CSS+JSに変換するタスクです。

##.vueファイル
Vueifyを使う際には例えば以下のように.vue形式のコンポーネントを記述します。(Polymerでのweb componentsの定義の仕方に似ていますね。)
scriptタグではv-componentに使用されるvue.jsのコンポーネントの定義をexportsにセットしています。

```sample.vue
<style>
.sample {
    width: 280px;
    border: 1px solid #eee;
}
.sample .title {
    font-weight: bold;
    font-size: 24px;
}
.sample .body {
    padding: 8px 0;
    text-align: center;
}
</style>

<template>
<div class="sample">
    <div class="title">{{title}}</div>
        <div class="body">
        <img v-attr="src: logo">
        <div>{{description}}</div>
        <a href="{{url}}">{{url}}</a>
    </div>
</div>
</template>

<script>
module.exports = {
    data: function () {
        return {
            title: "", 
            logo: "",
            url: "",
            description: ""
        };
    }
};
</script>
```

もしCSSをstylusやsassやlessを使って記述したい場合、cssを定義するstyleタグのlang属性に指定することができます。

```sample.vueのstyleタグでstylusを使う
<style lang="stylus">
.sample
    width 280px
    border 1px solid #eee

    .title
        font-weight bold
        font-size 24px

    .body
        padding 8px 0
        text-align center
</style>

```

HTMLの記述にjadeを使いたい場合は、<template>タグのlang属性にjadeをセットすることで利用可能です。
注意点は、対応するnodeモジュールをnpmでインストールしておく必要があります。

e.g. stylusで記述する時にはnpmでインストール
`npm install stylus --save-dev`

## ディレクトリ構造

今回の例では、bowerでvue.jsをインストールしてgulpでタスクを流します。

まずはbowerでvue.jsをインストール。
`bower install vue --save`

特にこのディレクトリ構造である必要はないですが、一つの例として全体の記載します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/51191/181bd56b-00a2-b27c-0401-0ccdce968688.png" width="254" height="312"></img>



## mainのjs定義

上記で作成したsample.vueコンポーネントを読み込むmainのJSを定義します。以下のように、それぞれbowerでインストールしたvue本体と自作のvueコンポーネントをrequireします。

```sample/main.js
    // bowerifyにより読み込み可能になっている
    var Vue = require("vue");
    // 自作成した.vue形式のコンポーネント
    var vueSample = require("../components/sample.vue");

    var app = module.exports = new Vue({

        el: '#app',
        // 自分の配下で使用するコンポーネント（<vue-sample>）を定義
        components: {
            "vue-sample": vueSample
        },

        data: {
            items: []
        },

        created: function() {
            // ダミーデータをセット
            this.items.push({
                "title": "hoge",
                "logo": "https://connpass-tokyo.s3.amazonaws.com/thumbs/de/dc/dedc44c50713733d06b9121186469c18.png",
                "url":"http://hogehoge/",
                "description": "hogehoge"
            });
            this.items.push({
                "title": "fuga",
                "logo": "https://connpass-tokyo.s3.amazonaws.com/thumbs/de/dc/dedc44c50713733d06b9121186469c18.png",
                "url":"http://fugafuga/",
                "description": "fugafuga"
            });
        }
    });
```

## HTMLの定義
サンプルのHTMLはvueコンポーネントをweb componentsみたいに<vue-sample>タグを使って埋め込むだけです。
それと同時に、データであるitemsをv-repeatで渡しています。

```sample/sample.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Vue Components Sample</title>
</head>
<body id="app">
    <h1>Vue Components Sample</h1>
    <!-- components -->
    <vue-sample v-repeat="items"></vue-sample>
    <!--/ components -->
    <!-- script -->
    <script src="../dist/main.js"></script>
    <!--/ script -->
</body>
</html>
```

## gulpによるvueifyタスクの実行

gulpで例えば以下のようにwatchなどを定義して、browserifyのvueifyタスクを実行します。(coffeeでなくてすみませんw)

```gulpfilejs
var gulp  = require('gulp');
var runSequence = require('run-sequence');
var browserify = require('gulp-browserify');

// .jsファイルと.vueファイルをwatch
gulp.task('watch', function() {
    gulp.watch(['sample/**/*.js', 'components/**/*.vue'], ['browserify']);
});

// browserifyのタスクのtransformにdebowerifyとvueifyを指定する
gulp.task('browserify', function() {
    gulp.src('sample/main.js')
        .pipe(browserify({transform: ["debowerify", "vueify"]}))
        .pipe(gulp.dest('dist'));
});

// watchして変更があったらビルドを実行するタスクの定義
gulp.task('default', function(){
    runSequence('browserify', 'watch');
});
```

実行するとcompileされたJSが出来上がります。（ちなみにビルドはgulpを使わなくてももちろんコマンドでもできます。）

出力先(dist/main.js)ファイルの下の方を見ると自分で定義した.vueファイルの中身がきちんとbrowserifyで生成したモジュールとなっていることが確認できます。

```dist/main.js(一部)
function(require,module,exports){
require("insert-css")(".sample{width:280px;}.sample .title{font-weight:700;font-size:24px}.sample .body{padding:8px 0;text-align:center}");
var __vue_template__ = "<div class=\"sample\">\n    <div class=\"title\">{{title}}</div>\n        <div class=\"body\">\n        <img v-attr=\"src: logo\">\n        <div>{{description}}</div>\n        <a href=\"{{url}}\">{{url}}</a>\n    </div>\n</div>";
module.exports = {
    data: function () {
        return {
            title: "", 
            logo: "",
            url: "",
            description: ""
        };
    }
};
module.exports.template = __vue_template__;
```

出力されたJSを読み込むsample/sample.htmlをブラウザで開くと、データバインディングされてきちんと表示されました。

![スクリーンショット 2014-12-16 19.43.27.png](https://qiita-image-store.s3.amazonaws.com/0/51191/ace56b66-ece7-706f-6160-1d4ebbe6bae0.png)




## sublime text package
.vue拡張子はエディターがシンタックスハイライトに対応していないので、sublime textでは以下のプラグインを使用することでハイライトされます。
https://github.com/vuejs/vue-syntax-highlight



<hr>

以上、いかがでしょう。個人的には長年フロントエンド開発をやってきましたが、こんなにお手軽にコンポーネントベースのJS開発ができるのは画期的だなと思いました。

vue.jsコンポーネント開発のためのvueify紹介記事でしたが、次回からは実際にアプリを作っていくための応用編を書きたいなと思います。