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

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


## 目次

* [表示編](https://qiita.com/buchiya4th/items/cff0b2ab9ebd550c4e0f)
* [ルーター編](https://qiita.com/buchiya4th/items/f154191c8157bbd2239a)
* [コンポーネント編](https://qiita.com/buchiya4th/items/d5a62722f1fc26230df0)
* CSS編 ←イマココ
* [JS編](https://qiita.com/buchiya4th/items/3f3b9c6e48866e6d9d27)
* [ライブラリ編](https://qiita.com/buchiya4th/items/b4814c0018f9311af461)
* [メタ編](https://qiita.com/buchiya4th/items/dd8f16dfcd296bd247bb)
* [ビルド編](https://qiita.com/buchiya4th/items/71649f8fbd2f72b9e6e7)


## 前提

* タスクは`npm scripts`で一限管理
* コマンドは`yarn`を使用
* `vue-cli`の`webpack-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の動作確認をおこないます。

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

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

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

```

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

```js: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で動作確認用のスタイルを記述します。

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

```

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

```

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

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

```html:/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](https://vue-loader.vuejs.org/ja/features/scoped-css.html)


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

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

```txt
/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ファイルを読み込む記述を追加します。

```html:/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](https://github.com/hail2u/normalize.scss)をダウンロードして以下に格納します。

```
/src/css/vendor/
```

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

```js: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です。
さっそく追加しましょう。

```bash
$ yarn add vue-body-class
```

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

```js:/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名を追記していきましょう。

```js:/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](https://www.npmjs.com/package/vue-body-class)


## stylelintの設定

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

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

```bash
yarn add -D stylelint stylelint-webpack-plugin
```

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

[stylelintの設定ファイル](https://gist.github.com/buchiya4th/f4ca1be2ab98ee5a8098fa68a93e752c)

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

[PostCSSとstylelintの環境構築](https://qiita.com/buchiya4th/items/01b4ad050b7c59b48539#%E3%82%A8%E3%83%87%E3%82%A3%E3%82%BF%E3%81%A7%E7%9B%A3%E8%A6%96)

### グローバルなCSS

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

```txt
/src/css/vendor/**/*.scss
```

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


### webpack環境下のCSS

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

```js:/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](https://github.com/vuejs/vue-loader/issues/303)

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


## postcss-sortingの追加

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

参考：[PostCSS SortingでCSSの@ルールやプロパティの記述順を整理](https://qiita.com/buchiya4th/items/e31443de593e48686c5c)


## まとめ

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