T-CREATOR

Emotion 初期設定完全ガイド:Babel/SWC/型定義/型拡張のベストプラクティス

Emotion 初期設定完全ガイド:Babel/SWC/型定義/型拡張のベストプラクティス

Emotion は CSS-in-JS のライブラリとして、React プロジェクトで広く利用されていますね。しかし、いざ導入しようとすると「Babel と SWC のどちらを選ぶべき?」「型定義はどう設定するの?」「型拡張って必要なの?」といった疑問が次々と湧いてくるのではないでしょうか。

本記事では、Emotion の初期設定における選択肢を整理し、それぞれの設定方法とベストプラクティスを段階的に解説します。最後まで読めば、あなたのプロジェクトに最適な Emotion 環境が構築できるようになるでしょう。

背景

CSS-in-JS としての Emotion の位置づけ

CSS-in-JS は、JavaScript の中で CSS を記述できる手法です。従来の CSS ファイル分割と比べて、コンポーネントとスタイルを密結合できるため、保守性や再利用性が向上します。

Emotion は styled-components と並ぶ人気の CSS-in-JS ライブラリで、以下の特徴を持ちますね。

  • 高いパフォーマンス: ランタイムでの最適化が優れている
  • 柔軟な記法: css prop と styled API の両方をサポート
  • TypeScript サポート: 型安全な開発が可能
  • 小さいバンドルサイズ: 必要な機能だけを含められる

トランスパイラの選択肢

Emotion を使用する際、JSX の変換処理を担うトランスパイラの選択が重要になります。主な選択肢は以下の 2 つです。

mermaidflowchart TD
  source["JSX + Emotion コード"] --> choice{"トランスパイラ<br/>選択"}
  choice -->|従来| babel["Babel + @emotion/babel-plugin"]
  choice -->|次世代| swc["SWC + @swc/plugin-emotion"]
  babel --> result1["変換後の JavaScript"]
  swc --> result2["変換後の JavaScript"]
  result1 --> runtime["ブラウザで実行"]
  result2 --> runtime

図で理解できる要点:

  • Emotion コードは必ずトランスパイラを経由して変換される
  • Babel と SWC はどちらも同じ結果を生成するが、速度が異なる
  • 最終的にはブラウザで実行可能な JavaScript になる

それぞれの特徴を表にまとめました。

#トランスパイラビルド速度成熟度プラグインエコシステム推奨ケース
1Babel★★☆★★★★★★多数の Babel プラグインを使う既存プロジェクト
2SWC★★★★★☆★★☆ビルド速度を重視する新規プロジェクト

型定義と型拡張の必要性

TypeScript で Emotion を使用する場合、以下の 2 つの型設定が必要です。

  • 基本の型定義: Emotion の API に対する型情報
  • 型拡張(Theme): カスタムテーマの型安全性を確保

型拡張を行わないと、theme オブジェクトにアクセスする際に型エラーが発生したり、補完が効かなくなったりします。

課題

Babel と SWC の選択基準が不明確

初学者にとって、Babel と SWC のどちらを選ぶべきか判断するのは困難ですね。公式ドキュメントでは両方の選択肢が提示されていますが、具体的な選択基準が明示されていません。

また、Next.js や Vite など、フレームワークによってデフォルトのトランスパイラが異なるため、さらに混乱を招きます。

css prop 使用時の JSX Pragma 設定

Emotion の css prop を使用するには、JSX の変換方法を Emotion 専用のものに切り替える必要があります。これを「JSX Pragma」の設定と呼びますが、設定方法が複数あり、どれを選ぶべきか迷うポイントです。

mermaidflowchart LR
  jsx["JSX コード<br/>(css prop 付き)"] --> pragma{"JSX Pragma<br/>設定方法"}
  pragma -->|方法1| comment["ファイルごとの<br/>コメント指定"]
  pragma -->|方法2| tsconfig["tsconfig.json<br/>での一括設定"]
  pragma -->|方法3| plugin["Babel/SWC<br/>プラグイン設定"]
  comment --> transform["Emotion の<br/>JSX 変換"]
  tsconfig --> transform
  plugin --> transform

