【React】twin.macro × TailwindCSSで爆速開発を行う方法

【React】twin.macro × TailwindCSSで爆速開発を行う方法 フロントエンド

はじめに

僕は個人開発や副業先で技術選定をする際は、専らTailwindCSSを使っています。
.cssファイルと.tsxファイルを交互に見たり、コードが長くなったりせず、爆速で開発できるのが気持ちいいですよね。
最近はjitモードも追加されて、痒い所に手が届くようになった、TailwindCSSですが、メリットはそのままにCSS in JSとして扱える、twin.macroの使い方についてまとめたいと思います。

twin.macroがどんな感じで使えるかは最後の方で紹介してますので、気になる方はそこだけでもみていって頂けると嬉しいです!

この記事で作成したプロジェクトをもとにセットアップを行いますので、よろしければ、こちらの記事も覗いていただけると嬉しいです。
( ちなみにcreate react appではなくviteを使っています。もし使ったことない方は爆速vite試してみてください。)

TailwindCSSインストール

まずは、パッケージをインストールしましょう。

yarn add tailwindcss postcss autoprefixer --dev

以下のコマンドでtailwind.config.jspostcss.config.jsを生成します。

yarn tailwindcss init -p

次に、tailwind.config.jsを以下のように書き換えましょう

/** @type {import('tailwindcss').Config} */
module.exports = {
  mode: 'jit',
  content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
}

そして、src/index.cssをこのように変更してください。
( デフォルトの内容は全消ししちゃって大丈夫です。 )

@tailwind base;
@tailwind components;
@tailwind utilities;

最後にsrc/main.tsxでTailwindCSSを読み込んであげればOKです。

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import 'tailwindcss/tailwind.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

これでTailwindCSSが使えるので、試しにsrc/App.tsxをこのように変更してみます。

// .....

function App() {
  const [count, setCount] = useState(0)

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p className="bg-yellow-600 text-[70px]">Hello Vite + React!</p>
        <p>
        <!-- ...... -->

こんな感じでいつもの画面が表示され、classNameを指定した部分が変化していれば完璧です!

HelloTailwindCSS
HelloTailwindCSS

twin.macroセットアップ

twin.macroインストール

TailwindCSSが使えるようになったので、次はこれをCSS in JSとして扱えるtwin.macroをインストールしていきます。

yarn add @emotion/react @emotion/styled
yarn add --dev twin.macro @emotion/babel-plugin-jsx-pragmatic babel-plugin-macros tailwindcss -D

GlobalStyle適用

次にtwin.macroが用意してくれてるGlobalStyleを適用するようにしましょう。
まずはsrc/GlobalStyles.tsxを新規に作成します。

import React from 'react'
import { css, Global } from '@emotion/react'
import tw, { theme, GlobalStyles as BaseStyles } from 'twin.macro'

const customStyles = css({
  body: {
    WebkitTapHighlightColor: theme`colors.purple.500`,
    ...tw`antialiased`,
    // グローバルスタイルをカスタマイズしたいなら、ここに追記していく
  },
})

const GlobalStyles = () => (
  <>
    <BaseStyles />
    <Global styles={customStyles} />
  </>
)

export default GlobalStyles

次に、src/main.tsxに作成したGlobalStyleを読み込むようにしましょう。
src/index.cssは使わなくなるので消しちゃって大丈夫です。
すでにsrc/index.cssにグローバルスタイルを書いてる場合はGlobalStyleに移行すると良いです。

import React from 'react'
import ReactDOM from 'react-dom/client'
import GlobalStyles from './GlobalStyles'
import App from './App'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <GlobalStyles />
    <App />
  </React.StrictMode>
)

viteにプラグインを追加

vite.config.jsを以下のように編集し、プラグインを追加しましょう

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          'babel-plugin-macros',
          [
            '@emotion/babel-plugin-jsx-pragmatic',
            {
              export: 'jsx',
              import: '__cssprop',
              module: '@emotion/react',
            },
          ],
          [
            '@babel/plugin-transform-react-jsx',
            { pragma: '__cssprop' },
            'twin.macro',
          ],
        ],
      },
    }),
  ],
  esbuild: {
    logOverride: { 'this-is-undefined-in-esm': 'silent' }
  },
});

Typescript設定

Typescriptを使ってない人はスキップしてOKです。
Typescriptを使っていて、@types/reactをインストールしてない方はインストールしてください。

src/types/twin.d.tsを新規で作成し、以下の内容を記載します。
( 他のディレクトリに型定義ファイルをまとめてる方はそちらのディレクトリに作成してください。)

import 'twin.macro'
import { css as cssImport } from '@emotion/react'
import { CSSInterpolation } from '@emotion/serialize'
import styledImport from '@emotion/styled'

declare module 'twin.macro' {
  // The styled and css imports
  const styled: typeof styledImport
  const css: typeof cssImport
}

declare module 'react' {
  // The css prop
  interface HTMLAttributes<T> extends DOMAttributes<T> {
    css?: CSSInterpolation
    tw?: string
  }
  // The inline svg css prop
  interface SVGProps<T> extends SVGProps<SVGSVGElement> {
    css?: CSSInterpolation
    tw?: string
  }
}

最後にtsconfig.jsonに1行追加して設定は完了です。

{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "jsxImportSource": "@emotion/react"
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

TailwindCSS + twin.macroを使ってみる

まずは、classNameで指定していたTailwindCSSをtwin.macroを使ってCSS in JSに書き換えてみましょう。
src/App.tsxを以下のように書き換えてください。

import { useState } from 'react'
import logo from './logo.svg'
import './App.css'
import tw from "twin.macro"

function App() {
  const [count, setCount] = useState(0)

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p css={tw`bg-yellow-600 text-[70px]`}>Hello Vite + React!</p>
        <p>
          <button type="button" onClick={() => setCount((count) => count + 1)}>
            count is: {count}
          </button>
          <!-- ....... -->

このようにjsxのcssプロパティにtwメソッドを使って、TailwindCSSを当てることができます。

次に、color引数によってchildrenのテキストカラーを変更させるTextコンポーネントについて考えてみます。

import tw, { TwStyle } from "twin.macro";

type Color = "blue" | "red" | "yellow";
type TextProp = {
  color: Color;
  children: React.ReactNode;
};

const colorize: {
  [key in Color]: TwStyle;
} = {
  blue: tw`text-blue-600`,
  red: tw`text-red-600`,
  yellow: tw`text-yellow-600`,
};

const Text = (prop: TextProp) => {
  return <span css={ colorize[prop.color] }>{ prop.children }</span>
};

こんな感じでTailwindCSSをCSS in JSを用いて利用することで、typescriptとの相性も抜群に良くなります。

また、以下のようにemotionと組み合わせて使ったり、

import { css } from "emotion/css";
import tw from "twin.macro";

const textStyle = css`
  ${tw`text-red-500`}
`;

styledコンポーネントチックに記述したり、

import tw from "twin.macro"

const Button = tw.button`
  bg-primary-200
  cursor-pointer
`;

<Button>push me</Button>

styledコンポーネントで引数を設けたりできます。

import tw, { styled } from "twin.macro";

const buttonBaseStyle = tw`bg-primary-600 text-white`

const Button = styled.button( ({ large }: { large?: boolean }) =>
  [
    buttonBaseStyle,
    large ? tw`text-xl` : tw`text-base`,
  ]
)

<Button> normal btn </Button>
<Button large> LARGE BTN </Button>

僕はtwメソッドだけでなんとかなる場合が多いので、まずはライトに爆速Tailwind開発始めてみてはいかがでしょうか。

コメント

タイトルとURLをコピーしました