背景
Emotion は CSS の出力にstylisというプロセッサを利用している。ただ、デフォルトでは最適化を行わないため、出力される CSS は冗長になってしまう。stylis のプラグインを利用することで最適化は可能そうであるが、あまりプラグインが公開されておらず、思ったような最適化ができるようなものは見つからなかった。
Emotion は CSS の出力にstylisというプロセッサを利用している。ただ、デフォルトでは最適化を行わないため、出力される CSS は冗長になってしまう。stylis のプラグインを利用することで最適化は可能そうであるが、あまりプラグインが公開されておらず、思ったような最適化ができるようなものは見つからなかった。
Next.js の SSR 時、Emotion が出力した CSS を事前に挿入するため pages/_document.tsx 内で以下のような定義をしている。
// pages/_document.tsx
// (中略)
const initialProps = await Document.getInitialProps(ctx);
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map(({ css, key, ids }) => {
return <style dangerouslySetInnerHTML={{ __html: css }} data-emotion={`${key} ${ids.join(' ')}`} key={key} />;
});
emotionStyles.styles.map 内の css は string であるため、この文字列を何らかで最適化できると考えた。
cssoを利用してみた。
インストールは以下の通り。
npm install -D csso @types/csso
pages/_document.tsx 内に csso を以下のように組み込んだ。
// pages/_document.tsx
// (中略)
const initialProps = await Document.getInitialProps(ctx);
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map(({ css, key, ids }) => {
const ast = syntax.parse(css);
const compressedAst = syntax.compress(ast, {
restructure: true,
forceMediaMerge: true,
comments: false,
}).ast;
const minifiedCss = syntax.generate(compressedAst);
return <style dangerouslySetInnerHTML={{ __html: minifiedCss }} data-emotion={`${key} ${ids.join(' ')}`} key={key} />;
});
css変数にはスタイル定義が文字列で入っているので、csso のsyntax.parseにそのまま渡すだけでパースできる。parse.compressで最適化を行い、parse.generateで文字列に戻している。
const ast = syntax.parse(css);
const compressedAst = syntax.compress(ast, {
restructure: true,
forceMediaMerge: true,
comments: false,
}).ast;
const minifiedCss = syntax.generate(compressedAst);
今回指定したparse.compressのオプションは以下の通り。
restructure:truetrue - 構造の最適化を有効にするforceMediaMerge:falsetrue - メディアクエリ(@media)をマージcomments:truefalse - すべてのコメントを削除するNext.js + Emotion の環境で csso を利用して CSS を最適化できた。
csso のオプションによるものだが、適応したソースだと以下のような最適化が行われた。
hsl()を利用していた箇所が HEX 値に変換されたPostCSS に加えて他プラグインを合わせて入れてみた。
pages/_document.tsx 内に PostCSS を以下のように組み込んだ。
// pages/_document.tsx
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
import postcss from 'postcss';
import combineSelectors from 'postcss-combine-duplicated-selectors';
import postcssSortMediaQueries from 'postcss-sort-media-queries';
// (中略)
const initialProps = await Document.getInitialProps(ctx);
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map(({ css, key, ids }) => {
const processedCss = postcss([
autoprefixer({
overrideBrowserslist: packageJson.browserslist,
}),
cssnano({
preset: ['cssnano-preset-advanced'],
plugins: [],
}),
combineSelectors({ removeDuplicatedProperties: true }),
postcssSortMediaQueries,
]).process(css).css;
return (
<style dangerouslySetInnerHTML={{ __html: processedCss }} data-emotion={`${key} ${ids.join(' ')}`} key={key} />
);
});
postcss関数の引数には PostCSS で利用するプラグインを配列で渡す。process関数には最適化したい CSS(string)を渡す。
Next.js + Emotion の環境で PostCSS を利用して CSS を最適化できた。
最適化の結果はプラグインによって変わる。