SvelteでCSSinJS(emotion)使ってみた

時間がない人は一番下の「使った感想」までジャンプ！

# これはなに
Svelteでemotion(CSSinJS)を試したので備忘録

## CSSinJSってなに
CSSをJSで書きますよ〜という概念。
詳しいメリットは後述しますが、ざっくりいえば下記な点より支持されています。

- スコープのないCSSにスコープを付与できる
- 変数・関数がJSとCSSで同じものが使える

主にスタイルにスコープを持てないReactで使われています。
emotion,styled-compornentなど、ライブラリも豊富です。

書いたコードはインラインやCSSファイルとしては吐き出されず、DOMのトップに直接追加されます。

<img width="474" alt="スクリーンショット 2020-03-13 12.38.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321220/ca1aade4-920c-f61c-7388-fe6e099dbfb5.png">

## メリットざっくり
- インラインではなく、実際のCSSを生成する
- SCSSより軽いらしい ライブラリ容量：emotion:8.9kb
- JSの変数・関数がそのままCSSに使える（後述）
- CSSにスコープを付与できる
    - ただし今回の場合、Svelte自体がスコープを持っているため、特別なメリットではない

## デメリットざっくり
- SASSと書き方の異なる部分があり、デザイナーがスタイルを書く場合は学習コストあり
- ~~mixinなど、SASS独自の機能は当然使えない~~
- mixinできた