図で理解できる要点:

  • css prop を使うには JSX 変換の指定が必須
  • 3 つの設定方法があり、プロジェクトに応じて選択する
  • どの方法も最終的には Emotion の JSX 変換を実行する

Theme の型拡張が複雑

Emotion で独自のテーマを定義する場合、TypeScript の Module Augmentation(モジュール拡張)を使う必要があります。しかし、以下の問題が発生しやすいですね。

  • 拡張ファイルの配置場所: どこに置けば認識されるのか
  • import の有無: import すべきか、しなくても動くのか
  • 複数テーマの管理: ライトモードとダークモードで型をどう扱うか

これらの疑問が解消されないと、型エラーに悩まされ続けることになります。

パッケージの依存関係が分かりにくい

Emotion には複数のパッケージがあり、どれをインストールすべきか迷います。

  • @emotion​/​react: コアパッケージ
  • @emotion​/​styled: styled API 用
  • @emotion​/​babel-plugin: Babel 用プラグイン
  • @swc​/​plugin-emotion: SWC 用プラグイン

特に、css prop だけ使う場合と styled API も使う場合で、必要なパッケージが異なる点が混乱を招きますね。

解決策

1. トランスパイラ選択のフローチャート

以下のフローチャートに従って、プロジェクトに適したトランスパイラを選択しましょう。

mermaidflowchart TD
  start["Emotion を導入"] --> q1{"Next.js 12+<br/>を使用?"}
  q1 -->|はい| swc_default["SWC を選択<br/>(デフォルト)"]
  q1 -->|いいえ| q2{"既存の Babel<br/>プラグインが多数?"}
  q2 -->|はい| babel_choice["Babel を選択"]
  q2 -->|いいえ| q3{"ビルド速度を<br/>最優先?"}
  q3 -->|はい| swc_choice["SWC を選択"]
  q3 -->|いいえ| babel_choice

推奨判断基準:

#条件推奨トランスパイラ理由
1Next.js 12 以降を使用SWCNext.js のデフォルト、追加設定が最小
2既存 Babel プラグインが 5 つ以上Babelプラグインエコシステムが豊富
3新規プロジェクトSWCビルド速度が最大 20 倍高速
4CRA(Create React App)BabelCRA のデフォルト構成と相性が良い

2. Babel 設定パターン

Babel を選択した場合の完全な設定手順です。

パッケージのインストール

まず必要なパッケージをインストールします。css prop と styled API の両方を使う想定です。

bashyarn add @emotion/react @emotion/styled

開発用の Babel プラグインを追加しましょう。

bashyarn add -D @emotion/babel-plugin

.babelrc の設定

プロジェクトルートに .babelrc ファイルを作成します。

json{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
    "@babel/preset-typescript"
  ]
}

Emotion のプラグインを追加します。この設定により css prop が自動的に認識されるようになります。

json{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
    "@babel/preset-typescript"
  ],
  "plugins": ["@emotion/babel-plugin"]
}

tsconfig.json の設定(TypeScript の場合)

TypeScript を使用する場合、JSX の変換設定を追加します。

json{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@emotion/react"
  }
}

jsxImportSource を設定することで、ファイルごとに ​/​** @jsxImportSource @emotion​/​react *​/​ を書く必要がなくなります。

3. SWC 設定パターン

SWC を選択した場合の設定手順です。Next.js 12 以降を想定しています。

パッケージのインストール

Emotion のコアパッケージをインストールします。

bashyarn add @emotion/react @emotion/styled

SWC 用のプラグインを追加しましょう。

bashyarn add -D @swc/plugin-emotion

next.config.js の設定

Next.js の設定ファイルに SWC プラグインを追加します。

javascript/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
};

module.exports = nextConfig;

SWC のコンパイラオプションを追加します。

javascript/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  compiler: {
    emotion: true,
  },
};

module.exports = nextConfig;

詳細なオプションを設定する場合は、以下のようにオブジェクト形式で記述できます。

javascript/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  compiler: {
    emotion: {
      // 本番環境でソースマップを有効化
      sourceMap: true,
      // 開発時に自動でラベルを付与
      autoLabel: 'dev-only',
      // ラベルのフォーマット指定
      labelFormat: '[local]',
    },
  },
};

