criticalで不等号ありのメディアクエリを扱う

こんにちは!このページでは、 JavaScriptのパッケージ「critical」を利用している中で、不等号ありのメディアクエリの扱い方で分かったことを共有します。

criticalは、コマンドラインインターフェース(CLI)で利用しています

試した環境

  • Ubuntu 24.04.2 LTS
  • npm 10.9.2
  • critical 7.2.1

検証のきっかけ

HTML・CSS・JavaScript で構成された静的サイトを構築するにあたり、critical を使って CSS をインライン化していました。また、CSS のコード量が増えてきたため、コードの品質を保つ目的で Stylelint も合わせて利用することにしました。

すると、Stylelint のチェック時に次のようなエラーメッセージが表示されました:

✖ Expected "context" media feature range media-feature-range-notation

これを受けて、使用していた @media (min-width: 768px) という指定を、推奨される指定 @media (width >= 768px) に変更しました。

ところが、この変更によって、critical による CSS のインライン抽出がうまく機能しなくなりました。不等号(>=)を含むメディアクエリが、critical に認識されなくなったのです。

この問題に対して、「Stylelint のルールを無効にする」「従来の構文に戻す」といった選択肢も検討しましたが、やはり新しい構文を活かしたまま対応したいと考えました。そこで、critical がこの形式を正しく処理できない理由と、解決方法を検証してみることにしました。

原因の分析

検証方法の検討

他の要因を排除し、問題の焦点をメディアクエリに絞るために、できるだけシンプルな構成の HTML および CSS ファイルを新たに用意します。問題が発生する条件と思われる @media (width >= 768px)を記載し、検証を進めることにしました。

また、CSSの抽出が正常に行えることを確認できれば、動作検証は可能と判断したため、inline化オプションは指定せず、標準出力の結果をもとに動作を確認することとしました。

テストファイルの準備

htmlを表示(test.html)
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>テストページ</title>
  <link rel="stylesheet" href="test-style.css">
</head>
<body> 
      <h2>テストタイトル</h2>
      <span class="sub-title">PC画面のみ表示するサブタイトルです</span>
</body>
</html>
cssを表示(test-style.css)
body {
  background-color: white;
  color: black;
}
h2 {
    margin: 0.25rem;
    font-size: 1.5rem;
}
.sub-title {
  display: none;
}
@media (width >= 768px) {
  .sub-title {
    display: block;
  }
}

用意した HTML ファイル(test.html)と CSS ファイル(test-style.css)は、dev ディレクトリの直下に配置しました。

テスト実行

devディレクトリ直下で以下のコマンドを実行します。

