Vue.js 単一ファイルコンポーネントを使って開発環境を強化する

最終更新日
2017.11.16

Vue.js はコンパイルせずにそのままブラウザで利用できるのも特徴だけどやっぱり .vue ファイルを使いたい!

単一ファイルコンポーネント

単一ファイルコンポーネントはコンポーネント毎に .vue 拡張子のファイルに分けて作成して Webpack などで一つのスクリプトファイルにバンドルします。.vue ファイルの中にはコンポーネントの JavaScript / HTML / CSS をまとめて書く事ができます。

child-comp.vue

<template>
<div id="example">
  <span>{{ text }}</span>
</div>
</template>
<script>
export default {
  data() {
    return {
      text: 'example'
    }
  }
}
</script>
<style scoped>
span { color: #ffbb00; }
</style>

このファイルを importrequire で読み込んで components に登録します。

main.js

import childComp from 'child-comp.vue'
new Vue({
  el: '#app',
  components: {
    childComp
  }
})
// グローバルもOK
Vue.component(childComp)

これは Vue の開発もだいぶ捗りそう(•ө•)ノ

scoped CSS

スタイルシート定義に scoped を設定しておくとその .vue ファイルで使っている HTML 要素とスタイルシートレセクタに data-v-xxxxxx というユニークな属性が付き自動的に紐付けられる。自分でクラスを付けなくてもよくなり他のスタイルと競合する事が無くなるので基本的には scoped で書くといい。上の例だとコンポーネント内の span タグだけ文字色が変わる。なおコンポーネントのルートタグには親と子の両方の ID が付くのでどちらに CSS を書いても大丈夫。

外部ファイルを読み込むこともできる

長くなる場合は .vue ファイルから外部ファイルを読み込む事ができる。スタイルシートファイルは複数読み込み可能。

child-comp.vue

<template src="./template.html"></template>
<script src="./script.js"></script>
<style src="./style1.scss" lang="scss"></style>
<style src="./style2.scss" lang="scss" scoped></style>

Vue-cliを使う場合

Vue-cli は Vue プロジェクトの開発環境を自動的にセットアップするコマンドラインインターフェースです。予め Node.jsnpm のインストールと基礎知識が必要。

ややこしい Webpack や ホットリロードの設定ファイルを作成してくれてコードを書き始める前から悩まずに済みます。単一ページ構成のサイトや SPA サイトを最初から作るならこれを使うとサクッと始められます。

npm install -g vue-cli
vue init (template-name) (project-name)
cd (project-name)
npm install

template-name

プロジェクトの雛形です。色々なテンプレートが用意されていますが私は webpack-simplewebpack をよく使っています。基本的には webpack-simple で十分だしシンプルなので設定構成を追いやすい。

手軽に試す webpack-simple の init

npm install -g vue-cli
vue init webpack-simple vue-test
cd vue-test
npm install

1行目から説明すると vue-cli をインストールします。次に Vue プロジェクトを webpack-simple テンプレートを使って展開します。次にプロジェクトのディレクトリに移動します。次に package.json に記述されている必要なモジュール群をインストールします。

インストールが終わったら以下のコマンドで Dev サーバーを起動してみましょう。

npm run dev

自動的にブラウザが開きます。このサーバーが立ち上がっている間はホットリロードが有効なのでファイルを編集するとブラウザリロードしなくても編集した部分だけが更新されます。このホットリロード機能が物凄い便利なのです。

webpack テンプレートの方はデフォルトで CSS の抽出と JavaScript をチャンク化してビルドします。自動テストを使いたかったりコードが大きくなるような場合こちらを選択すると楽でしょう。インストール時に ESLint や テストモジュールを使うかなどいくつか質問されます。ESLint は便利ですがエディタで既に使用している場合があると思うので好みで。

開発時のAPIのパス統一やクロスドメイン対策

開発サーバーの API のパスは /local/api だけど 本番は /main/api なんだよね~というパターンがある。

開発時の Dev サーバーのホスト名はデフォルトだと localhost:8080 になりますが、パスを変えたり別のドメインにある API からデータを取得するにはプロキシを設定するといい。

config/index.js (webpack-simple の場合は webpack.config.js)

proxyTable: {
  '/main/api': {
    target: 'http://dev.example.com',
    changeOrigin: true,
    pathRewrite: {
      '^/main/api': '/local/api'
    }
  }
}

これで http://dev.example.com/main/api というパスで /local/api にアクセスできるようになります。

開発モードでソースマップを使用する

ソースマップは JavaScript や CSS の圧縮前どこに書いてあったか確認できるコードを添付します。開発中はほぼ必須だけど、少々バグがあるようでデフォルトではオフになっています。

https://github.com/webpack-contrib/css-loader#sourcemap

特に問題なければ有効にしておくといい。

webpack

config/index.js 一番下

cssSourceMap: true

webpack-simple

webpack.config.js

{
  test: /\.vue$/,
  loader: 'vue-loader',
  options: {
    loaders: {
      'scss': process.env.NODE_ENV === 'production' ? 
        'vue-style-loader!css-loader!sass-loader' : 
        'vue-style-loader!css-loader?sourceMap!sass-loader'
    }
    // other vue-loader options go here
  }
}

webpack-simple で開発モードだけソースマップをオンにする

webpack-simple はプロダクトモードでソースマップがオンになっています。ビルド後は基本要らないので開発モードだけオンにします。

if (process.env.NODE_ENV === 'production') {
  // コメントアウトする
  // module.exports.devtool = '#source-map'
  // http://vue-loader.vuejs.org/en/workflow/production.html
  module.exports.plugins = (module.exports.plugins || []).concat([
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      sourceMap: false,
      compress: {
        warnings: false
      }
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true
    })
  ])
} else {
  // else で開発モードの設定を追加
  module.exports.devtool = '#eval-source-map'
  module.exports.plugins = (module.exports.plugins || []).concat([
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"development"'
      }
    })
  ])
}

