🔖

Next.js(App Router)の環境にEmotionを導入したときに躓いたこと&周辺知識

2024/01/25に公開

React、Next.jsともに最近触り始めた者です。
Next.jsもApp Routerから触り始めたのもあって、環境構築から色々躓いたので自分の理解を深めるがてら記事にしておこうと思います。

この記事で触れること、知れること

  • 2024年1月時点でNext.js(App Router) w/ Emotionの環境構築と注意点
  • JSX pragmaって何
  • RSC(React Server Component)とSSRの違いがよく分かる記事
    「React Server ComponentsとApp Routerをそろそろちゃんと理解したい」
    https://qiita.com/newbee1939/items/7ce919f9a1a7153582b8
    (めっちゃ分かりやすかったのでサマリにも書きたかった)

環境

Next.js 14.1 App Router
TypeScript 5.3.3
Emotion 11

インストール手順と解説

公式で紹介されている手筈でインストールを行います。

npm install

npm install --save @emotion/react

https://emotion.sh/docs/install


TypeScript側での設定

下記をtsconfig.jsonに記載します。

- "jsx": "preserve"
+ "jsx": "react-jsx",
+ "jsxImportSource": "@emotion/react"

https://emotion.sh/docs/typescript

公式のページで冒頭に記載されていることはこんな感じ
  • Emotionは@emotion/reactと@emotion/styledにTypeScriptの型定義を含んでるよ
  • その型定義はCSSプロパティをオブジェクト形式で記述したときに型推測してくれるよ
  • tsconfigに設定したJSX Pragmaの設定のお陰で、ファイルの冒頭にJSX Pragmaを記述しなくても良くなったよ(意訳)
// Before: /** @jsx jsx */というpragma(コンパイラへの司令)を記述する必要がありました
/** @jsx jsx */
import { css } from '@emotion/react'

const titleStyle = css({
  boxSizing: 'border-box',
  width: 300,
  height: 200
})


// Now: pragmaの記述が不要になりました、やったね!
import { css } from '@emotion/react'

const titleStyle = css({
  boxSizing: 'border-box',
  width: 300,
  height: 200
})

Emotion includes TypeScript definitions for @emotion/react and @emotion/styled. These definitions infer types for css properties with the object syntax, HTML/SVG tag names, and prop types.

The easiest way to use the css prop with TypeScript is with the new JSX transform and the jsxImportSource TSConfig option (available since TS 4.1). For this approach, your TSConfig compilerOptions should contain


上記の設定をするのですが、このうち"jsx": "react-jsx"の設定についてはnpm run devやnpm run buildをしたときに、下記のようにNext.js側で強制的に修正されてしまいます。
「The following mandatory changes were made to your tsconfig.json」に示されている通り、これはmandatoryな(必須な)修正のためそのままにしておきます。

We detected TypeScript in your project and reconfigured your tsconfig.json file for you. Strict-mode is set to false by default.
The following mandatory changes were made to your tsconfig.json:

    - jsx was set to preserve (next.js implements its own optimized jsx transform)

おそらく、Emotion側でこのセットアップを紹介しているのはNext.js以外の環境も考慮してのことなので"preserve"のままでOKと認識しています。

設定値の解説: tsconfig.jsonのjsx

今回の設定値 "jsx": "react-jsx"
TypeScriptがReactのJSX構文をどの変換器(コンパイラ)で処理するかを指定する設定です(JSX pragmaとも言えるかも)。

Emotionを導入する場合"react-jsx"のコンパイラを使用してねということ。この設定によって、Emotionの記述が正しく変換されます(が、Next.js環境下では前述のとおりなので、"preserved"にしておきましょう)。

設定値の解説: tsconfig.jsonのjsxImportSource

今回の設定値 "jsxImportSource": "@emotion/react"
JSX pragmaの変換器(コンパイラ)をどこから持ってくるのかという設定です。


JSXとかpramgaってなんだっけという方向けのおさらいと補足

JSXとは

  • JavaScript XMLの略
  • JavaScriptの拡張構文(syntax extension)
  • HTMLのようなマークアップをJavaScript内で記述することができる
  • React.createElement(componet, props, ...children)を呼び出している
  • ブラウザで実行される前に、トランスパイラであるBabelなどで通常のJavaScriptに変換される

JSX is a syntax extension for JavaScript that lets you write HTML-like markup inside a JavaScript file. Although there are other ways to write components, most React developers prefer the conciseness of JSX, and most codebases use it.
https://react.dev/learn/writing-markup-with-jsx

JSX とは、つまるところ React.createElement(component, props, ...children) の糖衣構文にすぎません。例として、次の JSX コードを見てみましょう。
https://ja.legacy.reactjs.org/docs/jsx-in-depth.html


JSX pragmaとは

