🐉

Next.js 上で Emotion と StyleX の共存をためしてみた!

2024/01/04に公開

明けましたね。今年もよろしくお願いします!
ログラスでエンジニアをしている塩谷 @shioyang です。

"Loglass Tech Blog Sprint" 20 週目の記事をお届けします!!!


今年の干支は「辰」ですね。辰は変化と成長の象徴です。
進化し続ける Web アプリ開発にとっても大きな変化と成長の年になるのではないでしょうか。

昨年 12 月 5 日、Meta Platforms, Inc. から新しい CSS-in-JS ライブラリの StyleX がオープンソースとして公開されました[1]。大規模なアプリケーション、再利用可能なコンポーネントライブラリ、静的型付けされたコードベースなどの要件を満たすように設計されています。

StyleX has been carefully designed to meet the requirements of large applications, reusable component libraries, and statically typed codebases.

一方、近年よく使用されている CSS-in-JS ライブラリのひとつである Emotion も別の特徴があります。
そこで、個人としての技術的興味から StyleX と Emotion をひとつの Next.js ウェブアプリケーション上で共存させる環境を構築してみることにしました。この試みから得られた構築手順の知見を共有します。

今回は以下のバージョンで環境構築をしています。

Package Version
Node.js v18.17.1
Next.js v14.0.4
@emotion/react v11.11.3
@stylexjs/stylex v0.4.1
@stylexjs/nextjs-plugin v0.4.1

構築したアプリケーションのリポジトリはこちらです。

https://github.com/shioyang/nextjs-with-emotion-and-stylex

1. Next.js に Emotion をセットアップする

まず最初に create-next-app を使って Next.js アプリケーションを作成します[2]

npx create-next-app@latest

create-next-app 実行時に聞かれる構成では、下記二点をデフォルトから変更しています。

  • Tailwind CSS: No
    • CSS ライブラリは Emotion を使うため
  • App Router: No
    • 現在のところ App Router はサポートされていないため[3]、Page Router を使う


create-next-app 実行時の選択

1-1. Emotion をインストールする

次に、Next.js で Emotion が使えるようにします。

cd my-app
npm install --save @emotion/react

TypeScript で使えるように、tsconfig.jsonjsxImportSourcetypes の記述を追加します[4]

tsconfig.json
@@ -12,10 +12,14 @@
     "resolveJsonModule": true,
     "isolatedModules": true,
     "jsx": "preserve",
+    "jsxImportSource": "@emotion/react",
     "incremental": true,
     "paths": {
       "@/*": ["./src/*"]
-    }
+    },
+    "types":[
+      "@emotion/react/types/css-prop",
+    ]
   },
   "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
   "exclude": ["node_modules"]

1-2. Emotion の動作確認をする

Emotion のセットアップは以上ですが、動作確認のためにコンポーネントを作っておきます。

mkdir src/components
src/components/ButtonWithEmotion.tsx
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'

const buttonStyle = {
    button: css`
      color: blue;
    `
}

const ButtonWithEmotion = () => {
    return <button css={buttonStyle.button}>Hello Emotion</button>
}

export { ButtonWithEmotion }

ButtonWithEmotion を使うように、index.tsx を書きかえます。

src/pages/index.tsx
import { ButtonWithEmotion } from "@/components/ButtonWithEmotion";
import Head from "next/head";

export default function Home() {
  return (
    <>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main>
        <div>
          <div>
            <span>Emotion</span>
          </div>
          <div>
            <ButtonWithEmotion />
          </div>
        </div>
      </main>
    </>
  )
}

ビルドして動作を確認します。

npm run build
npm run start

http://localhost:3000/ を開くと、下図のように Emotion で記述したコンポーネントが正しく表示されました。


Emotion で記述したコンポーネントの実行結果

2. Next.js に StyleX をセットアップする

2-1. StyleX をインストールをする

いよいよ、StyleX を入れます。

npm install --save @stylexjs/stylex

Next.js で使用するためのプラグインも必要です。

npm install --save-dev @stylexjs/nextjs-plugin

.babelrc.js ファイルを作成して、presetsplugins の設定をします。
記述する内容は公式ページの記述[5]や、GitHub 上の公式サンプルの記述[6]を参照するとよいです。