package.json インストールする場所

package.json にはプロジェクトの情報や使用するモジュールとバージョンなどが書かれています。

--save (-S) でインストールすると package.json の dependencies--save-dev (-D) でインストールすると devDependencies に追加されます。

基本は全部 devDependencies でも大丈夫なんですけど、開発環境として使うものは -D で、実際にアプリで使うもの同梱するものは -S でインストールするのがセオリーみたいです。モジュールはコマンドラインからインストール・アンインストールして package.json を直接編集するのはなるべく避けるといい。

非SPAサイトでホットリロード機能を使う

Vue-cli のホットリロード最高かよ! Vue-cli を使ったサーバーでの開発では XAMPP などで作っているファイルは表示できませんがそんな非 SPA サイトでもホットリロードを使う記事も書きました。こちらから

Gulp+Babel+Webpack を使う場合

SPA じゃない場合は Gulp でやりたい場合もあります。モジュール化させたファイルを Webpack にまとめてもらい Babel でブラウザ用に変換して Gulp に編集をウォッチしてもらう事にします。自分で Webpack の設定とかしないといけないので Vue-cli を使う場合よりも初期設定がやや面倒くさい。

必要なモジュール

とりあえず Webpack と vue-loader というモジュールを使います。

  • vue-html-loader
  • vue-template-compiler(vueと同じバージョン)
  • css-loader
  • sass-loader(SASS/SCSS を使う場合)
  • node-sass(同上)
  • babel-loader
  • babel-preset-es2015

上記の開発に必要なモジュールを --save-dev を付けてインストールします。実際にアプリで使うライブラリの場合は --save でインストールします。

もし何か足りなければそんな感じのエラーが出ます。英語ですが何が原因なのかちゃんと書いてあるのでコピペしてGoogle翻訳すると良いです。

gulpfile.js を書く

var gulp = require('gulp');
var webpack = require('webpack');