## 検証したFWについて
emotionを検証しました。
emotionはFWに依存しないCSSinJSのため、公式のブログでもサンプルコードが掲載されています。
（CSSinJSといえばstyled-components一択感はあるが、React依存）
[記事](https://svelte.dev/blog/svelte-css-in-js)

---

# 検証

ここからは実際の使い方の説明です。

## スタイルの指定

- htmlタグ内ではclassを{}で囲むと適用される
- `<script>`タグ内に`const hoge =`で指定
- importするときは`export const hoge =`にしてね
- タグ付きテンプレートリテラルで記述（バッククオーテーションで囲む）


### どうやってCSS書くの

CSSinJSだと、文字通りJSでCSSを書くため、
`margin-bottom`は`marginBottom`になったり、
ハイフンではなくキャメルケースで書きます。

それがJSXの中でインライン記法のように書ける`CSS prop`といいます。

```react:
   const style = css({
    marginBottom: '16px',
    borderRadius: '4px',
  });
```

いつものCSSと書き方が異なり書きづらいよ〜という人のために、
**タグ付きテンプレートリテラル**を使えばいつも通りのCSSの書き方になります。

```js
  const list = css `
    background: ${Color.BaseColor};
    margin-bottom: 16px;
    border-radius: 4px;
  `
```

ただし、Svelteでは`CSS prop`は書けないっぽい:no_mouth:　（JSXだし？）


## コード　サンプル

変数はそれだけでまとめるとよさそう

```js:Color.js
export default {
 BaseColor: '#FFF',
 KeyColor: '#444',
 SubColor: '#ececec'
}
```

こうやって呼び出す

```vue:sample.svelte
<script>
  import { css } from "emotion";
  import Color from "../../apps/style/Color.js";

  const list = css `
    background: ${Color.BaseColor};
    margin-bottom: 16px;
    border-radius: 4px;
  `
</script>

<li class={list}>
  <span class={icon}>
    <Label {type} />
  </span>
</li>
```

`const list = css ~ `でスタイルを定義すると、
templateの方で`class={list}`で適用されます。

この時classは`css-#ハッシュ値`で出力されます。
~~rollupで消せないかな？~~ できないらしい＞＜

<img width="263" alt="スクリーンショット 2020-03-10 9.39.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321220/931573b9-518e-a393-0de4-54eae6a1e861.png">

styled-compornent風に、`styled.h1 =`のように、
`const list = css.li `でタグの指定はできない


```vue:NGな書き方1

<script>
  import { css } from "emotion";

  export const List = css.li `//タグをこっちで指定
    background: ${Color.BaseColor};
    margin-bottom: 16px;
    border-radius: 4px;
  `
</script>

<List>
  <Icon>
    <Label {type} />
  </Icon>
</List>
```
タグを指定できるメリットとしては、

- 純粋にコンポーネント名だけでかけるのでスッキリする
- h1、h2、spanとpなどがスタイル側で指定できる

があるけど、
前者はtemplate部分だけでhtml構成が判別できないデメリットもあるし、
後者はスタイル側で指定しなくてもいいと思うので、
かけなくても問題はない:rolling_eyes:

https://qiita.com/totto357/items/4ab2c4cabe2a087da208


### Media Queries

```js:Base.js
//メディアクエリ
export const breakpoints = [480, 768, 980, 1200]//ブレイクポイント
export const mq = breakpoints.map(
  bp => `@media (min-width: ${bp}px)`
)
```

```js:呼び出すとき
  import { mq } from "../../apps/style/Base.js";

  //コンテンツ幅の980pxで切り替える
  ${mq[2]}{
    display: block;
  }
`
```

### keyframe Animation

```js
//keyframesを指定する
import { css,keyframes } from "emotion";

//アニメーション
//keyframesを先に書かないとエラー
const heightAnimation = keyframes `
 from {
   opacity:0;
  }
  to {
   opacity:1;
  }
`;

const searchBlock  = css `
  display: none;
  //ちゃんとハッシュ値でセットされる
  animation: ${heightAnimation} ease-in .3s;

  //レスポンシブ
  ${mq[2]}{
    display: block;
  }
`
```

### mixin 

```js

export function square(val , key) {
  return {
    width: val,
    height: key,
  };
}

const regularStyle = css`
  ${square('24px','12px')}
　background: hotpink;
`;
```

### propsを直接スタイルで指定して出し分ける方法

#### やりたかったこと

- propsでわざわざclassNameを指定しなくてもいいので、スタイルのためにコンポーネントを増やさずに済む
    - jQueryでは、onClickでclass名を付与->そのclass名の分だけコンポーネントを追加で作るイメージだった
    - propsでprimaryという値を渡せば、１つのコンポーネントの中でスタイルの出しわけができる
    - [参考](https://qiita.com/xrxoxcxox/items/fc8542cadc40ef2505eb)

#### できたこと

jsの配列をCSSに直接指定

```js

  let attr = {
    none: {
      caption: "",
      fontColor: "red"
    },
    place: {
      caption: "ほにゃほにゃ",
      fontColor: "orange"
    },
    image: {
      caption: "ふがふが",
      fontColor: "blue"
    }
  };

export const title = css `
  color: ${attr[type].fontColor};
  font-size: 2rem;
`;
</script>

<ICon {type} />
<label class={title}>{attr[type].caption}</label>

```
今までは、JSとSASSで変数を二重管理する部分があったので、管理が一元化できてよい
（slickのブレイクポイントとかね）

#### できないこと
propsでCSSを直接出し分けること。これが本当にやりたかった＞＜
これができると、状態ごとに毎回classを1つ追加する手間が省けて、コード量が減らせる。

例えば、`<a class="btn">`というDOMに`primary`という状態を付与する場合、
btnクラスの他に`is-primary{foo: bar;}；`のclassを新しく付与していた。

CSSに直接propsを渡せる場合、下記のコードのように、
btnクラスの中で`primary`が渡った場合のみ、必要なコードを動的に付与できる。
 `'@emotion/core'`を入れると使えるけど、React依存のようだ。

```vue

<script>
  const btn = css `
    //backgroundがpropsで渡ってきた色に変更、初期値はgreen
    background: ${props => props.background || "green"};

　　//primaryのpropsが渡ったら、btnにプロパティを追加
    ${props => props.primary && css`
      padding: 1em;
      margin: 1em;
      color: purple;
    `}
</script>

<p>
 <a class={btn} {primary}>
  test
 </a>
</p>

```
単純に書き方が違うだけかもしれないので、
良い書き方があればアドバイスほしいです！


# 使った感想

意外と使えそう:relaxed:

公式で言っている通り、SvelteのCSSと併用する前提になってはいるものの
mixin的使い方も可能なので、十分使えそうです。
デザイナーが使う場合、学習期間は設けるとよさそう。

## GOOD

- 親コンポーネントと子コンポーネントで同じclass名を指定してもスタイルはあたらない
- mixinもいける
- JS側と変数を共有できるのは嬉しい
    - slickのブレイクポイント、カラー変数など、２重管理が防げる

## BAD

- emotion公式のGlobal Stylesは使えない
    - installの必要な`'@emotion/core'`がreactでしか動かないのが諸悪の根源
    - Globalなclassを使う場合、CSSの外部ファイルとしてimportする
    - またはSvelteのGrobalとして指定する　[Using CSS-in-JS with Svelte](https://svelte.dev/blog/svelte-css-in-js)
- VSCodeにEmmetがない？
- Svelteがそもそもスコープを持っているので、擬似スコープのうまみはない

### デザイナーができそう？できない？
良いか悪いか、SvelteのCSSinJSは特有の複雑さが出てこないので、デザイナーでも慣れればOK
（「できないこと」であげたpropsを直接渡す、はグラフィック寄りのデザイナーはしんどいかも）


## 検証ポイント早見表
| 検証項目      |    結果    |    一言    |
|:---------------------|----------------------:|:----------------------:|
| 親子コンポーネントで継承できるか      |    △    |   importするかpropsでclassの付け替え    |
| .svelteファイル、外部ファイルとしても使えるか      |    ○    |   コンポーネントまたいだスタイルは外部ファイルimport   |
| グローバルで当てられるか     |    ○    |    jsファイルをimport   |
| レスポンシブ（SP,PC)     |    ○    |    MediaQuery    |
| keyframe animation     |    ○    |        |
| mixin     |    ○    |        |
| スタイルに直接propsが渡せるか     |    △    |    直接は渡せないので、変数・配列としてclass名を渡す、とかになりそう   |


## 参考

[emotionを使ったCSS-in-JS](https://www.dkrk-blog.net/javascript/emotion_css)
[emotion - フレームワークに依存しない洗練された CSS-in-JS](https://tech.recruit-mp.co.jp/front-end/post-17543/)
[CSS-in-JSのライブラリとして「emotion」を選択している理由](https://qiita.com/__sakito__/items/d240840eef7123f62acf)
[]()