CSS
vue.js
stylelint

Vue.jsでSPAサイトを作成するチュートリアル【4. CSS編】

Vue.jsを使い始めていろいろできることが多くなってきたので、整理する意味も兼ねてチュートリアルにまとめます。
今回はコーポレートサイトを想定して作成していきます。
※記事が長くなったのでチュートリアルを分割しました。

目次

前提

  • タスクはnpm scriptsで一限管理
  • コマンドはyarnを使用
  • vue-cliwebpack-simpleを使用
  • CSSはSCSSを使用し用途に合わせてPostCSSを使用

バージョン

  • "vue": "^2.5.11"
  • "webpack": "^3.6.0"
  • "node-sass": "^4.7.2"
  • "postcss": "^6.0.16"
  • "stylelint": "^8.4.0"

CSSの環境構築

SassとPostCSSを使用するので以下のコマンドでパッケージを追加します。
※今回はautoprefixerでPostcSSの動作確認をおこないます。

$ yarn add -D node-sass postcss postcss-loader autoprefixer

PostCSSの設定ファイルを追加します。

postcss.config.js
module.exports = (ctx) => ({
  plugins: [
    require('autoprefixer')
  ]
});

webpackの設定ファイルを修正します。

webpack.config.js
var path = require('path')
var webpack = require('webpack')

