The Pragmatic Ball boy

iOSを中心にやってる万年球拾いの老害エンジニアメモ

styled-componentsをゼロランタイムCSS in JSに置き換える検討

この記事はコネヒトアドベントカレンダー2024の20日目の記事です。

adventar.org

styled-componentsで書かれたプロジェクトをあまり移行コストをかけずにゼロランタイムCSS in JSに置き換えられないか、といくつかのゼロランタイムCSS in JSを軽く味見してみました。

なぜゼロランタイムCSS in JSにするのかということについてはいろいろなところで語られているかと思いますのでここでは省略させていだただきます。

選定条件

条件としては以下の2点で選定しました。

  • styled-componentsの書き方に近い
  • ゼロランタイムCSS in JSである

選定した結果以下の3つを味見しました。

  1. Linaria : Linaria – zero-runtime CSS in JS library
  2. panda : https://panda-css.com/
  3. Kuma UI : https://www.kuma-ui.com/

味見

環境

Remix, Vite, npm

styled-components

まずstyled-componentの場合の実装例です。

import { styled } from 'styled-components';

const SubTitle = styled.h2`
  font-weight: bold;
  color: ${(props) => props.color || 'black'};
`;

export default function Content() {
  return (
    <div>
      <h1>Hello, World!</h1>
      <SubTitle color="red">red</SubTitle>
      <SubTitle color="blue">blue</SubTitle>
    </div>
  );
}

サンプルコードはこちらのリポジトリのPullRequestにも載せてあります。 Pull requests · yanamura/zero-runtime-css-in-js-sample · GitHub

Linaria

setup

npm install @linaria/core @linaria/react @wyw-in-js/babel-preset
npm i -D @wyw-in-js/vite
// vite.config.ts
import { defineConfig } from 'vite';
import wyw from '@wyw-in-js/vite';
 
export default defineConfig(() => ({
  // ...
  plugins: [wyw()],
}));

実装

import { styled } from '@linaria/react';

const SubTitle = styled.h2`
  font-weight: bold;
  color: ${(props) => props.color || 'black'};
`;

export default function Content() {
  return (
    <div>
      <h1>Hello, World!</h1>
      <SubTitle color="red">red</SubTitle>
      <SubTitle color="blue">blue</SubTitle>
    </div>
  );
}

感想

styled-compontntsと全く同じコードで実装できたので、移植は楽そうかもしれません。

気になるところをあげるとすると、ドキュメントがGitHubのdocs以下しかなさそうなところと、babelに依存しているところくらいでしょうか。

panda

setup

npm install -D @pandacss/dev
npx panda init --postcss
// package.json
{
  "scripts": {
+    "prepare": "panda codegen",
    "dev": "vite",
    "build": "tsc && vite build",
    "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  }
}
// panda.config.ts
import { defineConfig } from "@pandacss/dev";

export default defineConfig({
  // Whether to use css reset
  preflight: true,

  // Where to look for your css declarations
  include: ["./src/**/*.{js,jsx,ts,tsx}", "./pages/**/*.{js,jsx,ts,tsx}"],

  // Files to exclude
  exclude: [],

  // Useful for theme customization
  theme: {
    extend: {},
  },

  syntax: 'template-literal',
  jsxFramework: 'react',

  // The output directory for your css system
  outdir: "styled-system",
});
npx panda codegen --clean

ポイントとしてはstyled-components系のスタイル(JSX style)を使うには、panda.config.tsに jsxFramework: 'react' を追加して, npx panda codegen --clean する必要があるところです。

実装

import { styled } from '../styled-system/jsx';

const SubTitle = styled.h2`
  font-weight: bold;
`

// color: ${(props) => props.color}; // 自前で定義したプロパティは使えない

export default function Content() {
  return (
    <div>
      <h1>Hello, World!</h1>
      <SubTitle>red</SubTitle>
      <SubTitle>blue</SubTitle>
    </div>
  );
}

感想

pandaは導入がややこしいです。ただ最初だけなのでそこまで気にする必要はないかもしれませんが。 prepareでpanda codegenが動いてstyled-systemフォルダ以下にファイルを生成しているのがちょっと気になりました。(ビルドしないとstyled-system以下を参照してもエディタ上ではエラーがでるのと、JSXを有効するときにこれを一回cleanしないといけなかったりと) 実装上は、styled-componentsのようにpropsを渡すことができないようなので、そういうコードを移植したい場合はちょっと困るかもしれません。

Kuma UI

setup

npm install @kuma-ui/core
npm install -D @kuma-ui/vite
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import KumaUI from "@kuma-ui/vite";
 
export default defineConfig({
  plugins: [
    react(),
    KumaUI({
      // Enable WebAssembly support for Kuma UI. Default is false and will use Babel to transpile the code.
      wasm: true // Optional
    }),
  ],
});

実装

import { styled } from "@kuma-ui/core";

const SubTitle = styled.h2`
  font-weight: bold;
`;

// color: ${(props) => props.color}; // 自前で定義したプロパティは使えない

export default function Content() {
  return (
    <div>
      <h1>Hello, World!</h1>
      <SubTitle color="red">red</SubTitle>{/** colorはKumaUIのUtility Props(https://www.kuma-ui.com/docs/Components/UtilityProps#color) */}
      <SubTitle color="blue">blue</SubTitle>
    </div>
  );
}

感想

導入は一番楽でした! 実装上は、panda同様styled-componentsのようにpropsを渡すことができないようでした。 ただいくつかUtility Propsというのが用意されていてそれで代用できるケースもあるかもしれません。

総括

styled-componentの書き方を変えずにゼロランタイムCSS in JSにするには、Linariaがあまり既存のコードに影響が少なく持ってこれるという点ではよいかなという印象でした。

ただ、調べているうちに、Compiledではstyledの記法がdeprecatedになっていたりと、styled-componentsの書き方に縛られるのはあまりよくないのでは・・という感想を持ちました。 個人的にもstyled-componentsの書き方はパッと見で機能を持つコンポーネントなのかただ装飾だけのコンポーネントなのかわかりづらいのがあまり好きではないので、最初から採用するのであればstyledのスタイルよりはcssのスタイルのほうを選んだほうがよいかなと思っています。