フロントエンドエンジニアのtakeuchimです。日々アイスタイルのサイトUIに関するHTML、CSS、JSの運用・新規開発などをしています。
ユーザやブランドさんが利用するおおよそ30くらい開発環境を受け持っていますが、古い開発環境もたくさんあり、いろんな人がいろんな思い(設計)を込めてきたものが後で思いがけず表示事故を招くことが多々見受けられます。
最近ではWeb vitals、表示速度などの対応で、いろんなものが掘り起こされ、表示崩れの事故を将来招きそうな実装の例を発見しているのでこれらがもう生み出されないように記事をまとめておきたいと思います。
アイスタイルで見つけたHTML、CSSのスコープ(影響範囲)が危険な実装の例
ここ3年くらい見守ってきた中で多く事故につながっている、つながりそうな危うい実装には、スコープ(影響範囲)が考えられていないという共通点がありました。
アイスタイルで見つけた、スコープ(影響範囲)が大解放されてる危険な実装例を見ていきたいと思います。JavaScriptなどのプログラムではよくスコープなどは語られますが、ここではHTML、CSSをメインに見ていきます。
●範囲が大きい要素型セレクタでスタイルを指定
ブラウザデフォルトのスタイルをリセットするために、要素型セレクタを利用するのは仕方ないとは思います。
画面用のCSSに下記のように設定してしまうと、運用時に後から別のp要素を追加した際に、違うスタイルをあてたいときに、これをさらに上書きしてなど調整が必要になります。
さらに、今はだしわけの都合上HTMLが表示されてなかったが、実はある条件で表示されるHTMLの中にp要素があり影響を受けて表示事故なんてことも。
たとえclassセレクタなどだったとしても、範囲が大きいセレクタは今はよくてものちのちどうなるか分からなくなるためおすすめできないです。
■記述されたファイルが読まれている画面のp要素が対象になってしまう例
p {
color: #f00;
padding-left: 30px;
}
■メインカラムと思われるスコープがpの前に追加されたが、これでもまだ影響範囲がでかい例
#main p {
color: #f00;
padding-left: 30px;
}
●サイト全体で読まれている共通CSSに特定の画面のスタイル
あるサイト全体で読まれているcommon.cssにはリセットや全画面にあるヘッダー、フッター、ぱんくずなどの記述が入っていました。
それに加えて、特定の画面のみで利用されているであろうスタイルが混ざっているのを発見。
影響範囲は読まれている全画面になってるだろうけど、多分追加するclass名を開発環境内で検索などせずにおそらく追加したと思われます。もしどこかの画面のclass名と被っていたとしたらと背筋が凍りました。
後でリファクタしようにも共通CSS触るので、全体的に調査して削除するのが問題ないかどうか確認する工数莫大な遺産です。
●知らない間に違う画面にCSSなどが読み込まれていた
なんでこのファイルがこの画面に読まれているのか紐解いたときに発見した例です。
あるあるな気はしますが、ある画面のこのモジュールをこの画面にも使いたいからHTMLはコピーして持ってきて、CSSは同じファイル追加で読んでおけばいいかなものでした。
直近はあまり見かけないんですが、フロントエンド側が意図せず勝手に違う画面に読み込みされしまったパターンです。
知らない間に依存関係が生まれてしまい、さらにこの事情を知らない人が読まれたファイルにいろいろ追記していったとしたら、どうでしょうか。さらにカオスになっていくのが想像できると思います。
このファイルを元の画面とあとで読まれた画面から引きはがす調査とリファクタの工数と考えると怖いです。
●違う開発環境のファイルを参照していた
あるサイトのロゴが表示されないと表示事故の連絡を受けて衝撃をうけた例でした。
なんとhttpから始まるフルパスで違う開発環境からデプロイしてアップされる画像ファイルを参照していて、参照先のロゴが削除されてしまって404になってしまいました。
参照先の開発環境もまさかそんなところからアクセスされているとはですよね。
まだ大きな表示事故には至ってないですが、そのあとも画像ファイルだけでなくて、CSSファイルも例としてありました。
CSSファイルも影響範囲知らず勝手に削除、改修されてしまったとしたら大きな表示崩れを招くことになるので、フロントエンド内には周知し、ハードコピーして依存性をなくすリファクタをする予定です。
アイスタイルでの主なスコープ(影響範囲)制御の手法の例
VueやNuxt.jsは少し違ったところがあるかもですが、通常の大規模サイトではスコープを限定するために、下記の手法を取り入れてます。
多くの受け持ちサービスがあり、ある程度メンバーは固定しますが、いろんな人がいろんなサービスを担当する必要があるので、複雑で覚えにくいものは取り入れていません。
●画面IDで影響範囲をその画面に絞り込む
CSSシグネチャと呼ばれて古くからある手法ですね。bodyかその画面のメインコンテンツ部分にid、classを付与して、スタイルのセレクタの頭に入れるというシンプルなものです。
その画面のファイルの中身はその画面のメインコンテンツ部分のみへの影響範囲に絞ることができます。
■メインコンテンツ部分のHTML
<div class="profile-info">
~メインコンテンツ部分省略~
<!-- /profile-info --></div>
■画面のCSSファイル
.profile-info .hoge {
}
●画面ごとのCSSファイルを作成、読み込む範囲を限定(関係ない画面のファイルを読み込まない)
特定の画面はその画面用のファイルを作成してそこしか読まない状態にしたり、同じ画面グループになりそうなところは共通のCSSを作るかなど影響範囲を考慮して最小限の範囲にCSSファイルが読まれるように設計します。
●リセット以外はclassをあて、裸の要素は可能な限りセレクタに書かない
要素の中のspan1つくらいなら、CSSで見渡せる範囲なので可としてますが、明示的にどのHTMLとどのCSSが結びついているのかを分かりやすくすることで、他の人が見てもわかりやすい状態にします。
あるclass名の中にたくさんclassが設定されていない裸要素があるとのちのち個別に指定するのに複雑なCSSのセレクタを記述し他の人が見てわかりづらい、ふとしたHTMLのだしわけで表示崩れが起きるなどがあります。
■NGな例
.product-info div {
}
<section class="profile-info">
<div><h3>タイトル</h3></div>
<!-- /profile-info --></section>
■OKな例
.product-info .product-info-title {
}
<section class="profile-info">
<div class="product-info-title"><h3>タイトル</h3>
<!-- /profile-info-title --></div>
<!-- /profile-info --></section>
例にあるようなHTMLの構成ですが、将来的にdivがさらに増えたときにdivにclassを与えておけば、section.profile-infoの中にdivを含むHTMLを追加したときにこのdivにもスタイルが当たってしまいコントロールしずらくなりますよね。これを解決するために、すでに設定されていたdivにclassを与えるか、新しく追加するdivに派生classをあてて、値を上書きするとなりますが、のちのちこのようなことになるのであればはじめからclassで明示的に充てておいた方がメンテナンス性もよいかなと思います。
●モジュールの中のクラス名は一番上のclass名を接頭辞で付与する
基本的にはモジュールごとの一番ルートになる要素のclass名を下層のclass名の接頭辞としてつけます。
ネストするたびにclass名長くなりがちですが、このルールでつけていくことで、ほぼ被らないclass名にできます。
いろんなCSSにはいろんな命名規則がありますが、同じ開発環境をずっと同じ人が触るわけではないので、ぱっと見わかりやすいclass名をつけるということが大事です。
<section class="content-list">
<h2 class="content-list-title">タイトル</h2>
<p class="content-list-text">ああああ<span class="content-list-icon">あ</span>ああ</p>
<!-- /content-list --></section>
外側のモジュールはだいたいそのモジュールがなんなのかを表すclass名つける人が多いのかなと思うのですが、その中の要素にはシンプルなclass名、例えば「title」「close」「text」などつけることがありますよね。
外部の広告が書いていたり、基本そのようなことはないですが、サイト全体のCSSにそのまま書かれるなどしたら影響を受けて上書きされる可能性もあります。
●CSSではセレクタに必ずモジュールのルートを指定することで他モジュールへの影響範囲をなくす
上記のHTMLの構成にプラスして、CSSでもさらにスコープを絞ります。階層が深くなるたびにセレクタを指定しても
■静的CSS
.content-list .content-list-title {
}
.content-list .content-list-title-text {
}
■sass
.content-list {
.content-list-title {
}
.content-list-title-text {
}
}
ここで注意しなくてはいけないのは、間にあるclassを全部いれてしまうと最終的なセレクタが長くなってしまい、CSSの容量が重くなってしまうのと、sassで全部書くとネストが深くなり見づらくなっていくなどの問題もでてきます。
.content-list-title-textへの指定の仕方として、階層すべてをセレクタに入れると長くなるので、最低親のclassが入っていれば基本問題なしとしています。
外部に実装を委託しているやスコープを強固にしておきたい場合などは入れるという判断もあります。
■静的CSS
.content-list .content-list-title .content-list-title-text {
}
■sass
.content-list {
.content-list-title {
.content-list-title-text {
}
}
}
●違う環境のファイルを参照しない
違う開発環境のものを参照している場合、参照先が404になったり、勝手に変更が加わらないのを前提にしてなら問題はないかと思います。
参照された側も参照されているとは気づかないですから、不要になった時点で整理して削除されたり、改修が加わってこちらにどう影響するのかも分からないです。
同じものを利用する場合は、基本的にはハードコピーで対応です。
同期する必要がある場合は多少工数発生はしますが、のちのちの事故やサービス的に同期が不要になったり、関係者変わった場合に忘れ去られて事故になるくらいだったらましかなと思います。
同期に工数かかる場合は、社内配布パッケージにしてnpmインストールで対応できないかなど検討します。
●同じようなモジュールを他の画面で利用するときには、影響範囲を検討した上で違うclass名や違うCSSファイルを作成して依存性をなくす
これは「違う環境のファイルを参照しない」とも通じるところがあります。
この画面のこのモジュールをこっちの画面にも持って来たいという要望は、そこそこあります。
このときに単純に「このモジュールがあるCSSをじゃ違う画面にも読んで同じHTML使っちゃえばいいでしょ」という考えのもとやってはいけない例にもでてきた、予期しない画面にCSSファイルが読まれていたが起きます。
基本的には、対象画面にすでに用意してあるCSSファイルにハードコピーして、この画面にふさわしいclass名に変えたりという処理をしたほうが安全です。
同じモジュールならこの先UI改修などあるから同じものつかったほうがいいのでは?と思う人もいると思いますが、アイスタイルでは同時にすべてのUIを改修するということはないのでこの方が別々に管理できて管理上よいです。
共通化するのであれば、商品画面のグループのCSSにいれるのか、サイト全体に範囲を広げるのかはよく検討した上でディレクトリ構成やファイル構成など決定します。
スコープ(影響範囲)を絞ることによるメリット
●表示崩れを防止できる
これは当たり前だと思いますが、スコープが想像できないくらい大きくなっている場合は、ある条件でしか表示されないHTMLが表示されたや意図しない画面の表示が崩れていたなど地雷が仕込まれてるのと同じ状態です。
スコープ(影響範囲)を最小限にすることで、その地雷を完全に取りきるのは難しいかもしれませんが、0に近づけることは可能かと思います。
●実装前に影響範囲の調査をしなくていい
アイスタイル運用あるあるなんですが、この画面のこの部分に例えばアイコン足すだけ、バナー追加するだけのような小さな改修なんですが、実際作業してみたらCSSが他の画面でも呼ばれていて同じHTMLが利用されていて複雑なことになっていたということが結構あります。なので、実装する前に中身がすんなり実装できるのかも調査しなくてはいけません。
調査した結果、先に依存性をなくすやCSSファイルを分離したほうがいいとか、最悪このUIに調整できれば同じCSSが読まれている同じような画面で崩れないように調整できますなど調整をする必要がでてきます。
調査工数に加え、さらにリファクタも同時にする場合やデザインも調整する必要があるであればその工数も増えてしまうことになります。
●リファクタに工数かけなくていい
問題があるコードを発見したらリファクタしなくてはいけないですよね。
そのコードの影響範囲を絞るために影響範囲を把握した上で、調整しても大丈夫か調査し、影響している画面を洗い出し見た目影響がないかどうかも確認しなくてはいけなくて地味に大変な作業になります。
影響範囲(スコープ)を最小限に
自身が書いたコードがどこにどう影響を与えてしまうか分かっていない、影響すること自体に気づいてない方もい現実いたりします。
書いたコードの影響範囲を最小限にする、自身(あとで改修するだろう他の人)が見渡せる範囲にするというのが大規模サイトを長い間表示事故を起こさせないようにするコツかなと思います。
今をみるだけじゃなく、将来このコードがどうなっていくかを考えて運用に耐えられる実装を今後も考えていきたいと思います。
長年大規模サイトの運用をしてきて良いと思った手法をざっくり紹介させていただきました。この記事を読んで少しでも皆様のお役に立てると幸いです。