module.exports = nextConfig;

tsconfig.json の設定

TypeScript の設定は Babel の場合と同じです。

json{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@emotion/react"
  }
}

4. Theme の型拡張パターン

独自のテーマを型安全に使用するための設定です。

テーマオブジェクトの定義

まず、テーマの実体を定義します。src​/​styles​/​theme.ts に配置しましょう。

typescript// カラーパレットの定義
const colors = {
  primary: '#3f51b5',
  secondary: '#f50057',
  background: '#ffffff',
  text: '#333333',
};

スペーシングやタイポグラフィも追加します。

typescript// カラーパレットの定義
const colors = {
  primary: '#3f51b5',
  secondary: '#f50057',
  background: '#ffffff',
  text: '#333333',
};

// スペーシングシステム
const spacing = {
  xs: '4px',
  sm: '8px',
  md: '16px',
  lg: '24px',
  xl: '32px',
};

テーマオブジェクトをエクスポートします。

typescript// カラーパレットの定義
const colors = {
  primary: '#3f51b5',
  secondary: '#f50057',
  background: '#ffffff',
  text: '#333333',
};

// スペーシングシステム
const spacing = {
  xs: '4px',
  sm: '8px',
  md: '16px',
  lg: '24px',
  xl: '32px',
};

// テーマオブジェクトの統合
export const theme = {
  colors,
  spacing,
};

// テーマの型を取得
export type Theme = typeof theme;

型拡張ファイルの作成

src​/​types​/​emotion.d.ts ファイルを作成して、Emotion の Theme 型を拡張します。

typescript// @emotion/react モジュールを拡張
import '@emotion/react';

独自の Theme 型をインポートして、モジュール拡張を行います。

typescript// @emotion/react モジュールを拡張
import '@emotion/react';
import { Theme as CustomTheme } from '../styles/theme';

declare module で型を上書きしましょう。

typescript// @emotion/react モジュールを拡張
import '@emotion/react';
import { Theme as CustomTheme } from '../styles/theme';

declare module '@emotion/react' {
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  export interface Theme extends CustomTheme {}
}

この設定により、theme.colors.primary などの補完が効くようになります。

ThemeProvider での使用

アプリケーションのルートで ThemeProvider を設定します。src​/​pages​/​_app.tsx の例です。

typescriptimport { ThemeProvider } from '@emotion/react';
import type { AppProps } from 'next/app';
import { theme } from '../styles/theme';

ThemeProvider でアプリケーション全体をラップします。

typescriptimport { ThemeProvider } from '@emotion/react';
import type { AppProps } from 'next/app';
import { theme } from '../styles/theme';

