Edited at

Vue.jsを使った大規模開発に必要なもの

More than 1 year has passed since last update.


初めに

正直JS界隈のエコシステムは難しいです。わからないことが目白押しです。

今までVue.jsをブラウザ上でなんのエコシステムも使わずに使ってきましたが、

規模が大きくなるとしんどくなるのは明らかなので

Vue.jsガイド:大規模アプリケーションの構築を参考にwebpackを使ったりnpmを使って、

それっぽい環境を作って一からコードを書いてみました。

(編注:上記リンクはv1.0当時のものです。v2.0のガイドは単一ファイルコンポーネントプロダクション環境への配信のヒントルーティング状態管理単体テストサーバサイドレンダリングなどをご覧ください。)


前提

Vue.jsガイド:大規模アプリケーションの構築の指示に従い構築するだけですが、この時点で以下の様な選択肢がある模様です。


  • プリプロセッサ?の違い

    Webpack(こっちだけ触ります)

    Browserify


  • ルーティング

    オフィシャルでvue-router推奨なのでそれに従います。


  • サーバとの通信

    vue-resourceを使うといいみたいですが、今回はjqueryを使います。


  • 単体テスト

    Karma


上記以外の技術は使わない方向でやってみます。


はじめよう

まずはnpmを使えるようにします。

$ brew install npm


環境構築


npmのパッケージ導入

プロジェクトの為のディレクトリを作りそのディレクトリに移動します。

そして、以下のコマンドでpackage.jsonを作成します。

$ npm init

対話プロンプトに適当に答えると以下の様なファイルが作成されます。


package.json

{

"name": "vuejssample",
"version": "0.0.1",
"description": "vue.js + webpack+ vue-loader mysample",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "m0a",
"license": "MIT"
}

webpack,vue-loaderの導入(local)


インストール

#ローカルインストール

npm install webpack vue-loader --save-dev
npm install vue --save

#グローバルインストール
npm install webpack -g


--save-dev オプションはインストール時に開発用の依存関係としてpackage.jsonに登録するってことです。

--saveは実際に必要なライブラリをpackage.jsonに登録します。

ローカルインストールなのでnode_modulesにインストールされますが、

リモートリポジトリに登録する際に上記を除外します。

除外するために.gitignoreを作っておきます。自分の場合は環境を作っているので

gi >.gitignoreで一発です。

以降必要なライブラリはnpmで管理できるようになりました。


webpackの設定

webpack用のconfigはwebpack.config.jsとなります。

webpackがビルドプロセスでvue-loaderを動かして.vueファイルを解釈してくれます。

以下の様なconfigを作ります。


webpack.cinfig.js


// webpack.config.js
module.exports = {
entry: './src/main.js',
output: {
path: "./build",
filename: 'build.js'
},
module: {
loaders: [
{ test: /\.vue$/, loader: 'vue' },
]
}
}

src/main.jsにコードを書いていき最終的な結果を得るのはbuild/build.jsというわけですね。


開発します。(最初はシンプルに)

まずはディレクトリを作ってみます。

webpack.config.jsに書いたようにsrcディレクトリが開発用

buildディレクトリが出力用です。

以下のように基本的なファイルを作成します。


./src/main.js

var Vue = require('vue')

var appOptions = require('./app.vue')
var app = new Vue(appOptions).$mount('#app')



./src/app.vue

<style>

.red {
color: #f00;
}
</style>

<template>
<h1 class="red">{{msg}}</h1>
</template>

<script>
module.exports = {
data: function () {
return {
msg: 'Hello world!'
}
}
}
</script>



index.html

<!DOCTYPE html>

<html>
<head>
<meta charset="utf-8">
<title>vue simple sample </title>
</head>
<body>
<div id="app"></div>
<script src="build/build.js"></script>
</body>
</html>


editorにatomを使っているならLanguage Vue Componentを導入するとシンタックスハイライトが効きます。

作り終わったら

$ webpack --watch

でファイル修正時に自動でビルドが効くようになります。

参考:

https://github.com/vuejs/vue-loader-example

ここまでの作業を以下に記録します

https://github.com/m0a-mystudy/vuejssample/tree/v1.0


vue-router導入

Vue.jsはシンプルで素敵ですがrouterがないのが辛いです。

公式推奨のvue-routerを導入します。

以下のコマンドを実行するだけで導入できます。


インストール

$ npm install vue-router --save


おお、簡単に導入できた。環境を作るメリットがやっとわかりました。

