Next.js と Emotion でいろいろハマった件 2

Nextjs で export する時に、Emotion の出力結果を <head> にインライン展開する方法についてです。
また、PostCSS が効かなかったので、ビルド時に Autoprefixer を実行するように対応してみた内容もまとめました。

Version

  • TypeScript 4.0.5
  • Next.js 10.0.1
  • React 17.0.1
  • Emotion 10/11

インライン展開したい

ずっと npm run dev で作業していて全然気が付かなかったやつ。

Emotion で書いた CSS は何もせずに next build でビルドしても描画されるのですが、そのままではインライン展開されていません。

インライン展開するには Example に倣って emotion-server パッケージで extract する必要がありました。

next.js/examples/with-emotion at master · vercel/next.js · GitHub

_document.jsstatic async getInitialProps(ctx) { } をそっくりそのまま使います。

AutoPrefixer を使いたい

Emotion は内部で Stylus を使用しているそうで IE11 用のベンダープレフィックスは付与されません。

Next.js は Autoprefixer がビルトインされているのですが、emotion-server でインライン展開したものにうまく適用させることができませんでした。

そのため、インライン展開の時 emotion-server から出力される CSS に直接 Autoprefixer を当てることにしました。

私がやったのは以下のような感じです。

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

npm install -D autoprefixer postcss

適当なフォルダにスクリプトを用意する

今回は Warning については console.warn ではなくログファイルに出力しています。

add-prefix.js

const autoprefixer = require('autoprefixer');
const postcss = require('postcss');
const fs = require('fs');
const path = require('path');

export async function addPrefix(css) {

  const log = path.join(process.cwd(), 'logs/autoprefixer.log');
  fs.writeFileSync(log, '');

  return postcss([
    autoprefixer({
      flexbox: 'no-2009',
      grid: "autoplace",
      overrideBrowserslist: [
        "last 1 version",
        "> 1%",
        "ie 11"
      ]
    })
  ]).process(css).then(result => {
    result.warnings().forEach(warn => {
      // console.warn(warn.toString());

      // logging
      fs.appendFileSync(log, `${warn.toString()}\n`);
    });

    return result.css;
  });
}

emotion-server の出力結果に対してスクリプトを適用する

_document.tsx

...
import { addPrefix } from '../適当なフォルダ/add-prefix';

...

export default class AppDocument extends NextDocument<Props> {
  static async getInitialProps(ctx) {
    const initialProps = await NextDocument.getInitialProps(ctx);
    const styles = extractCritical(initialProps.html);
+   const postCss = await addPrefix(styles.css);
    return {
      ...initialProps,
      styles: (
        <>
          {initialProps.styles}
          <style
            data-emotion-css={styles.ids.join(' ')}
            dangerouslySetInnerHTML={{ __html: postCss /* styles.css */ }}>
          </style>
        </>
      )
    }
  }
  ...

あとがき

関わっていた案件のターゲットブラウザに IE11 が入っていたのに Grid バンバン使ったのが良くなかった。

関連記事
unimoku

Web サイトの制作、運営とアプリケーションのフロントエンド開発などをやっています。主に使うのはTypeScript、JavaScript、PHP、C/C++。特にTypeScriptが好きです。