export default function App({
  Component,
  pageProps,
}: AppProps) {
  return (
    <ThemeProvider theme={theme}>
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

5. 複数テーマの管理パターン

ライトモードとダークモードを切り替える場合の設計です。

テーマ定義の分離

共通の色定義と、モードごとの色を分けて管理します。src​/​styles​/​themes.ts を作成しましょう。

typescript// 共通のカラーパレット
const commonColors = {
  primary: '#3f51b5',
  secondary: '#f50057',
  success: '#4caf50',
  error: '#f44336',
};

ライトモード用のテーマを定義します。

typescript// 共通のカラーパレット
const commonColors = {
  primary: '#3f51b5',
  secondary: '#f50057',
  success: '#4caf50',
  error: '#f44336',
};

// ライトモードテーマ
export const lightTheme = {
  ...commonColors,
  background: '#ffffff',
  text: '#333333',
  border: '#e0e0e0',
};

ダークモード用のテーマを追加します。

typescript// 共通のカラーパレット
const commonColors = {
  primary: '#3f51b5',
  secondary: '#f50057',
  success: '#4caf50',
  error: '#f44336',
};

// ライトモードテーマ
export const lightTheme = {
  ...commonColors,
  background: '#ffffff',
  text: '#333333',
  border: '#e0e0e0',
};

// ダークモードテーマ
export const darkTheme = {
  ...commonColors,
  background: '#121212',
  text: '#ffffff',
  border: '#424242',
};

// 型定義(どちらのテーマも同じ構造)
export type Theme = typeof lightTheme;

型拡張の更新

先ほど作成した src​/​types​/​emotion.d.ts を更新します。

typescriptimport '@emotion/react';
import { Theme as CustomTheme } from '../styles/themes';

declare module '@emotion/react' {
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  export interface Theme extends CustomTheme {}
}

テーマ切り替えロジック

状態管理でテーマを切り替える例です。src​/​contexts​/​ThemeContext.tsx を作成しましょう。

typescriptimport { createContext, useState, ReactNode } from 'react';
import { ThemeProvider as EmotionThemeProvider } from '@emotion/react';
import { lightTheme, darkTheme } from '../styles/themes';

// コンテキストの型定義
type ThemeContextType = {
  isDark: boolean;
  toggleTheme: () => void;
};

コンテキストを作成します。

typescriptimport { createContext, useState, ReactNode } from 'react';
import { ThemeProvider as EmotionThemeProvider } from '@emotion/react';
import { lightTheme, darkTheme } from '../styles/themes';

// コンテキストの型定義
type ThemeContextType = {
  isDark: boolean;
  toggleTheme: () => void;
};

export const ThemeContext = createContext<ThemeContextType>(
  {
    isDark: false,
    toggleTheme: () => {},
  }
);

Provider コンポーネントを実装します。

typescriptexport const ThemeProvider = ({
  children,
}: {
  children: ReactNode;
}) => {
  const [isDark, setIsDark] = useState(false);

  // テーマ切り替え関数
  const toggleTheme = () => {
    setIsDark((prev) => !prev);
  };

  // 現在のテーマを選択
  const currentTheme = isDark ? darkTheme : lightTheme;

  return (
    <ThemeContext.Provider value={{ isDark, toggleTheme }}>
      <EmotionThemeProvider theme={currentTheme}>
        {children}
      </EmotionThemeProvider>
    </ThemeContext.Provider>
  );
};

6. パッケージ構成の一覧表

使用するパッケージを一覧で整理します。

#パッケージ名用途css prop のみstyled API も使用Babel 使用時SWC 使用時
1@emotion/reactコアパッケージ★ 必須★ 必須★ 必須★ 必須
2@emotion/styledstyled API 用不要★ 必須★ 必須★ 必須
3@emotion/babel-pluginBabel プラグイン不要不要★ 必須不要
4@swc/plugin-emotionSWC プラグイン不要不要不要★ 必須

この表を参考に、プロジェクトに必要なパッケージだけをインストールしましょう。

具体例

実践例 1: Next.js + SWC + Theme の完全セットアップ

新規 Next.js プロジェクトで Emotion をセットアップする完全な手順です。

プロジェクトの初期化

Next.js プロジェクトを作成します。

bashyarn create next-app my-emotion-app --typescript
cd my-emotion-app

パッケージのインストール

Emotion の必要なパッケージをインストールしましょう。

bashyarn add @emotion/react @emotion/styled
yarn add -D @swc/plugin-emotion

ディレクトリ構成

以下の構成でファイルを作成します。

cssmy-emotion-app/
├── src/
│   ├── pages/
│   │   └── _app.tsx
│   ├── styles/
│   │   └── theme.ts
│   ├── types/
│   │   └── emotion.d.ts
│   └── components/
│       └── Button.tsx
├── next.config.js
└── tsconfig.json

next.config.js の設定

SWC で Emotion を有効化します。

javascript/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  compiler: {
    emotion: {
      sourceMap: true,
      autoLabel: 'dev-only',
      labelFormat: '[local]',
    },
  },
};

module.exports = nextConfig;

tsconfig.json の更新

JSX の import source を設定します。既存の compilerOptions に追加しましょう。

json{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@emotion/react"
  }
}

テーマの定義

src​/​styles​/​theme.ts を作成します。

typescriptexport const theme = {
  colors: {
    primary: '#0070f3',
    secondary: '#ff4081',
    background: '#ffffff',
    text: '#000000',
    gray: {
      100: '#f5f5f5',
      200: '#eeeeee',
      300: '#e0e0e0',
    },
  },
  spacing: {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px',
  },
  breakpoints: {
    mobile: '480px',
    tablet: '768px',
    desktop: '1024px',
  },
};