main.jsをいじってモジュールをロードしルーティングの設定コードを記述します。


main.js


var Vue = require('vue')
+ var VueRouter = require('vue-router')
+ Vue.use(VueRouter)
- var appOptions = require('./app.vue')
- var app = new Vue(appOptions).$mount('#app')
+ var PageA = Vue.extend(require('./components/pageA.vue'));
+ var PageB = Vue.extend(require('./components/pageB.vue'));
+ var App = Vue.extend({});
+ var router = new VueRouter();

+ router.map({
+ '/pageA': {
+ component: PageA
+ },
+ '/pageB': {
+ component: PageB
+ }
+ })
+
+ router.start(App, '#app')


app.vueは削除して以下のファイルを追加します。


src/components/pageA.vue

<style>

.yellow {
color: #f0f;
}
</style>

<template>
<h1 class="yellow">apge A</h1>
<p>
this page is A!!!
</p>
<p>
msg = {{msg}}
</p>

</template>

<script>
module.exports = {
data: function () {
return {
msg: 'yes page A'
}
}
}
</script>



src/components/pageB.vue

<style>

.blue {
color: #00f;
}
</style>

<template>
<h1 class="blue">apge B</h1>
<p>
this page is B!!!
</p>
<p>
msg = {{msg}}
</p>

</template>

<script>
module.exports = {
data: function () {
return {
msg: 'yes page B!!!!!'
}
}
}
</script>


index.htmlを以下のように修正します。

<!DOCTYPE html>

<html>
<head>
<meta charset="utf-8">
<title>vue simple sample </title>
</head>
<body>
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- ナビゲーション向けに v-link ディレクティブを使用 -->
<a v-link="{ path: '/pageA' }">Go to PageA</a>
<a v-link="{ path: '/pageB' }">Go to PageB</a>
</p>
<!-- route outlet -->
<router-view></router-view>
</div>
<script src="build/build.js"></script>
</body>
</html>

ページ遷移ができました。

以下に記録を残します。

https://github.com/m0a-mystudy/vuejssample/tree/v2.0/


jQueryをglobalに設定する

さてjQueryをインストールします。


インストール

npm install jquery --save


以下の設定でglobalにします。


webpack.config.js

+var webpack = require('webpack');

+
module.exports = {
entry: './src/main.js',
output: {
path: './build',
filename: 'build.js'
},
module: {
loaders: [
{ test: /\.vue$/, loader: 'vue' },
]
- }
+ },
+ plugins: [
+ new webpack.ProvidePlugin({
+ $: "jquery",
+ })
+ ]
}

試しに使ってみます。

以下のようにコードを変更


src/components/pageA.vue

<template>

<h1 class="yellow">apge A</h1>
<p>
this page is A!!!
</p>
<p>
msg = {{msg}}
</p>
+ <pre>
+ {{items | json 2}}
+ </pre>
+

</template>

<script>
+ // webpack.ProvidePluginがないと以下を追加しないとjqueryが使えません
+ // var $ = require('jquery');
module.exports = {
data: function () {
return {
- msg: 'yes page A'
+ msg: 'yes page A',
+ items:''
+ }
+ },
+ ready:function(){
+ this.getJson();
+ },
+ methods: {
+ getJson:function(){
+ var that = this;
+ $.ajax({
+ type: 'GET',
+ crossDomain: true,
+ url: 'https://qiita.com/api/v2/items?page=1&per_page=5',
+ dataType: 'json',
+ success: function(json) {
+ that.$data.items = json;
+ },
+ data: null
+ });
+
}
}
}


こうして書いてみるとテンプレートとコードが一つにまとめられるのは便利っぽいですね。

以下にコードを残します。

https://github.com/m0a-mystudy/vuejssample/tree/V3.0


テスト

テスト環境を作ります。


karmaの導入

karma自体はテストランナー(テストを自動実行するツール)で実際のテスト用のフレームワークはjasmineを使います。

jsのエコシステムはこんな感じでいろんな名前が付いてるツールを組み合わせるし、デファクトスタンダードもよくわからないので、本当に途方にくれます。

まずはシンプルな環境から入れていきます。

karma-chrome-launcherを入れていることからわかるようにテスト自体はchromeで行う想定です。

別なブラウザを使う方は逐次入れ替えてください。


インストール

# Install Karma:

$ npm install karma --save-dev

# Install plugins that your project needs:
$ npm install karma-jasmine karma-chrome-launcher --save-dev