cat test.html | critical
実行結果を表示
#実行コマンドと結果
~/dev$ cat test.html | critical
Not rebasing assets for test-style.css. Use "rebase" option
body{background-color:#fff;color:#000}h2{margin:.25rem;font-size:1.5rem}.sub-title{display:none}~/dev$

テスト結果の確認

実行結果を見ると、@media を使ったメディアクエリの指定が出力されていません。
このことから、inline用の CSS が抽出されていないことが確認できました。

2回目のテストの準備

CSSファイルのメディアクエリの指定方法だけを変更して、再度テストを実行してみることにします。

ファイルを開いて編集することもできますが、効率よく作業を進めるため、以下のコマンドを使って、メディアクエリの指定を @media (width >= 768px) から @media (min-width: 768px) に一括で置き換えました。

sed -i 's/@media (width >= 768px)/@media (min-width: 768px)/g' test-style.css

テスト実行(2回目)

cat test.html | critical
実行結果を表示
#実行コマンドと結果
~/dev$ sed -i 's/@media (width >= 768px)/@media (min-width: 768px)/g' test-style.css
~/dev$ cat test.html | critical
Not rebasing assets for test-style.css. Use "rebase" option
body{background-color:#fff;color:#000}h2{margin:.25rem;font-size:1.5rem}.sub-title{display:none}@media (min-width:768px){.sub-title{display:block}}~/dev$

原因の特定

実行結果を見ると、@media の指定が表示されていることが確認できます。
つまり、この形式であればメディアクエリが正しく抽出されることがわかります。
このことから、やはり問題の原因はメディアクエリの記述方法にあったと特定できました。

対処方法を探る

ソースコードで仕様を確認してみる

GitHubで公開されているソースコードをざっくりと確認してみましたが、「こう書かなければならない」といった明確なルールや制限を示す記述は見当たりませんでした。
実際、メディアクエリは変数に格納されて処理されており、@media (width >= 768px)という指定が問題があるようには見えませんでした。これらの処理はPenthouseが担っているのかもしれません。

「うまくいく指定」と「うまくいかない指定」の違いに注目

それぞれを比較して気になったのは、記述の中に含まれている不等号(< )とイコール(=)の存在です。これが、想定外の挙動を引き起こしている可能性は否定できません。

そして、この「記号」と「想定外の挙動」というキーワードをきっかけに、過去の出来事を思い出しました。

空白が引き起こす予期せぬエラー

過去に私が直面したケースの一つに、変数の値に空白が含まれていたことによって、値が意図せず分割されてしまい、予期しないエラーが発生したというものがありました。

空白がデリミタ(区切り文字)として扱われることで、本来の意図とはまったく異なる処理結果になってしまったのです。

空白や改行、タブといった、制御文字や特殊文字とされるこれらの文字は、目に見えにくく、一見すると些細なものに思えますが、プログラム処理においては、記号以上に注意が必要な要素なのです。

空白に焦点を当てる

以下の5つのパターンで結果を比較してみます。px サイズとカラーを変化させ、処理結果がどのように異なるかを視覚的にわかりやすくします。

  • 1.空白なし : @media (width>=777px)
  • 2.不等号(<)の前に空白がある : @media (width >=666px)
  • 3.等号(=)の後に空白がある : @media (width>= 555px)
  • 4.等号・不等号の前後に空白がある : @media (width >= 444px)
  • 5.かっこの直後と直前に空白がある : @media ( width>=333px )

3回目のテストの準備

test-style.cssファイルに、この5つのパターンを追記します

テストパターン追記
echo '@media (width>=777px) { .sub-title { color: red; } } ' >>  test-style.css
echo '@media (width >=666px) { .sub-title { color: yellow; } } ' >>  test-style.css
echo '@media (width>= 555px) { .sub-title { color: green; } } ' >>  test-style.css
echo '@media (width >= 444px) { .sub-title { color: blue; } } ' >>  test-style.css
echo '@media ( width>=333px ) { .sub-title { color: orange; } } ' >>  test-style.css

テスト実行(3回目)

cat test.html | critical
実行結果を表示
#実行コマンドと結果
~/dev$ cat test.html | critical
Not rebasing assets for test-style.css. Use "rebase" option
body{background-color:#fff;color:#000}h2{margin:.25rem;font-size:1.5rem}.sub-title{display:none}@media (min-width:768px){.sub-title{display:block}}@media (width>=777px){.sub-title{color:red}}@media (width>=333px){.sub-title{color:orange}}~/dev$ 

✅導き出した解決策

分かれた結果

実行結果を確認すると、表示されたパターンと表示されなかったパターンがありました。
width>=777px と width>=333px のみが抽出されています。つまり、等号・不等号の前後に空白がないパターンのみが、正常に出力されていることがわかります。

一方で、等号・不等号の前後にいずれかに空白を記述してしまった場合は、CSS の抽出がされておらず、空白の有無が出力の可否に影響を及ぼしていることがわかります。

どのように記述すればよいか?

今回の検証結果から、@media (width >= 768px)と記述するのではなく、@media (width>=768px)と記述すれば、対応可能なことが明らかになりました。

📌今回のまとめ

一般的に、可読性を高めるためには、等号や不等号の前後に空白を入れるスタイルが好まれます。たとえば Python では、このような書き方が推奨されており、開発者の多くが自然にそのスタイルに慣れています。

そのため、自然とこれらの前後に空白を入れてしまうことも多いでしょう。「critical」と「不等号ありのメディアクエリ」との組み合わせに限っては、この記述をすると抽出されませんので、思わぬ落とし穴にはまってしまった感覚になるかもしれません。
今回、その回避方法(空白を詰めれば抽出される)は得られましたが、気になる点は残りました。

min-width を指定した場合、コロン(:)の後に空白があっても正しく出力される一方で、不等号(>=など )を使う場合だけ空白を入れてはいけない、というのはやや不自然に感じられます。

これは critical の挙動なのか、あるいは Penthouse の挙動なのかは定かではありませんが、不等号が正しく認識されず、画面サイズに応じた制御がうまく機能していないように見受けられます。

この点を正しく理解したうえで活用していくために、次回はこうした挙動について、もう少し掘り下げてみたいと思います。

👉 次回の記事を読む