.babelrc.js
module.exports = {
    presets: ['next/babel'],
    plugins: [
      [
        '@stylexjs/babel-plugin',
        {
          dev: process.env.NODE_ENV === 'development',
          runtimeInjection: false,
          genConditionalClasses: true,
          treeshakeCompensation: true,
          unstable_moduleResolution: {
            type: 'commonJS',
            rootDir: __dirname,
          },
        },
      ],
    ],
  };

next.config.js の変更も必要です。
こちらは GitHub 上の公式サンプルコードを参考にしています[7]

next.config.js
/** @type {import('next').NextConfig} */
const stylexPlugin = require('@stylexjs/nextjs-plugin');
const babelrc = require('./.babelrc.js');
const plugins = babelrc.plugins;
const [_name, options] = plugins.find(
  (plugin) => Array.isArray(plugin) && plugin[0] === '@stylexjs/babel-plugin',
);
const rootDir = options.unstable_moduleResolution.rootDir ?? __dirname;

module.exports = stylexPlugin({
  rootDir,
  useCSSLayers: true,
})({
  transpilePackages: ['@stylexjs/open-props'],
});

2-2. StyleX の動作確認をする

StyleX のセットアップは以上ですが、動作確認のためコンポーネントを作成しておきます。

src/components/ButtonWithStyleX.tsx
import stylex from "@stylexjs/stylex";

const buttonStyle = stylex.create({
  button: {
    backgroundColor: "red",
  },
});

const ButtonWithStyleX = () => {
    return <button className={stylex(buttonStyle.button)}>Hello StyleX</button>
}

export { ButtonWithStyleX }

ButtonWithStyleX を使うように、index.tsx を書きかえます。

src/pages/index.jsx
@@ -1,4 +1,5 @@
 import { ButtonWithEmotion } from "@/components/ButtonWithEmotion";
+import { ButtonWithStyleX } from "@/components/ButtonWithStyleX";
 import Head from "next/head";

 export default function Home() {
@@ -19,6 +20,14 @@ export default function Home() {
             <ButtonWithEmotion />
           </div>
         </div>
+        <div>
+          <div>
+            <span>StyleX</span>
+          </div>
+          <div>
+            <ButtonWithStyleX />
+          </div>
+        </div>
       </main>
     </>
   )

ビルドして動作を確認します。

npm run build
npm run start

http://localhost:3000/ を開くと、下図のように StyleX で記述したコンポーネントが正しく表示されました。Emotion で記述したコンポーネントも問題なく表示されています。


StyleX で記述したコンポーネントの実行結果

まとめ

どこかで思わぬコンフリクトが発生するのではないかと思いましたが、少なくとも今回のような単純なアプリケーションではうまく動作しました。

構築したアプリケーションのリポジトリはこちらです。

https://github.com/shioyang/nextjs-with-emotion-and-stylex

通常、ひとつのアプリケーション内では、用途に合わせて最適な CSS ライブラリを選定すべきでしょう。複数の CSS ライブラリが使用できたとしても、複雑性の増加・可読性の低下・スタイルの衝突・メンテナンスコストの増加などのデメリットが大きくなるからです。

しかし、限定的な以下の状況では共存させるメリットがあります。

  • 異なる CSS ライブラリを使用したコンポーネントがあり、書きかえのコストを払えない
  • 一部のコンポーネントでは CSS ライブラリごとの特長を最大限に活かしたい
  • 一方の CSS ライブラリから他方へ段階的に移行したい

この記事が皆さんの開発に少しでも役立つことを願っています。もし何か質問やコメントがあれば、ぜひ聞かせてください。また、他の面白い技術の組み合わせなどもシェアしていただけると嬉しいです。

2024 年も挑戦と成長に満ちた良い年にしていきましょう!!!


脚注
  1. https://stylexjs.com/blog/introducing-stylex/ ↩︎

  2. https://nextjs.org/docs/getting-started/installation ↩︎

  3. https://github.com/emotion-js/emotion/issues/2928 ↩︎

  4. https://emotion.sh/docs/typescript#emotionreact ↩︎

  5. https://stylexjs.com/docs/learn/installation/#production ↩︎

  6. https://github.com/facebook/stylex/blob/main/apps/nextjs-example/.babelrc.js ↩︎

  7. https://github.com/facebook/stylex/blob/main/apps/nextjs-example/next.config.js ↩︎

株式会社ログラス テックブログ

Discussion