export type Theme = typeof theme;

型拡張の設定

src​/​types​/​emotion.d.ts を作成します。

typescriptimport '@emotion/react';
import { Theme as CustomTheme } from '../styles/theme';

declare module '@emotion/react' {
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  export interface Theme extends CustomTheme {}
}

_app.tsx の設定

src​/​pages​/​_app.tsx で ThemeProvider を設定します。

typescriptimport { ThemeProvider } from '@emotion/react';
import type { AppProps } from 'next/app';
import { theme } from '../styles/theme';

export default function App({
  Component,
  pageProps,
}: AppProps) {
  return (
    <ThemeProvider theme={theme}>
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

コンポーネントでの使用例

src​/​components​/​Button.tsx を作成して、theme を使用します。

typescriptimport styled from '@emotion/styled';

export const Button = styled.button`
  padding: ${({ theme }) => theme.spacing.md};
  background-color: ${({ theme }) => theme.colors.primary};
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    opacity: 0.8;
  }
`;

css prop を使った別の実装例です。

typescript/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import { useTheme } from '@emotion/react';

export const CssPropButton = () => {
  const theme = useTheme();

  return (
    <button
      css={css`
        padding: ${theme.spacing.md};
        background-color: ${theme.colors.primary};
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;

        &:hover {
          opacity: 0.8;
        }
      `}
    >
      Click me
    </button>
  );
};

この設定により、型補完が効いた状態で Emotion を使用できますね。

実践例 2: CRA + Babel + 複数テーマの実装

Create React App で Babel を使う場合の例です。

プロジェクトの初期化

CRA でプロジェクトを作成します。

bashyarn create react-app my-emotion-app --template typescript
cd my-emotion-app

パッケージのインストール

必要なパッケージをインストールしましょう。

bashyarn add @emotion/react @emotion/styled
yarn add -D @emotion/babel-plugin

Babel 設定の追加

CRA では .babelrc が隠れているため、package.json に設定を追加します。

json{
  "babel": {
    "presets": ["react-app"],
    "plugins": ["@emotion/babel-plugin"]
  }
}

別の方法として、craco を使って設定を上書きすることもできます。

bashyarn add -D @craco/craco

craco.config.js を作成して、Babel プラグインを追加します。

javascriptmodule.exports = {
  babel: {
    plugins: ['@emotion/babel-plugin'],
  },
};

package.json のスクリプトを更新しましょう。

json{
  "scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test"
  }
}

複数テーマの定義

src​/​styles​/​themes.ts を作成します。

typescriptconst baseTheme = {
  spacing: {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px',
  },
  borderRadius: {
    sm: '4px',
    md: '8px',
    lg: '12px',
  },
};

export const lightTheme = {
  ...baseTheme,
  colors: {
    primary: '#1976d2',
    secondary: '#dc004e',
    background: '#ffffff',
    surface: '#f5f5f5',
    text: '#212121',
    textSecondary: '#757575',
  },
};

export const darkTheme = {
  ...baseTheme,
  colors: {
    primary: '#90caf9',
    secondary: '#f48fb1',
    background: '#121212',
    surface: '#1e1e1e',
    text: '#ffffff',
    textSecondary: '#b0b0b0',
  },
};

export type Theme = typeof lightTheme;

テーマコンテキストの実装

src​/​contexts​/​ThemeContext.tsx を作成します。

typescriptimport {
  createContext,
  useContext,
  useState,
  ReactNode,
  useEffect,
} from 'react';
import { ThemeProvider as EmotionThemeProvider } from '@emotion/react';
import { lightTheme, darkTheme } from '../styles/themes';

type ThemeMode = 'light' | 'dark';

type ThemeContextType = {
  mode: ThemeMode;
  toggleTheme: () => void;
};

コンテキストとフックを定義します。

typescriptconst ThemeContext = createContext<
  ThemeContextType | undefined
>(undefined);

export const useThemeContext = () => {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error(
      'useThemeContext must be used within ThemeProvider'
    );
  }
  return context;
};

Provider コンポーネントを実装しましょう。