Webpack 自体凄い沢山設定があって覚えるの大変そうですね(´•ω•`)

var webpackBuild = function (opt) {
    var plugins = [
        new webpack.optimize.UglifyJsPlugin({
            sourceMap: opt.map ? true : false,
            compress: {
                warnings: false
            },
        }),
    ];
    webpack({
        entry: './src/js/main.js',
        output: {
            filename: 'bundle.js',
            path: "./dist/js/",
        },
        plugins: opt.min ? plugins : [],
        module: {
            rules: [{
                    test: /\.vue$/,
                    exclude: /node_modules/,
                    loader: 'vue-loader',
                    options: {
                        loaders: {
                            scss: 'vue-style-loader!css-loader!sass-loader?includePaths[]=./src/sass/'
                        }
                    }
                },
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    loader: 'babel-loader',
                }
            ]
        },
        devtool: opt.map ? 'source-map' : '',
        resolve: {
            extensions: ['.js', '.vue']
        }
    }, function (error, stats) {});
}
gulp.task('webpack.watch', function() {
    webpackBuild({ map: false, min: false });
});

.vue ファイルの中で SCSS の使用と変数定義用のファイルを @import をできるようにしました。

watch タスクに登録。

gulp.task('watch', function() {
  gulp.watch(['./src/**/*.js', './src/**/*.vue', './src/**/*.scss'], ['webpack.watch']);
});

CSSをファイルに抽出する

Webpack テンプレートの設定を参考にして、.vue の中の CSS コードを抽出してファイルに吐くようにしてみました。vue-loader のページにも載っています

var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')

var webpackBuild = function (env) {
  var webpackBaseConfig = {
    cache: true,
    devtool: 'source-map',
    entry: {
      app: './src/main.js',
      /* 自分のコード以外をまとめる */
      vendor: ['vue', 'axios', 'es6-promise']
    },
    output: {
      path: './dist',
      filename: '[name].js',
      publicPath: 'dist'
    },
    resolve: {
      extensions: ['.js', '.vue'],
      alias: {
        '@': path.resolve('src'),
        'vue$': 'vue/dist/vue.common.js'
      }
    },
    module: {
      rules: [
        {
          test: /\.vue$/,
          loader: 'vue-loader',
          include: [path.resolve('src')],
          options: {
            /* vue-loader のオプション CSSを抽出する */
            loaders: {
              'css': ExtractTextPlugin.extract({
                use: ['css-loader'],
                fallback: 'vue-style-loader',
              }),
              'scss': ExtractTextPlugin.extract({
                use: [{
                  loader: 'css-loader'
                },
                {
                  loader: 'sass-loader',
                  options: {
                    includePaths: [path.resolve('src/sass')]
                  }
                }],
                fallback: 'vue-style-loader',
              })
            },
          }
        },
        {
          test: /\.js$/,
          loader: 'babel-loader',
          include: [path.resolve('src')],
        },
      ]
    },
    plugins: [
      new webpack.DefinePlugin({
        'process.env': {
          'NODE_ENV': JSON.stringify(env)
        }
      }),
      /* ライブラリを共有ファイルにする */
      new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor',
        filename: 'vendor.bundle.js',
        minChunks: Infinity,
      }),
      /* JavaScriptを圧縮する */
      new webpack.optimize.UglifyJsPlugin({
        /* ソースマップがある場合はここでも指定 */
        sourceMap: true,
        compress: {
          warnings: false
        },
      }),
      /* CSSを抽出する */
      new ExtractTextPlugin({
        filename: './css/app.bundle.css'
      }),
      /* ローダー用オプション ロードファイルの改行スペース省くだけ? ↓かどっちか */
      /*new webpack.LoaderOptionsPlugin({
        minimize: true,
      }),*/
      /* CSSの最適化 */
      /*new OptimizeCSSPlugin({
        cssProcessorOptions: {
          safe: true,
        },
      }),*/
    ],
  }
  webpack(webpackBaseConfig, function (error, stats) {});
};

gulp.task('webpack.build', function () {
  webpackBuild('production');
});
gulp.task('webpack.dev', function () {
  webpackBuild('development');
});

ちょっと遅いのでやはりホットリロードのありがたみを感じる。

おわりに

正直 Vue-cli が非常にお手軽なので特に事情がなければ Vue-cli を使用するのが楽ですが、公式では Node.js や npm を使った事が無いというような場合は気軽に Vue-cli を使うのはあまり推奨されていない。予め基礎知識を備えてから使うと良いかも。

とりあえず今まで何も考えずに jQuery で作っていた所はプレーンな JavaScript や Vue コンポーネントにしました。Vue は導入する場合全部作りなおさないとダメというわけじゃなくて、すこしづつ移行できるっていうのもいいですねー(๑'ᴗ'๑)

Vue を使い始めると DOM に直接アクセスする事は大分減りますが、それでも body タグとか Vue アプリの範囲外を操作したい時があります。標準関数だとセレクタ操作は結構面倒くさいので、jQuery よりコンパクトな Selector というセレクタ処理に特化したライブラリを Vue と併用するのも良さそう。(ちょっとした DOM 操作のためだけに読み込むには jQuery は少々サイズが大きいので。)

さて、前回親子間のコンポーネントのやりとりをやったので、次回は非親子間のコンポーネントのやりとりについてやります。