背景
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
:true
true
- 構造の最適化を有効にするforceMediaMerge
:false
true
- メディアクエリ(@media
)をマージcomments
:true
false
- すべてのコメントを削除する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 を最適化できた。
最適化の結果はプラグインによって変わる。