クロコめも2。

ただのめもー

vueプロジェクトセットアップ2016/4/14版

2ヶ月も前のやり方でbuildが通るわけないじゃない。

javascriptおそるべし

今回の改良ポイント

  • 以前のままだとbuildできない箇所の修正
  • vueの公式でおすすめのテスト構成(karma,jasmine)でテストを作る
  • ちょっとまともなtestを用意する 簡易コンポーネントを用意してそのテストをする

各モジュールバージョン

  • node: v5.10.1
  • npm:3.8.6
  • babel-core:6.7.6
  • vue:1.0.21

まずテストを作る前準備(小さいvueプロジェクト)

setup directory and node_modules

mkdir -p {build,test/{unit,e2e},src/{components,views/app,filters,directives,assets/{js,style,images,html}}}
npm init -y
npm i -g webpack webpack-dev-server babel-core babel-loader node-sass
npm i -S vue vuex
npm i -D babel-core babel-loader webpack style-loader css-loader node-sass sass-loader html-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 lodash
npm i -D extract-text-webpack-plugin html-webpack-plugin@2
npm i -D node-bourbon node-neat

.babelrc

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

webpack.config.js

var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');

var bourbon = require('node-bourbon').includePaths;
var neat = require('node-neat').includePaths;

var contentBase = __dirname + '/build';
var cssLoader = ExtractTextPlugin.extract('style', 'css?sourceMap!sass')

var htmlWebpackPlugin = new HtmlWebpackPlugin({
    filename: 'index.html',
    template: 'src/assets/html/index.template.html'
});

var pkg = require('./package.json');
var vendor = Object.keys(pkg.dependencies);

var options = {
    sassLoader: {
        includePaths: [bourbon, neat]
    },
    entry: {
        app: ["./src/assets/js/app.js", "./src/assets/style/app.scss"],
        vendor: vendor,
    },
    output: {
        path: contentBase + '/',
        filename: "js/[name].js",
        contentBase: contentBase + '/build'
    },
    resolve: {
        alias: {
            vue: "vue/dist/vue",
            lodash: "lodash"
        },
        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: ExtractTextPlugin.extract('style', 'css?sourceMap!sass')
            },
            {
                test: /\.html$/,
                loader: "html"
            },
            {
                test: /\.js(x?)$/,
                loader: 'babel-loader?sourceMaps=true'
            }
        ]
    },
    plugins: [
        htmlWebpackPlugin,
        new ExtractTextPlugin('./css/app.css', {disable: false}),
        new webpack.optimize.CommonsChunkPlugin('vendor', 'js/vendor.js')
    ]
};

if (process.env.NODE_ENV === 'production') {
    var defplugin = new webpack.DefinePlugin({
        'process.env': {
            NODE_ENV: '"production"'
        }
    });
    var minplugin = new webpack.optimize.UglifyJsPlugin({
        compress: {
            warnings: false
        }
    });
    options.plugins.push(defplugin);
    options.plugins.push(minplugin);
    options.plugins.push(new webpack.optimize.OccurenceOrderPlugin());
}

module.exports = options;

package.json(script property)

  "scripts": {
    "hot": "webpack-dev-server -d --inline",
    "build": "NODE_ENV=production webpack --progress --hide-modules",
    "test": "karma start karma.conf.js",
  },

src/assets/html/index.template.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>タイトルを入れてね</title>
</head>
<body>
<app><!-- ここにAPPが入ります --></app>
</body>
</html>

src/assets/js/app.js

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

require('../style/app.scss');

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

src/assets/style/app.scss

@import "~bourbon/app/assets/stylesheets/bourbon";
@import "~bourbon-neat/app/assets/stylesheets/neat";


body {
  @include outer-container();
  background-color: #000000;
  color: yellow;
  .header {
    @include span-columns(12);
    background-color: #999999;
  }
  .navigator {
    @include span-columns(5);
    background-color: #333333;
  }
  .footer {
    @include span-columns(12);
    background-color: #666666;
  }
}

src/views/app/index.js

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

src/views/app/template.html

<div class="header">ヘッダー</div>
<div class="navigator">ナビゲーター</div>
<div class="contents">コンテンツ</div>
<div class="footer">フッター</div>

ここでnpm run hotを実行してみる。

ログを見てエラーが出ずにwebpack: bundle is now VALID.と表示されたら↓URLで確認

http://localhost:8080/

CSSが効いてれば多分問題なし。

停止するときはctrl+c

ついでにnpm run buildでビルドして、buildディレクトリに吐き出されるソースもちょろっと確認

テストを作る

npmまわり準備

