序章
管理可能で拡張性のあるCSSを構築するために、先人たちは数々のCSS設計や命名規約を生み出しました。
OOCSS、SMACSS、BEM、SuitCSS...
そしてそれらの考えを統合したFLOCSSが登場しました。
FLOCSSは強力なツールです。
しかし、悲しいことに強力すぎて誰も使いこなすことができなかったのです。
「これってComponentとProjectどっちだ?適当でいいか」
「最初に作った人はFLOCSSを理解していたかもしれないが、あとから保守した人が理解していなくてめちゃくちゃになった」
「サーバーエンジニアなのに急にフロントを書かないといけなくなった。FLOCSS? 覚えること多すぎて無理!」
この状況から脱出するための新しい技術が生まれました。
単一ファイルコンポーネントです。
単一ファイルコンポーネントって?
こういうやつです。
<template>
<p>{{ greeting }} World!</p>
</template>
<script>
module.export = {
data: function() {
return {
greeting: 'Hello'
}
}
}
</script>
<style>
p {
font-size: 2em;
text-align: center;
}
</style>
ようは、template(html)、script(js), style(css)を一つのファイルにまとめて、それを使いまわそうということです。
これの何が嬉しいのかはvueの公式から引用させていただきます。
注意すべき重要な点の1つは、関心事項の分離がファイルタイプの分離と等しくないことです。 現代の UI 開発では、コードベースを互いに織り交ぜる3つの巨大なレイヤーに分割するのではなく、それらを疎結合なコンポーネントに分割して構成する方がはるかに理にかなっています。コンポーネントの内部では、そのテンプレート、ロジック、スタイルが本質的に結合されており、実際にそれらを配置することで、コンポーネントがより一貫性と保守性に優れています。
名前が長いので以下、単一ファイルコンポーネントはSFC(Single File Components)と呼びます。
SFCってどうやったら使えるの?
vue.js
vue.jsを使ったプロジェクトであればvue-loader
を使えばすぐに使い始めることができます。
詳しくは公式を参照してください。
Riot.js
RiotはSFCが前提なっているため、普通に使っていけば自然にSFCになるはずです。
React
TODO
フレームワークなし
こちら(webpack-component-loader)が使えそうです。
新しいSFC CSS設計、その名はKaom(仮)
これから説明するSFC CSS設計を**Kaom(仮)**と名付けます。
スタイル言語の選択
**Kaom(仮)**にはmixin機能と変数機能、import機能が必須です。
なのでスタイル言語にはこれらの機能があるものを選択しましょう。
逆に言えばこれら機能があればlessでもsassでもstylusでもなんでもいいです。
post-cssを駆使してもOKです。
(指定ディレクトリの中をまとめてimportする機能があるstylusがややオススメ。)
以下はstylusのコードで説明していきます。
ディレクトリ構成
src/
├ components/
│├ TOP.vue
│└ ...
├ styles/
│├ bases/
││├ _body.styl
││└ ...
│├ mixins/
││├ _card.styl
││└ ...
│├ _bases.styl
│├ _mixins.styl
│└ _variables.styl
└─ main.js
components
コンポーネントを配置します。
コンポーネント内のstyleをどのように書くかは後述。
styles
スタイルシートを配置します。
bases
サイト全体の設定を行います。
ここではhtmlタグや属性を指定してスタイルを記述していきます。
クラスやIDを指定してはいけません。
例えば以下のように書きます。
ul, ol
list-style-type: none
margin: 0
padding: 0
dl
margin-bottom: $small-spacing
dt
font-weight: bold
margin-top: $small-spacing
dd
margin: 0
bittersというCSSがあるので、こちらを参考にするといいと思います。
そのままコピーしてきてもいいかも。
mixins
再利用したいスタイルをmixinで定義します。
shadow(depth = 1)
if depth == 1
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12)
else if depth == 2
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, .2), 0 6px 20px 0 rgba(0, 0, 0, .19)
else if depth == 3
box-shadow: 0 12px 15px 0 rgba(0, 0, 0, .24), 0 17px 50px 0 rgba(0, 0, 0, .19)
else if depth == 4
box-shadow: 0 16px 28px 0 rgba(0, 0, 0, .22), 0 25px 55px 0 rgba(0, 0, 0, .21)
else if depth == 5
box-shadow: 0 27px 24px 0 rgba(0, 0, 0, .2), 0 40px 77px 0 rgba(0, 0, 0, .22)
else
box-shadow: none
_bases.styl
basesディレクトリの中を読み込見ます。
stylusの場合、2行だけ、以下のようになります。
@import './_variables.styl'
@import './bases/*'
_mixins.styl
mixinsディレクトリの中を読み込見ます。
stylusの場合、2行だけ、以下のようになります。
@import './_variables.styl'
@import './mixins/*'
_basesと同じですね。
_variables.styl
サイト全体で使用する変数を定義していきます。
こちらもbittersを参考にするとどんなことを書くのかわかると思います。
main.js
エンドポイントとなるファイルです。
次の章で詳しく説明します。
質問: Utilityはどこに置くの?
答え: Utilityを作ってはいけません。
.mbs { margin-bottom: 10px; }
のような汎用クラスはHTML内にスタイル情報を持ち込む悪しき習慣です。
詳しくはこちら(お前ら今すぐそのCSSフレームワーク使うのやめろ!)に書きました。
再利用したいスタイルはmixinを使いましょう。
各ファイルの読み込み方法
webpackを使った場合の各ファイルの読み込み方法です。
//
// styles
//
import 'normalize.css'; // 必要な場合
import './styles/_bases.styl';
エンドポイントなるmain.js
ではnormalize.css
などのリセットCSSと先程説明した_bases.styl
のみ読み込みます。
_variables.styl
や_mixins.styl
は以下のようにします。
# webpack 2.0
path = require('path')
webpack = require('webpack')
module.exports =
entry: './src/main.js'
output:
path: path.resolve(__dirname, './dist')
publicPath: '/dist/'
filename: 'build.js'
module:
rules: [
{
test: /\.vue$/
use:
loader: 'vue-loader'
options:
loaders:
stylus: [
{
loader: 'style-loader'
options: sourceMap: true
}
{
loader: 'css-loader'
options: sourceMap: true
}
{
loader: 'stylus-loader'
options: sourceMap: true
}
{
loader: 'stylus-resources-loader'
options:
resources: [
path.resolve(__dirname, './src/styles/_variables.styl')
path.resolve(__dirname, './src/styles/_mixins.styl')
]
}
]
}
こうすることですべてのコンポーネントから@import
の記述無しで変数とmixinが使えるようになります。
コンポーネントの記述
著者の宗教上の理由により
- framework: vue.js
- template : pug
- script : CoffeeScript2
- style : stylus
をつかって説明させていただきます。
スタイルガイド
Vue.js コンポーネント スタイル ガイドを全面的に採用します。
Riotの場合はRiotJS Style Guideを参照してください。
ファイル名
Vue.js コンポーネント スタイル ガイドに従い、2または3語で命名します。
それをUpperCamelCaseでファイル名にします。例) UserList.vue
コンポーネント内のテンプレートとスタイル
CSS 命名規約
命名の一貫性を保つために命名規約を導入しましょう。
命名規約は色々有りますが、どれを使えばいいでしょうか?
単一ファイルコンポーネントでは、コンポーネントごとにスタイルが独立しているので名前の衝突の危険はありません。
この危険を回避するための意味合いが強いBEMなどは不要です。
rscssがわかりやすく、大げさでなく、使いやすいのでおすすめです。
Kaom's Primacy(仮)
上で「単一ファイルコンポーネントでは、コンポーネントごとにスタイルが独立しているので名前の衝突の危険はありません。」と言いました。
これはscoped
オプションによる力です。
どのような力かこちらvue-loaderから引用して説明します。
<template lang="pug">
.example hi
</template>
<style lang="styl" scoped>
.example
color: red
</style>
これをコンパイルするとこうなります。
<template>
<div class="example" data-v-f3f3eg9>hi</div>
</template>
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>
ここで追加されたdata-v-f3f3eg9
という文字列はコンポーネントごとに固有のものです。
つまり、ランダムな文字列を付与することによって、別のコンポーネントへ影響を与えることを防いでいるのです。
このように自動的にセレクターにランダムな文字列を付与することでスタイルの漏れを防ぐことを**Kaom's Primacy(仮)**と呼ぶことにします。
Kaom's Roots(仮)
templateはコンポーネント名と同じ名前(dash-case)のクラスで全体を囲みましょう。
このクラスを持ったタグを**Kaom's Roots(仮)**と呼ぶことにします。
<template lang="pug">
.user-list //- <- これがKaom's Roots(仮)
h1 hello
</template>
<style lang="styl" scoped>
.user-list
background-color: $theme-color;
</style>
以下のように**Kaom's Roots(仮)**が複数あるのはNGです。
<template lang="pug">
.user-list1
h1 hello
.user-list2
h1 goodbye
</template>
**Kaom's Roots(仮)**はdiv
である必要はありません。
以下のようにheader
, footer
, main
, form
, ul
などを自由に使ってください。
<template lang="pug">
header.main-header
h1 hello
</template>
<style lang="styl" scoped>
.main-header
background-color: $theme-color;
</style>
To be continued...
See also
TODO
-
説明加筆
- 各フレームワークでのSFCの実装方法について
- マジックナンバーの使用について
- 用語の統一
- 例の追加
- 名前をつける(必要なら)