typescriptexport const ThemeProvider = ({
  children,
}: {
  children: ReactNode;
}) => {
  // localStorage からテーマを復元
  const [mode, setMode] = useState<ThemeMode>(() => {
    const saved = localStorage.getItem('theme-mode');
    return (saved as ThemeMode) || 'light';
  });

  // テーマ切り替え
  const toggleTheme = () => {
    setMode((prev) =>
      prev === 'light' ? 'dark' : 'light'
    );
  };

  // localStorage に保存
  useEffect(() => {
    localStorage.setItem('theme-mode', mode);
  }, [mode]);

  const currentTheme =
    mode === 'light' ? lightTheme : darkTheme;

  return (
    <ThemeContext.Provider value={{ mode, toggleTheme }}>
      <EmotionThemeProvider theme={currentTheme}>
        {children}
      </EmotionThemeProvider>
    </ThemeContext.Provider>
  );
};

App.tsx での使用

src​/​App.tsx でテーマを適用します。

typescriptimport { ThemeProvider } from './contexts/ThemeContext';
import { MainContent } from './components/MainContent';

function App() {
  return (
    <ThemeProvider>
      <MainContent />
    </ThemeProvider>
  );
}

export default App;

テーマ切り替えボタンの実装

src​/​components​/​ThemeToggle.tsx を作成します。

typescriptimport styled from '@emotion/styled';
import { useThemeContext } from '../contexts/ThemeContext';

const ToggleButton = styled.button`
  padding: ${({ theme }) => theme.spacing.md};
  background-color: ${({ theme }) => theme.colors.primary};
  color: ${({ theme }) => theme.colors.text};
  border: none;
  border-radius: ${({ theme }) => theme.borderRadius.md};
  cursor: pointer;
  font-size: 16px;
  transition: opacity 0.2s;

  &:hover {
    opacity: 0.8;
  }
`;

export const ThemeToggle = () => {
  const { mode, toggleTheme } = useThemeContext();

  return (
    <ToggleButton onClick={toggleTheme}>
      {mode === 'light'
        ? '🌙 ダークモード'
        : '☀️ ライトモード'}
    </ToggleButton>
  );
};

グローバルスタイルの適用

src​/​components​/​GlobalStyles.tsx を作成して、全体のスタイルを定義します。

typescriptimport { Global, css, useTheme } from '@emotion/react';

export const GlobalStyles = () => {
  const theme = useTheme();

  return (
    <Global
      styles={css`
        * {
          margin: 0;
          padding: 0;
          box-sizing: border-box;
        }

        body {
          font-family: -apple-system, BlinkMacSystemFont,
            'Segoe UI', 'Roboto', sans-serif;
          background-color: ${theme.colors.background};
          color: ${theme.colors.text};
          transition: background-color 0.3s, color 0.3s;
        }
      `}
    />
  );
};

src​/​components​/​MainContent.tsx でグローバルスタイルを読み込みます。

typescriptimport { GlobalStyles } from './GlobalStyles';
import { ThemeToggle } from './ThemeToggle';

export const MainContent = () => {
  return (
    <>
      <GlobalStyles />
      <div>
        <h1>Emotion Theme Example</h1>
        <ThemeToggle />
      </div>
    </>
  );
};

この実装により、テーマの切り替えが localStorage に保存され、リロード後も保持されます。

実践例 3: トラブルシューティング

Emotion の初期設定でよく遭遇するエラーと解決方法をまとめました。

エラー 1: Property 'css' does not exist on type 'DetailedHTMLProps'

エラーコード: TypeScript Error TS2322

エラーメッセージ:

bashProperty 'css' does not exist on type 'DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'.

発生条件:

  • tsconfig.json で jsxImportSource が設定されていない
  • ファイルの先頭に JSX Pragma が記載されていない

解決方法:

tsconfig.json に以下を追加します。

json{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@emotion/react"
  }
}

または、ファイルごとに以下のコメントを追加しましょう。

typescript/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';

エラー 2: Module '"@emotion​/​react"' has no exported member 'Theme'

エラーコード: TypeScript Error TS2305

エラーメッセージ:

sqlModule '"@emotion/react"' has no exported member 'Theme'.