npm i -D function-bind jasmine-core karma karma-jasmine karma-phantomjs-launcher karma-spec-reporter karma-webpack phantomjs phantomjs-polyfill phantomjs-prebuilt

やろうとしていること

components/navigator/index.js

export default {
    template: require("./template.html"),
    data() {
        return {
            'calcResult': 0,
            'value1': 0,
            'value2': 0
        }
    },
    methods: {
        init(value1, value2) {
            this.value1 = value1;
            this.value2 = value2;
        },
        doCalc() {
            this.calcResult = Number(this.value1) + Number(this.value2);
        }
    }
}

components/navigator/template.html

<div class="navigator">
    <input type="number" v-model="value1" />
    <span> + </span>
    <input type="number" v-model="value2" />
    <button @click="doCalc">calc</button>
    <span class="result" v-text="calcResult"></span>
</div>

views/app/index.js 書き換え

import Navigator from '../../components/navigator'
export default {
    template: require("./template.html"),
    components: {
        Navigator
    }
}

views/app/template.html 書き換え

<div class="header">ヘッダー</div>
<navigator></navigator>
<div class="contents">コンテンツ</div>
<div class="footer">フッター</div>

karma.conf.js

// webpackの設定でテストするのに都合の悪い部分を上書き ここから
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var htmlWebpackPlugin = new HtmlWebpackPlugin({
    filename: 'index.html',
    template: 'src/assets/html/index.template.html'
});

var webpackConf = require('./webpack.config.js');
delete webpackConf.entry;
webpackConf.plugins =  [
    htmlWebpackPlugin,
    new ExtractTextPlugin('./css/app.css', {disable: false})
];
// webpackの設定でテストするのに都合の悪い部分を上書き ここまで

module.exports = function(config) {
    config.set({

        // base path that will be used to resolve all patterns (eg. files, exclude)
        basePath: './',


        // frameworks to use
        // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
        frameworks: ['jasmine'],


        // list of files / patterns to load in the browser
        files: [
            './test/unit/index.js'
        ],

        proxies: {
            // 静的なファイルで必要なものはここに登録
            //'/assets/images/hoge.svg': './src/assets/images/hoge.svg'
        },

        // list of files to exclude
        exclude: [
        ],


        // preprocess matching files before serving them to the browser
        // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
        preprocessors: {
            'test/unit/index.js': ['webpack'],
            'src/**/*.js': ['webpack'],
        },

        webpack: webpackConf,

        webpackMiddleware: {
            noInfo: true
        },

        // test results reporter to use
        // possible values: 'dots', 'progress'
        // available reporters: https://npmjs.org/browse/keyword/karma-reporter
        reporters: ['spec'],


        // web server port
        port: 9876,


        // enable / disable colors in the output (reporters and logs)
        colors: true,


        // level of logging
        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
        logLevel: config.LOG_INFO,


        // enable / disable watching file and executing tests whenever any file changes
        autoWatch: true,


        // start these browsers
        // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
        browsers: ['PhantomJS'],


        // Continuous Integration mode
        // if true, Karma captures browsers, runs the tests and exits
        singleRun: true,

        // Concurrency level
        // how many browser should be started simultaneous
        concurrency: Infinity

    })
};

webpackの設定の書き換えはpluginsのCommonsChunkPluginが絡んでくるとkarmaちゃんが動いてくれなくなっちゃうから

何かもっと良い解決方法があるといいな

test/unit/index.js

Function.prototype.bind = require('function-bind');

var testsContext = require.context('.', true, /\.spec$/);
testsContext.keys().forEach(testsContext);

test/unit/navigator.spec.js

import Vue from 'vue';
import Navigator from 'components/navigator';


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

    Vue.config.debug = true;
    Vue.options.replace = false;

    const vm = new Vue({
        template: '<div><navigator></navigator></div>',
        components: { Navigator }
    }).$mount();

    it('test sample', () => {
        expect(vm.$el.querySelector('.result').textContent).toBe('0');
    });

    it ('calc test', (done) => {
        vm.$children[0].init(100, 50);
        vm.$children[0].doCalc();
        expect(vm.$children[0].$data.calcResult).toBe(150);
        // まだ反映していないはず
        expect(vm.$el.querySelector('.result').textContent).toBe('0');
        vm.$nextTick(() => {
            // 反映されるはず
            expect(vm.$el.querySelector('.result').textContent).toBe('150');
            done();
        })
    });

});

ここで、npm run testもしくはnpm testを実行してみる。

navigatorのテスト
    ✓ test sample
    ✓ calc test

PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 2 of 2 SUCCESS (0.003 secs / 0.003 secs)

このようなログがでたら成功。