$ npm install -g karma-cli


参考:http://karma-runner.github.io/0.13/intro/installation.html

さてここでさらに今回の環境のプラグインを導入します。


インストール

npm install --save-dev karma-webpack

npm install --save-dev karma-sourcemap-loader

karma-webpackを使うことでテストを実行するファイルをプリプロセスでワンファイル化します。今回.vueファイルにまとまっていますが、

karma自体にこのファイルを解釈する機能がないのでwebpackでjsに変換後にテストを走らせる想定です。

ただしそのままではエラー時に行番号が分からなくなるので、karma-sourcemap-loaderを入れることで行番号を出すようにします。


karma用のconfig作成

以下のコマンドにてkarma.conf.jsを作成します。

krama init

対話インターフェースには全部イエスと答えておきます。

作成後以下のように修正します。


karma.conf.js

var webpack = require('webpack');

// Karma configuration
// Generated on Sat Oct 03 2015 17:25:04 GMT+0900 (JST)
module.exports = function(config) {
config.set({

basePath: '',
frameworks: ['jasmine'],
files: [
'test/*_test.js', //<-- testディレクトリ内にtestを書く想定です。
],
exclude: [
],

preprocessors: {
'test/*_test.js': ['webpack','sourcemap'], //<-- 追加
},

reporters: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
// <-- 追加(start)
webpack: {
// webpack configuration
module: {
loaders: [
{ test: /\.vue$/, loader: 'vue' },
]
},
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
})
],
devtool: 'inline-source-map' //<-- 基本的にほとんどwebpack.config.jsと同じですが、ここの追加を忘れないようにします。
},
webpackMiddleware: {
// webpack-dev-middleware configuration
// i. e.
// inoInfo: true
},
plugins: [
require("karma-jasmine"),
require("karma-webpack"),
require("karma-chrome-launcher"),
require("karma-sourcemap-loader"),
]
// <-- 追加(end)
})
}



テストの実行

プロジェクト直下から以下のコマンドで実行します。

karma start

専用のブラウザが立ち上がりファイルが変更されるたびに自動実行されます。

ログはターミナルに出るので、別途ターミナルを立ち上げて実行した方がいいでしょう。


テストコードを書いていく

testディレクトリを作成し以下のようにテストコードを記載していきます。


test/pageA_test.js


describe('pageA', function () {
// require source module
var pageA = require('../src/components/pageA.vue');
it('should have a ready hook', function () {
expect(typeof pageA.ready).toBe('function')
})
it('should set correct default data', function () {
expect(typeof pageA.data).toBe('function')
var defaultData = pageA.data()
expect(defaultData.msg).toBe('yes page A')
})
it('set method cheeck', function() {
expect(typeof pageA.methods.getJson).toBe('function')
})
})

//非同期実行のテスト
describe('pageA.methods.getJson', function () {
// require source module
var pageA = require('../src/components/pageA.vue');
var pageAThis = {$data:{}}; //pageA.methods.getJsonはthisにアクセスするのでここで作成しておく
beforeEach(function(done){
pageAThis.$data = pageA.data(); //$data手動初期化
// console.log(pageAThis);
pageA.methods.getJson.call(pageAThis,done); //thisを指定して呼び出し。callバックにてdoneを実行されないと次に進まない
})

it('pageA.methods.getJson result', function() { //done実行後に呼び出される
// console.log(pageAThis);
expect(typeof pageAThis.$data.items).toBe('object')
})
})


テストが自動実行されるなら以下のようなエラーが出るかと思います。

Chrome 45.0.2454 (Mac OS X 10.11.0) pageA.methods.getJson pageA.methods.getJson result FAILED

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Chrome 45.0.2454 (Mac OS X 10.11.0): Executed 4 of 4 (1 FAILED) (5.006 secs / 5.003 secs)

pageAのgetJsonはcallbackを受け取らない実装になっていたので修正します


src/components/pageA.vue


methods: {
- getJson:function(){
+ getJson:function(callback){
var that = this;
$.ajax({
type: 'GET',
@@ -42,6 +42,9 @@
dataType: 'json',
success: function(json) {
that.$data.items = json;
+ if(typeof callback === 'function') {
+ callback();
+ }
},
data: null
});

保存すると自動実行されて今度は成功しているかと思います。

ここまでの作業は以下に記録しています。

https://github.com/m0a-mystudy/vuejssample/tree/V4.0

次やるとしたらプロダクション環境の構築になります。