JSXの構文自体をブラウザはサポートしていないため、そのままの記述ではブラウザはJSXを解釈(実行)することができません。ブラウザが読めるように(つまり、JSXをプレーンなJavaScriptに)変換する必要があります。
その変換方法の指定すること(司令のこと)をJSX pragmaと言います。

変換方法の指定ということで、もっと具体的に言及しようとすると「JSX → プレーンなJavaScriptの変換を行うための”関数を指定すること”」とも言えるかと思います。

通常、ReactのJSXは React.createElement という関数に変換されますが、JSX pragmaを記述することで、別の関数に変換することができます。
こちらはTypeScriptのドキュメントではありますが、もう少し詳しく知りたい方はここらへん読んでみると理解が深まるかもしれません。
https://www.typescriptlang.org/docs/handbook/jsx.html
JSX_pragma_modes

私は新参者でこれまでのReactのコンテキストが無いために、JSXの解析がどうとかっていうのを読んでみても「ふーん」程度でしたが、ある程度React触っている方なら色々繋がって面白いかも知れませんね。

あと、違うライブラリですが個々の記事も分かりやすかったです。
(理解の助けになったので紹介したかった)
https://www.gatsbyjs.com/blog/2019-08-02-what-is-jsx-pragma/

pragmaという言葉について

CSの文脈においてpragma(プラグマ)とは、コンパイラ側に渡す司令のことだそうです。
”これこれこんな感じでコンパイル(変換)して”という記述のことをpragmaというわけですね。
JSX pragmaでは、JSXという構文をどのように処理してほしいかをコンパイラ側に教えてあげているということになります。

コンピュータ・プログラミングにおいて、ディレクティブまたはプラグマ(「プラグマティック」から)は、コンパイラ(または他の翻訳者)が入力をどのように処理すべきかを指定する言語構成要素である。
In computer programming, a directive or pragma (from "pragmatic") is a language construct that specifies how a compiler (or other translator) should process its input.
https://en.wikipedia.org/wiki/Directive_(programming)


'use client'しろと怒られる場合

下記の記事で触れられている通り、現状ではCSS-in-JSライブラリであるEmotionはNext.jsの全ての機能をサポートしているわけではありません。なお、こちらの記事は2023年7月時点の情報です。
https://zenn.dev/aplulu/articles/next-13-app-directory-emotion

私はこれを知らずに使っていて、画面にHello Worldを出力するだけのpage.tsxでuse clientしろと言われてずっと混乱していました。use clientしろというログにEmotionが関係してそうな情報があったので、理解できるに至りましたが、、、

export default function Home() {
  return <div>hello world</div>;
}

use client error

2024年1月末の応状況について

  • Emotion:Next.jsのApp Routerの仕組みには対応した
  • Emotion:RSC(React Server Component)は対応中のため、使うならuse clientが必要
  • Next.js:CSS in JS頑張ってるなう
情報元:Next.jsとEmotionの対応状況

Next.js公式:CSS-in-JS

The following are currently working on support:
emotion
https://nextjs.org/docs/app/building-your-application/styling/css-in-js

Emotion公式:Plans to support Next.js 13 - /app directory #2928

As far as Material UI is concerned:

  • Emotion is fully compatible with the Next.js App Directory, per Plans to support Next.js 13 - /app directory #2928 (comment). There might be bugs to fix still, but I haven't seen them in practice yet: Library Upgrade Guide: <style> (most CSS-in-JS libs) reactwg/react-18#110.
  • Now, the next problem to solve is RSC: Emotion in React Server Components? #2978.
    https://github.com/emotion-js/emotion/issues/2928

Emotion公式:Emotion in React Server Components? #2978
https://github.com/emotion-js/emotion/issues/2978

Next.jsのApp Routerではデフォルトで(何も記述しない状態で)RSCとして扱う挙動のため、RSCにEmotionが対応していない&tsconfigで各ファイルに暗黙的にJSX Pragmaの記述がされている環境では、ほとんどのファイルにuse clientをつけていかないといけないです(認識違ってたら教えてください)。

Emotion側の目線ではtsconfigにJSX pragmaの設定をしてファイルの冒頭にJSX Pragmaの記述をしなくて良くなったけど、そうするとデフォルトでRSCのNext.js環境下ではほとんどのファイルにuse client書かないといけなくなって、使いやすさという観点ではうーんという感じもします笑
あと一番気になるのは、デフォルトでRSCとして扱うNext.jsの思想や流れとも違うことをしている点でしょうか。そういう意味でもEmotion側のRSC対応待ちの状況です(もちろん、プルリク出してその流れを加速させるのもいいですね)。
https://github.com/emotion-js/emotion


なお、RSCとかSSRってなんだっけという方はこちらの記事がすごく分かりやすかったのでオススメです。
https://qiita.com/newbee1939/items/7ce919f9a1a7153582b8#rscreact-server-componentsとは何か

Discussion