CDN利用時の注意¶
CSSカスケードレイヤー導入時の、CDN経由のCSSの扱いについて説明します。
CDN利用時の問題点¶
@layer を導入したプロジェクトにCDN経由のCSSを取り込む場合、次のような問題が発生する可能性があります。
CDNのCSSは、通常 @layer が指定されていないことが多い(非レイヤーCSS)
↓
非レイヤーCSSは、 @layer 管理されたCSSより優先度が高い
↓
CDNのCSSを、 @layer 指定されたCSSから !important 無しで上書きできない
スライダーなどのプラグイン付属のCSSは、デザインによって上書きすることが多いため、CDNを利用する場合は、上書きの方針を定めておく必要があります。
※ !important 利用については「解決方法-3」で詳しく述べます。
取り込むファイルが @font-face 指定などの「実質グローバルになるアットルール」に該当し、上書きが必要ない場合はこの限りではありません。
CDNの利用方法¶
CDNのCSSをHTTP経由で取り込む方法は、次の2つのうちどちらかになります。
- CSSファイルから
@importで取り込む - HTMLファイルから
linkタグで取り込む
1・@import で取り込む¶
1 は、 @import url("https://cdn...") layer(...); のように、layer() 付きの @import で取り込む方法です。
読み込みが直列になることによるパフォーマンス面を許容できる場合、ネイティブCSSと PostCSS(postcss-import)では、CDNのCSSを直接レイヤーに所属させられます。
この場合、@import は @layer の宣言文の直後(他のルールより前)に記述する必要があります。
一方、Sass では利用できません。Sass はプレーンCSSの @import を出力ファイルの先頭に移動するため、@layer の宣言より先にインポート先のレイヤーが登録され、宣言した順序が壊れます。
2・link タグで取り込む¶
2 は、一般的なCDNの利用方法です。
以下から、2 の方法について、問題を整理して対策方法をまとめます。
CDNのCSSをHTTP経由で直接取り込まずに、ファイルとしてコピー配置する場合は、 外部製CSSの取り込み をご確認ください。
@layer 導入時のHTMLでの読み込み¶
プロジェクトのCSSを全て @layer に所属させている状態において、CDN経由で非レイヤーのCSSをHTMLから取り込む場合を考えてみます。
一般的なHTMLでの読み込み順は、次のようになります。
<!-- 1・CDN経由のCSS(内部は非レイヤーのCSS) -->
<link rel="stylesheet" href="https://cdn.example.com/plugin.css">
<!-- 2・全域用(内部は全て@layer指定済) -->
<link rel="stylesheet" href="/assets/css/global.css">
<!-- 3・ページ個別用(内部は全て@layer指定済) -->
<link rel="stylesheet" href="/assets/css/pages/product.css">
上記では、CDNのCSSを最初に読み込んでいます。
通常であれば、後続する 2 と 3 のファイルでCDNのCSSを上書きできます。
しかし、@layer の導入環境下では、ファイルの読み込み順序に関係なく、必ず非レイヤーのCSS(1のCDNのCSS)の方が、@layer 指定のCSS(2 と 3)より優先されます。
つまり、この状態では、いくら 2 と 3 のCSSファイルで、1 に対する上書き用のCSS(通常のスタイル)を書いても効きません。
この問題の解決方法は3つあります。
解決方法-1: 上書き用のCSSを追加で読み込む¶
次のように、1の後に、同じ非レイヤーCSSを読み込んで、そのファイルから上書きします。
<!-- CDN経由のCSS(非レイヤー) -->
<link rel="stylesheet" href="https://cdn.example.com/plugin.css">
<!-- 非レイヤーCSS上書き用(非レイヤー) -->
<link rel="stylesheet" href="./assets/css/override-cdn.css">
@charset "UTF-8";
/* CDN の plugin.css を上書き */
.plugin-element {
color: #333; /* 非レイヤー同士なので後で読み込むCSSで上書きする */
}
上書きの役割を明確に分離できますが、広範囲に及ぶ場合は管理が煩雑になります。
解決方法-2: CSS内部の記述を工夫して解決する¶
解決方法-1のように別途ファイルを読み込むことなく、 global/ や pages/ に配置したCSS内部に、CDNの上書き用のCSSを記述する方法です。
部分的に @layer のブロックから外して「非レイヤー」のCSSとして記述します。
@charset "utf-8";
/* CDN の plugin.css を非レイヤーとして上書き */
.plugin-element {
color: #333;
}
/* @layerのCSS */
@layer pages {
.object-1 { ... }
.object-2 { ... }
.object-3 { ... }
}
上記は pages/ のCSSですが、global/ の場合は、global.css にて、概念レイヤーを包括的に @layer に所属させる方法を標準としています。
各パーシャルファイルのCSSは必ず @layer に所属するため、別途、非レイヤー上書き用のファイルを作成し、追加で読み込む必要があります。
@charset "utf-8";
@use "sass:meta";
@layer base , layout , components , pages , utilities , print;
/* 各レイヤーの `_index.scss` を読み込み */
@layer base { @include meta.load-css("./global/base/"); }
/* 〜中略〜 */
@layer print { @include meta.load-css("./global/print/"); }
/* CDN上書き用の `_override-cdn.scss`(非レイヤー)を読み込み*/
@include meta.load-css("./global/override-cdn");
どちらの場合も、従来のカスケード仕様で「後勝ち」になるよう、HTMLでは先にCDNのCSSを読み込んでおく必要があります。(冒頭の ”読み込み順の例” のコードを参照)
解決方法-3: !important で上書きする¶
@layer 内のCSSに !important を付ければ、対策をしなくても非レイヤーのCSSを上書きできます。これは、次の仕様によるものです。
- CDNが通常宣言なら、
!importantは無条件で勝つ - CDNが
!important付きでも、!important同士はレイヤーの強弱が反転する
→ レイヤー所属の!importantがレイヤー外の!importantに勝つ
@layer pages {
.plugin-element {
color: #333 !important; /* 技術的に問題なく優先される */
}
}
1〜数か所だけの場合や、今すぐ直したい緊急対応の場合に有用です。
常態化してきたら、解決方法-1、2 への置き換えを推奨します。
将来的な対応(参考情報)¶
将来、CDN利用の問題はスマートに解決できるようになるかもしれません。
HTMLの <link> 要素において、直接 layer 属性を指定する方法です。
<link rel="stylesheet" href="https://cdn.example.com/plugin.css" layer="library">
まだCSSWGでも提案段階の状態のようですが、問題なく利用可能になった場合は、CDNのCSSをHTMLで読み込んだ段階で、任意のCSSレイヤーに組み込めます。