CSS-in-JS ライブラリ Linaria を触って理解する
なぜ styled-components 風の CSS-in-JS が良いと思っているか
- object style と比較して、そのままの CSS が書ける
- camelize したりしなくていいので、CSS のエコシステムに乗りやすい
- e.g. stylelint
- デザイナに CSS 書いてって言いやすい(幻想かもしれない)
- styled-components の extension がないエディタではキツい?
- サポートされてるエディタは多そうだけど、困るエディタはどんなのがあるんだろう?
- camelize したりしなくていいので、CSS のエコシステムに乗りやすい
- CSS Modules と比較して、CSS の定義を利用箇所に colocation できる
- CSS に擬似的に Scope を持ち込める
- CSS を export しないことでより Scope が狭く・明確になる
- その他
-
css
や interpolation によるスタイルの合成
-
一方で、レシピサービスのフロントエンドに CSS in JS を採用した話 - クックパッド開発者ブログ で言われてるような、コンポーネントが増える・HTML Element が隠れることによるデメリットも一定あるはず。
それに対しては css
prop が有効な気がしている。
…ということを考えつつ andreipfeiffer/css-in-js: A thorough analysis of all the current CSS-in-JS solutions with SSR & TypeScript support for Next.js を眺めていて、
- colocation できる
- tagged template で書ける
- TypeScript support
-
css
prop はダメだがclassName
で代用できそう
にくわえ、.css
を吐けるというのが目に止まって Linaria を試していた
とりあえず Next.js with TypeScript に放り込んでみる
Next.js 本体リポジトリに Example app with linaria という便利なものがあるので、とりあえずこれをなぞる
$ yarn add linaria next-linaria
$ yarn add --dev @babel/core
const withLinaria = require('next-linaria')
module.exports = withLinaria({})
無事にぶっこわれた
./src/pages/_app.tsx:2:12
Syntax error: Unexpected token
1 | import "../../styles/globals.css";
> 2 | import type { AppProps } from "next/app";
| ^
3 |
4 | function MyApp({ Component, pageProps }: AppProps) {
5 | return <Component {...pageProps} />;
.babelrc
が必要なのを見逃していた
(Example にはちゃんと存在していた…)
{
"presets": ["next/babel", "linaria/babel"]
}
.babelrc
で linaria の preset 追加しないといけないのはわかる
ただ、忘れると import type { ... } from "...";
でぶっ壊れるのは不思議
こう↓いうのを書くと
const mdH1Css = css`
&::before {
content: "#";
padding-right: 8px;
font-size: 0.8em;
color: rgba(0, 0, 0, 0.6);
}
color: rgba(0, 0, 0, 0.84);
font-size: 36px;
`;
const MdH1: React.FC = ({ children }) => <h1 className={mdH1Css}>{children}</h1>;
yarn dev
だとこう↓なる
<style>.maa7pl{color:rgba(0,0,0,0.84);font-size:36px;}.maa7pl::before{content:"#";padding-right:8px;font-size:0.8em;color:rgba(0,0,0,0.6);}</style>
<h1 class="maa7pl">Test post</h1>
yarn build && yarn start
だとこう↓なる
<link rel="stylesheet" href="/_next/static/css/455a0496615195b4771b.css" data-n-p=""/>
<h1 class="maa7pl">Test post</h1>
styled-components だと css
を css
に interpolation できるが、linaria はムリなのかも?
const textCss = css`
color: rgba(0, 0, 0, 0.84);
`;
const mdH1Css = css`
${textCss}
&::before {
content: "#";
padding-right: 8px;
font-size: 0.8em;
color: rgba(0, 0, 0, 0.6);
}
font-size: 36px;
`;
Error: Cannot find module '@babel/preset-env'
が出る
@babel/preset-env
は next/babel
が持っていたはず…
interpolation 先が css
だろうと styled.h1
だろうと変わらんっぽい
const textCss = css`
color: rgba(0, 0, 0, 0.84);
`;
const MdH1 = styled.h1`
${textCss}
&::before {
content: "#";
padding-right: 8px;
font-size: 0.8em;
color: rgba(0, 0, 0, 0.6);
}
font-size: 36px;
`;
型を見たら string
だったので、まあそうだねという感じ
css
がどのようにバベられてるのかは後で確認したい
css
で dynamic な style は作れないらしい
styled-components でいうこう↓いうやつ
type TextStyle = "body1" | "body2";
const textCss = css<{ textStyle: TextStyle }>`
font-size: ${props => fontSizeMap[props.textStyle]}px;
`;
ドキュメントによると、CSS Custom Properties を使うといいとのこと
const textCss = css`
font-size: var(--font-size);
`;
export function Post({ title, body }) {
return (
<article>
<h1 style={{ "--font-size": fontSizeMap["h1"] }} className={textCss}>{title}</h1>
<div style={{ "--font-size": fontSizeMap["body2"] }} className={textCss}>{body}</div>
</article>
);
}
CSS Custom Properties については IE 除けば一番遅い Edge でも2017年10月に対応してる。
CSS Custom Properties 使うにしてもパラメタが型に現れないのは厳しいので、まるまる Hook に隠蔽するのがまだ現実的か
const textCss = css`
font-size: var(--font-size);
`;
export const useTextStyleCss = (textStyle: TextStyle) => {
return {
className: textCss,
style: { "--font-size": `${fontSizeMap[textStyle]}px` },
};
}
CSS Custom Properties については、そもそも styled
での dynamic style(props わたすやつ)でも利用されているらしい。
When using the
styled
tag, dynamic interpolations will be replaced with CSS custom properties. References to constants in the scope will also be inlined. If the same expression is used multiple times, the plugin will create a single CSS custom property for those.
linaria/HOW_IT_WORKS.md at v2.1.0 · callstack/linaria
小規模なアプリでの利用を除き、Linaria 全体が Custom Properties 前提と考えて良さそう
css
でも props 使いたい人は他にもいて、要望はでている
needs triage のまま半年経ってるけど…
なにがどうバべられてどうなるのかは linaria/HOW_IT_WORKS.md at v2.1.0 · callstack/linaria にわかりやすくまとまっている
css
が返すのが classname なのが悩ましい
この API を採った時点で別の css
を mixin した css
みたいなのが実現しづらくなってる気がする
あと、利用側での classNae の記述順によって挙動が変わりうるのもお悩みポイント