Next.js 10 + TypeScriptでEmotionを使う

# はじめに

Next.js 10になって、React 17になったりしてEmotionが動くようにするのに少し工夫が必要になりました。
その方法について、まだEmotionの公式のドキュメントではまとめられていないようだったので、試行錯誤した内容をここに記事として残そうと思います。

この記事で作り上げた環境はこちらに用意してあります。

リポジトリ: https://github.com/282Haniwa/next-with-emotion

# Next.jsのプロジェクトを作る

Next.jsの環境構築は[こちらの記事](https://qiita.com/282Haniwa/items/dcce1ba6bb6ae541893e)で解説しているので、省略させていただきます。

とりあえず動く環境で試したい方は、

```shell-session
$ git clone https://github.com/282Haniwa/next-example.git
$ cd next-example
$ yarn install
$ yarn dev
```

で環境を作ってください。


# Emotionを導入する

Emotionを動かすのに必要なパッケージを追加します。

```shell
$ yarn add @emotion/react
```

これで、コンポーネントを以下のように編集すると、Emotionを使うことができます。

```tsx
/** @jsxImportSource @emotion/react */

import React from 'react'
import { css } from '@emotion/react'

const hello = css`
  color: red;
`

type Props = {
  name: string
}

export const Hello: React.FC<Props> = (props) => {
  const { name } = props
  return (
    <div>
      <h1 css={hello}>Hello {name}!</h1>
    </div>
  )
}

```

公式では、

```tsx
// this comment tells babel to convert jsx to calls to a function called jsx instead of React.createElement
/** @jsx jsx */
import { css, jsx } from '@emotion/react'
```

のように`/** @jsx jsx */`と記述するようにとありますが、React 17からは
`pragma and pragmaFrag cannot be set when runtime is automatic.`
というようなエラーが出るようになっているので、この設定を無効化するように設定するか、`/** @jsxImportSource @emotion/react */`というようなプラグマにする必要があります。

全てのコンポーネントに`/** @jsxImportSource @emotion/react */`というjsxプラグマを記述することでEmotionを動作させることは可能ですが、毎回書くのはめんどくさいので`@emotion/babel-preset-css-prop`を導入して書かなくてよくしてしまいます。

まず、`@emotion/babel-preset-css-prop`を入れます。

```shell
$ yarn add --dev @emotion/babel-preset-css-prop
```

次に、`babel`の`presets`にこれを使うように記述して、`tsconfig.json`に`css` propの型がちゃんと設定されるように記述します。

```json:.babelrc
{
  "presets": ["next/babel", "@emotion/babel-preset-css-prop"]
}

```

```json:tsconfig.json
{
  "compilerOptions": {
    // ...
    "jsxImportSource": "@emotion/react"
  }
}

```

これでEmotionを使う準備ができました。
試してみましょう。

```tsx:Hello.tsx
import React from 'react'
import { css } from '@emotion/react'

const hello = css`
  color: red;
`

type Props = {
  name: string
}

export const Hello: React.FC<Props> = (props) => {
  const { name } = props
  return (
    <div>
      <h1 css={hello}>Hello {name}!</h1>
    </div>
  )
}

```

![スクリーンショット 2020-12-25 16.37.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/615997/b38dbbac-e963-222e-32bf-56564bc2db38.png)


ただし、Emotionで`:nth-child()`など、一部のセレクタを使いたい場合にSSRだとうまく機能しない場合があります。
`nth-child`などを使わない場合は、以下の設定をしないことを推奨されているので、必要ない場合はここまでの設定で大丈夫ですが、必要になる場合は次の設定を行ってください。


# SSRで`nth-child`などのセレクタを使えるようにする(オプション)

完成形: https://github.com/282Haniwa/next-with-emotion/tree/with-nth-child

cloneするにはブランチを指定してください。

```shell
$ git clone https://github.com/282Haniwa/next-with-emotion.git -b with-nth-child
```

まず、必要なパッケージを追加します。

```shell
$ yarn add @emotion/server @emotion/css
```

次に、`_document.tsx`と`_app.tsx`を修正します。

```tsx:_document.tsx
import React from 'react'
import Document, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentContext,
  DocumentInitialProps,
} from 'next/document'
import { extractCritical } from '@emotion/server'

export default class MyDocument extends Document {
  static async getInitialProps(
    ctx: DocumentContext,
  ): Promise<DocumentInitialProps> {
    const initialProps = await Document.getInitialProps(ctx)
    const styles = extractCritical(initialProps.html)
    return {
      ...initialProps,
      styles: (
        <>
          {initialProps.styles}
          <style
            data-emotion-css={styles.ids.join(' ')}
            dangerouslySetInnerHTML={{ __html: styles.css }}
          />
        </>
      ),
    }
  }

  render(): JSX.Element {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

```

```tsx:_app.tsx
import React from 'react'
import { AppProps } from 'next/app'
import { CacheProvider } from '@emotion/react'
import { cache } from '@emotion/css'
import 'src/styles/globals.css'

const MyApp: React.FC<AppProps> = ({ Component, pageProps }) => {
  return (
    <CacheProvider value={cache}>
      <Component {...pageProps} />
    </CacheProvider>
  )
}

export default MyApp

```

これで、`nth-child`などのセレクタも使えるようになりました。
コンポーネントを修正して試してみましょう。

```tsx:Hello.tsx
import React from 'react'
import { css } from '@emotion/react'

const hello = css`
  color: red;
  &:nth-child(2n) {
    color: blue;
  }
`

type Props = {
  name: string
}

export const Hello: React.FC<Props> = (props) => {
  const { name } = props
  return (
    <div>
      <h1 css={hello}>Hello {name}!</h1>
      <h1 css={hello}>Hello {name}!</h1>
    </div>
  )
}

```

![スクリーンショット 2020-12-25 16.36.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/615997/3301ea1e-5a60-930e-0bc6-42be4edaba81.png)


# まとめ

最後まで読んでいただきありがとうございました！

EmotionはReactで使えるCSS in JSライブラリの中で一番気に入っているライブラリなので、是非皆さんに使っていただきたいなーと思います！

# 参考

- https://emotion.sh/docs/introduction
- https://emotion.sh/docs/ssr
- https://github.com/vercel/next.js/tree/master/examples/with-emotion-11
- https://github.com/emotion-js/emotion/issues/2041