戦えプログラマン

いつだって戦いなのだ

create-react-app + TypeScript + styled-components で、css class 名に styled-components の変数名を出力する

ようやく css 戦国時代も終わりを迎え、 styled-components の勝利ということで皆の衆異論はないかと思う(ポジトーク
少なくとも、 BEMM とか FLOCSS とか、あの手の温もりと涙ぐましい努力・根性を身につけるぐらいなら、
styled-components の設計について知見を深めるべきかと思う。

で、その styled-components をより使いやすくするための記事である。

概要

  • styled-components を使って css を書くと、css class名は乱数で出力される
  • 「この css どこに実装されてるんだっけ?」というデバッグがつらい
  • styled-components を宣言した時の変数名も併せて出力してほしい

イメージ

f:id:arx0balest:20180922111653p:plain

そんな時に

前提

  • npx create-react-app ${PJ_NAME} --scripts-version=react-scripts-ts でReact開発環境を作った
  • styled-components はインストール済み
  • create-react-app の eject はしない。絶対にだ。

やり方

install

  • typescript-plugin-styled-components を使う
  • Webpack の設定変更が必要になるため、 react-app-rewired も使う
  • create-react-app の eject はしない。絶対にだ。(大切なことなので二ry)
npm i -D react-app-rewired typescript-plugin-styled-components
touch config-overrides.js

設定

config-overrides.js

config-overrides.js

module.exports = {
  /**
   * The Webpack config to use when compiling your react app for development or production.
   */
  webpack: function (config, env) {
    const isDev = env === "development"

    if (isDev) {
      // ローカル開発環境のみ、styled-componentsの変数名をDOMに吐き出す
      const loaders = config.module.rules[1].oneOf
      const tsLoader = loaders.find((loader) => (
        String(loader.test) === String(/\.(ts|tsx)$/)
      ))
      const tsLoaderInner = tsLoader.use.find((loader) => (
        String(loader.loader).includes("ts-loader")
      ))

      // @see https://github.com/Igorbek/typescript-plugin-styled-components#ts-loader
      const createStyledComponentsTransformer = require("typescript-plugin-styled-components").default
      tsLoaderInner.options.getCustomTransformers = () => ({
        before: [createStyledComponentsTransformer()]
      })
    }

    return config
  }
}

npm scripts

確認

npm start

おわかりいただけただろうか・・・
f:id:arx0balest:20180922111814p:plain

ちなみに、該当箇所のコンポーネントのコードは下記の通り
CRAのサンプルコードの App.css を、 styled-components 化したものである

src\App.tsx

import * as React from "react"
import styled from "styled-components"
import logo from "./logo.svg"

export const App = () => (
  <Root>
    <Header>
      <Logo src={logo} alt="logo" />
      <Title>Welcome to React</Title>
    </Header>
    <Intro>
      To get started, edit <code>src/App.tsx</code> and save to reload.
    </Intro>
  </Root>
)

const Root = styled.div`
  text-align: center;
`

const Header = styled.header`
  background-color: #222;
  height: 150px;
  padding: 20px;
  color: white;
`

const Logo = styled.img`
  animation: App-logo-spin infinite 20s linear;
  height: 80px;

  @keyframes App-logo-spin {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(360deg);
    }
  }
`

const Title = styled.h1`
  font-size: 1.5em;
`

const Intro = styled.p`
  font-size: large;
`

src

実際はさらにわかりやすくするため、ファイル名も表示するよう設定している。

余談

このプラグインでいいのか?

本家の babel-plugin-styled-components に比べ typescript-plugin-styled-components はシェアが少ない。
もっとメジャーなのがあるのだろうか?

create-react-app の eject はしない。絶対にだ。

CRA の eject を安直に勧めてくる人もいるが、個人的にはオススメしない。
(よほどシビアなパフォーマンスチューニングなどを求められない限り)

数年前ならいざしらず、今は react-app-rewired を使えば大抵の設定変更は可能である。
例えば typescript-plugin-styled-componentsのようなプラグインの追加のためだけに eject し、
大量の設定ファイルの管理・バージョンアップのマイグレーション etc. を自分でやるのは
どう考えてもコスパが悪いだろう。