module.exports = {
  // 省略
  module: {
    rules: [
    // 省略
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            'scss': [
              'vue-style-loader',
              'css-loader',
              'postcss-loader',  // ← 追加
              'sass-loader'
            ],
// 省略

Index.scssとAbout.scssで動作確認用のスタイルを記述します。

/src/pages/Index/Index.scss
.block {
  color: red;
  display: flex;
}

/src/pages/About/About.scss
.block {
  color: blue;
  display: flex;
}

ブラウザでindexとaboutをそれぞれ確認し、開発ツールでdisplayがプレフィックスがついていて、それぞれ赤文字、青文字になっていたら成功です。

ちなみに、前回vueファイル側でstyle部分にscopedを記述していました。

/src/pages/Index/Index.vue
<template
  lang="pug"
  src="./Index.pug"
/>

<script src="./Index.js" />

<style
  lang="scss"
  src="./Index.scss"
  scoped
/>

ページ単位では上書きされないようにローカルなスタイルにする意味でscopedを記述していました。
上書きさせたい場合、scopedを記述しなければ通常の記述になります。

参考:スコープ付き CSS

グローバルなCSSの環境構築

上記のCSSの設定はvueコンポーネントごとの個別用で使用し、全体で使用するスタイルは別で用意します。
※reset.css, normarize.css, 各共通コンポーネント等です。
CSSディレクトリを作成し、CSS設計をおこないます(以下は参考)

/src
  /css
    /base
      _variable.scss
      _mixin.scss
      _function.scss
      _base.scss
    /components
      _typography.scss
      _button.scss
      _icon.scss
      _form.scss
      _table.scss
    /modules
      _header.scss
      _footer.scss
    /layouts
      _container.scss
    /templates
      _temp-list.scss
    _temp-article.scss
    /vendor
      _normarize.scss
    style.sscss
  /layouts
    Default.vue
  /pages
    Index.vue
    Hoge.vue
  App.vue
  main.js
  router.js
  index.html
  その他設定ファイル等

cssファイルを読み込む記述を追加します。

/src/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>vue-skeleton</title>
    <link rel="stylesheet" href="/css/style.css"> <!-- ← 追加 -->
  </head>
  <body>
    <div id="app"></div>
    <script src="/js/bundle.js"></script>
  </body>
</html>

normalize.scssをダウンロードして以下に格納します。

/src/css/vendor/

グローバルなSCSSはwebpackとは別で監視したほうがwebpackに依存しなくて良いので、node-sassを使って監視します。

package.json
{
  // 省略
  "scripts": {
    "watch:server": "browser-sync start --config bs-config.js",
    "watch:file": "cpx \"./src/**/{*.html,*.jpg,*.png,*.gif,*.svg,*.eot,*.ttf,*.woff,package.json}\" ./dist",
    // ↓ 追加
    "watch:css": "node-sass src/css/style.scss dist/css/style.css -w --source-map true",
    // ↑ 追加
    "watch:js": "webpack -w",
    "start": "run-p watch:*",
    // 省略
  },
  // 省略
}

vue-body-classの追加

CSS設計をおこなう際に、ページごとにスタイルをテンプレートとして出し分けたい場合があります。
そんな時にbodyにページごとのclassを振って出し分けるのですが、vue-routerはrouter-view部分の差分変更をおこなうので、基本的にはbodyの操作をしないようになっていると思います。
そんな時にrouter-viewでpathの変更ごとにbodyのclass設定をおこなえるようにしたのがvue-body-classです。
さっそく追加しましょう。

$ yarn add vue-body-class

main.jsに追記しましょう。
この時、Vue.use(BodyClass, router)const router = ...の後に記述しないときちんと反応しないので気をつけましょう。

/src/main.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import Routes from './router.js'
import BodyClass from 'vue-body-class' // ← 追記

Vue.use(VueRouter)

const router = new VueRouter({
  mode: 'history',
  routes: Routes
})

Vue.use(BodyClass, router) // ← 追記

const app = new Vue({
  router,
  render:h => h(App)
}).$mount('#app');

次にrouter.jsに実際に各ページに付与するclass名を追記していきましょう。

/src/router.js
export default [
  {
    path: '/',
    name: '',
    component: require('./layouts/Default.vue').default,
    children: [
      {
        path: '',
        name: 'index',
        meta: { bodyClass: 'page-index' }, // ← 追記
        component: require('./pages/Index/Index.vue').default,
      },
      {
        path: '/about/',
        name: 'about',
        meta: { bodyClass: 'page-about' }, // ← 追記
        component: require('./pages/About/About.vue').default
      },
      {
        path: '/service/',
        name: 'service',
        meta: { bodyClass: 'page-service' }, // ← 追記
        component: require('./pages/Service/Service.vue').default
      },
      {
        path: '/recruite/',
        name: 'recruite',
        meta: { bodyClass: 'page-recruite' }, // ← 追記
        component: require('./pages/Recruite/Recruite.vue').default
      },
      {
        path: '/contact/',
        name: 'contact',
        meta: { bodyClass: 'page-contact' }, // ← 追記
        component: require('./pages/Contact/Contact.vue').default
      }
    ]
  }
]

ブラウザで確認してページごとにbodyのclassが変更されていれば成功です。
ちなみに、親で付与したclassを子に引き継いだり上書きしたり、ということもできます。
詳しくは公式ドキュメントを参考にしてください。

参考:vue-body-class

stylelintの設定

グローバルなCSSとwebpack環境下のCSS両方にstylelintを設定します。

パッケージを追加します。

yarn add -D stylelint stylelint-webpack-plugin

stylelintの設定ファイルを用意します。
今回は以下を/srcに格納します。

stylelintの設定ファイル

エディタで監視する設定をおこないます。
以下を参考におこなってみてください。

PostCSSとstylelintの環境構築

グローバルなCSS

他所から持ってきたSCSSファイルにstylelintを反応させたくないので、.stylelintignoreを/src直下に作成して以下を記述します。

/src/css/vendor/**/*.scss

/src/cssのscssファイルでエラーや警告表示になる記述をして、stylelintがエディタで反応したら成功です。

webpack環境下のCSS

webpackの設定ファイルに追記します。

/src/webpack.config.js
// 省略
var StylelintPlugin = require('stylelint-webpack-plugin')

module.exports = {
  // 省略
  plugins: [
    new StylelintPlugin({
      files: ['**/*.vue', '**/*.scss']
    })
  ],
  // 省略
}

参考:A nice way to lint .vue files with Stylelint? #303

Index.vueやAbout.scssでstylelintがエディタで反応していたら成功です。
エラー・警告が出ている場所を確認して修正しましょう。

postcss-sortingの追加

プロパティ順をソートできるようにしましょう。
以下の記事を参考に導入しましょう。scssファイルでもいけます。

参考:PostCSS SortingでCSSの@ルールやプロパティの記述順を整理

まとめ

CSSは設定しやすくハンドリングが難しいので、設計がとても大事です。
scopedはどれに適用させるのか、共通コンポーネントのスタイルはどこに書くべきか、等の判断も必要になってくるかと思います。
全体を俯瞰し、破綻しにくく運用しやすい設計を意識すると良いでしょう。