クロコめも2。

ただのめもー

vuejsプロジェクト雛形を楽に作りたい

2日経つと前に何作ってたか忘れてまた最初からやりたくなる病。
そのために作り始めを早めるメモ

主に組み込むものとバージョン

  • node(v5.4.0)
  • npm(3.5.3)
  • vue(^1.0.14)
  • vuex(^0.2.0)
  • es6
  • mocha(2.3.4)
  • power-assert(1.2.0)

手順概要

  1. ディレクトリ用意
  2. いろいろインストール
  3. 環境に必要なもの用意
  4. 小さいvuexサンプル
  5. 最小テスト

手順詳細

ディレクトリ用意

# ディレクトリ作成
mkdir プロジェクトのフォルダ
cd プロジェクトのフォルダ
mkdir -p {build,test/{unit,e2e},src/{components,views/app,filters,directives,assets/{js,style,images}}}

いろいろインストール

npm init --y
npm i -g webpack webpack-dev-server mocha babel-core babel-loader power-assert
npm i -S vue vuex
npm i -D babel-core babel-loader webpack style-loader css-loader sass-loader html-loader mocha mocha-loader babel-plugin-add-module-exports babel-plugin-transform-runtime babel-polyfill babel-preset-es2015 babel-preset-stage-2 babel-runtime file-loader url-loader espower-babel power-assert

環境に必要なもの用意

.babelrc

{
  "presets": ["es2015", "stage-2"]
}

webpack.config.js

module.exports = {
    entry: {
        app: "./src/assets/js/app.js"
    },
    output: {
        path: './build/js',
        publicPath: '/js/',
        filename: "[name].js"
    },
    resolve: {
        alias: {
            vue: "vue/dist/vue"
        },
        modulesDirectories: ['node_modules', 'src/', 'src/assets/js/**', 'src/assets/style/**', 'src/views/**', 'src/directives/**', 'src/filters/**', 'src/components/**'],
 // require()するときに拡張子を省略可能にします
 extensions: ['', '.webpack.js', '.web.js', '.js']
 },
 devServer: {
            contentBase: "./build",
        },
 module: {
            preloaders: [],
            loaders: [
                {
                    test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
                    loader: "url-loader?limit=10000&minetype=application/font-woff"
                },
                {
                    test: /\.png$/,
                    loader: "url-loader?limit=100000"
                },
                {
                    test: /\.jpg$/,
                    loader: "file-loader"
                },
                {
                    test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
                    loader: "file-loader"
                },
                {
                    test: /\.scss$/,
                    loader: "style!css!sass"
                },
                {
                    test: /\.html$/,
                    loader: "html"
                },
                {
                    test: /\.js(x?)$/,
                    loader: 'babel-loader?sourceMaps=true'
                }
            ]
        }
 };

package.jsonのscriptsの中身を書き換え

  "scripts": {
    "hot": "webpack-dev-server -d --inline",
    "test": "mocha --compilers js:espower-babel/guess test/unit",
    "e2e": "mocha --compilers js:espower-babel/guess test/e2e"
  },

上から、
npm run hotデバッグ用のサーバ起動するためのもの
npm testでtest/unit配下に格納したテストを実行するもの
npm run e2eでe2eテストに格納したテストが動くはず(今回はやらない)

小さいvuexサンプル

前段その1サーバ起動確認

build/index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>sample</title>
</head>
<body>
    <div id="app">this is app</div>
    <script type="text/javascript" src="js/app.js" charset="utf-8"></script>
</body>
</html>

src/assets/js/app.js

console.log('hoge');

ここでコンソール上でnpm run hotを実行して下記URLで"this is app"、デバッグコンソールに"hoge"と表示されればここまでOK

http://localhost:8080/

前段その2小さいvueコンポーネント

build/index.html書き換え

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>sample</title>
</head>
<body>
<app></app>
<script type="text/javascript" src="js/app.js" charset="utf-8"></script>
</body>
</html>

src/assets/js/app.js書き換え

import Vue from 'vue'
import App from '../../views/app'

new Vue({
    el: 'body',
    components: { App }
});

src/views/app/index.js

export default {
    template: require('./template.html')
}

src/views/app/template.html

<div>this is app view</div>

http://localhost:8080/にthis is app viewと表示されていればOK

小さいvuexサンプル(vuexさん公式のcounterサンプルを丸パクリします)

src/views/app/index.js書き換え

import Counter from '../../components/counter'

export default {
    template: require('./template.html'),
    components: {
        "counter": Counter
    }
}

src/views/app/template.html書き換え

<counter></counter>

src/components/counter/index.js

import store from '../../assets/js/store'

export default {
    template: require('./template.html'),
    computed: {
        count () {
            return store.state.count
        }
    },
    methods: store.actions
}

src/components/counter/template.html

<div>
    Clicked: {{ count }} times
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="incrementIfOdd">Increment if odd</button>
    <button @click="incrementAsync">Increment async</button>
</div>

src/assets/js/store/index.js

 import Vue from 'vue'
 import Vuex from 'vuex'

 Vue.use(Vuex);

 // mutation types
 // optional if you don't like constants.
 const INCREMENT = 'INCREMENT';
 const DECREMENT = 'DECREMENT';

 // root state object.
 // each Vuex instance is just a single state tree.
 const state = {
     count: 0
 };

 // actions are what components will be able to
 // call as store.actions.xxx
 // note these are not the final functions the
 // components will be calling.
 const actions = {

     // for simple actions that just dispatches a single mutation,
     // we can just provide the mutation type.
     increment: INCREMENT,
     decrement: DECREMENT,

     // for a normal action function, it always recieves the store
     // instance as the first argument, from which we can get the
     // dispatch function and the state object. Any additional
     // arguments will follow the store argument.
     incrementIfOdd: ({ dispatch, state }) => {
         if ((state.count + 1) % 2 === 0) {
             dispatch(INCREMENT)
         }
     },

     // Same thing for async actions.
     incrementAsync: ({ dispatch }) => {
         setTimeout(() => {
             dispatch(INCREMENT)
         }, 1000)
     }
 };

 // mutations are operations that actually mutates the state.
 // each mutation handler gets the entire state tree as the
 // first argument, followed by additional payload arguments.
 // mutations must be synchronous and can be recorded by middlewares
 // for debugging purposes.
 const mutations = {
     [INCREMENT] (state) {
         state.count++
     },
     [DECREMENT] (state) {
         state.count--
     }
 };

 // A Vuex instance is created by combining the state, the actions,
 // and the mutations. Because the actions and mutations are just
 // functions that do not depend on the instance itself, they can
 // be easily tested or even hot-reloaded (see counter-hot example).
 // 
 // You can also provide middlewares, which is just an array of
 // objects containing some hooks to be called at initialization
 // and after each mutation.
 export default new Vuex.Store({
     state,
     actions,
     mutations
 })

http://localhost:8080/にカウンターが表示されていればOK

最小テスト

test/unit/store-test.js

 import assert from 'power-assert';

 import store from '../../src/assets/js/store'

 describe('storeのテスト', () => {

     it('mutationsのincrement処理を実行するとstateのcountが増える', () => {
         let before = store.state.count;
         store.actions.increment();
         let after = store.state.count;
         assert(before < after);
     });

     it('mutationsのdecrement処理を実行するとstateのcountが減る', () => {
         let before = store.state.count;
         store.actions.decrement();
         let after = store.state.count;
         assert(before > after);
     });

 });

npm testで実行して結果が表示されればOK

assert(before === after)などに書き換えてエラーを出してみるのもよいかも