発生条件:

  • 型拡張ファイル(emotion.d.ts)が認識されていない
  • tsconfig.json の include に型定義ファイルが含まれていない

解決方法:

tsconfig.json の include を確認します。

json{
  "include": ["src/**/*", "src/types/**/*.d.ts"]
}

型拡張ファイルのパスが正しいか確認しましょう。

typescript// src/types/emotion.d.ts
import '@emotion/react';
import { Theme as CustomTheme } from '../styles/theme';

declare module '@emotion/react' {
  export interface Theme extends CustomTheme {}
}

IDE を再起動するか、TypeScript サーバーを再起動します。VS Code の場合は以下のコマンドを実行してください。

arduinoCmd + Shift + P → "TypeScript: Restart TS Server"

エラー 3: Cannot find module '@swc​/​plugin-emotion'

エラーコード: Module Not Found Error

エラーメッセージ:

arduinoError: Cannot find module '@swc/plugin-emotion'

発生条件:

  • next.config.js で SWC プラグインを指定しているが、パッケージがインストールされていない

解決方法:

パッケージをインストールします。

bashyarn add -D @swc/plugin-emotion

Next.js 12.2 以降では、プラグインなしでも動作する場合があります。その場合は以下の設定で十分です。

javascriptmodule.exports = {
  compiler: {
    emotion: true,
  },
};

エラー 4: Theme の補完が効かない

症状:

  • theme.colors.primary などの補完が出ない
  • エラーは出ないが、型チェックが機能していない

原因:

  • 型拡張ファイルが import されていない
  • 循環参照が発生している

解決方法:

型拡張ファイルを確認します。import 文が正しく記載されているか確認しましょう。

typescriptimport '@emotion/react';
import { Theme as CustomTheme } from '../styles/theme';

declare module '@emotion/react' {
  export interface Theme extends CustomTheme {}
}

_app.tsx などで型拡張ファイルを明示的に import します。

typescriptimport '../types/emotion.d.ts'; // 明示的に読み込む
import { ThemeProvider } from '@emotion/react';

TypeScript のバージョンが古い場合は更新しましょう。

bashyarn add -D typescript@latest

まとめ

Emotion の初期設定は、トランスパイラの選択、JSX Pragma の設定、型定義の拡張という 3 つのステップで構成されます。それぞれのステップで複数の選択肢があり、プロジェクトの特性に応じて最適な組み合わせを選ぶことが重要です。

設定の選択基準

#項目新規プロジェクト既存プロジェクト(Babel)Next.js 12+CRA
1トランスパイラSWCBabelSWCBabel
2パッケージ@emotion/react + @swc/plugin-emotion@emotion/react + @emotion/babel-plugin@emotion/react@emotion/react
3設定ファイルnext.config.js.babelrcnext.config.jscraco.config.js
4TypeScript 設定jsxImportSourcejsxImportSourcejsxImportSourcejsxImportSource

ベストプラクティス

  1. トランスパイラの選択: Next.js 12 以降では SWC、CRA や既存の Babel 環境では Babel を選択しましょう
  2. 型拡張の実装: 独自テーマを使う場合は必ず emotion.d.ts で型拡張を行います
  3. パッケージの最小化: css prop だけ使う場合は @emotion/styled は不要です
  4. 複数テーマの管理: テーマごとに別オブジェクトを定義し、Context で切り替えます
  5. 型補完の確保: tsconfig.json の jsxImportSource 設定を忘れずに行いましょう

実装のチェックリスト

設定が正しく完了しているか、以下のチェックリストで確認してください。

  • @emotion/react がインストールされている
  • トランスパイラに応じたプラグインがインストールされている
  • tsconfig.json で jsxImportSource が設定されている
  • 型拡張ファイル(emotion.d.ts)が作成されている
  • ThemeProvider がアプリケーションルートに配置されている
  • theme オブジェクトで型補完が効いている
  • css prop が TypeScript エラーなく使用できている

これらのステップを踏むことで、型安全で保守性の高い Emotion 環境を構築できます。初期設定の選択肢に迷ったときは、本記事のフローチャートや一覧表を参考にして、プロジェクトに最適な構成を見つけてくださいね。

関連リンク