CSS Module Scripts の夢を見る・script 要素の書き方による動作の違いまとめ

CSS Module Scripts という仕様が出来て、JS (ESModules) から CSS ファイルを import して適用できるようになったらしい。

Webpack によるビルドを前提にした React プロジェクトなんかだと、以前から「CSS Modules」という形で JS から CSS を import して適用する書き方があったが、CSS Module Scripts というのはそれとは別物。ビルドの必要はなく、ブラウザのみで動作するネイティブな仕様だ。

コードとしてはこんな感じで書く。

<script type="module">
import styles from './styles.css' assert { type: 'css' };
document.adoptedStyleSheets.push(styles);
</script>

import 構文は ESModules の中でしか書けないので、type="module" が必須。

assert というところが独特だ。assert 部分ではファイルタイプを指定しているが、CSS の他にも JSON ファイルを import する時にファイルタイプを指定するために策定されている模様。

んで、読み込んだスタイル (変数 styles) をページに適用するには、document.adoptedStyleSheets に追加してやれば良い。コイツは配列で、

document.adoptedStyleSheets = [styles];

こんな風にも書いたりできるのだが、最近 push() 関数が生えたので、コレを使うと「既存のスタイルはそのままに、スタイルの追加 (カスケード)」ができるようになる。

2022年6月現在、この書き方によって CSS を適用できるのは Chrome 系ブラウザのみで、Firefox や iOS Safari では上手く適用できなかった。


ところで、type="module" を書いた script 要素って、どのタイミングで読み込みと実行が入るんだろう?あと、外部 JS ファイルを読み込む場合と、インラインに JS を書く場合とに違いはあるのか?head 要素に書いた時と body 要素の最後に書いた時とでの違いは?asyncdefer とかもあった気がするけどイマイチ分かっていない…。

ということで、以下のデモページでは CSS Module Scripts の動作確認と、色んな script 要素の書き方をセットで試している。開発者コンソールを確認してみてほしい。

<script> の書き方の違いによる動作の違いは、

のタイミングがいつになるのか、と、HTML パースを中断するかどうかが変わってくる。つまり src="" 属性を書かないインライン JS に対して async とか defer とかを書いた時は「JS ファイルの読み込み」に関しては関係してこないようだ。

簡単にまとめると以下のとおり。CSS Module Scripts に関連する type="module" (ESModules) に関してだけ言うと、コレは defer 属性を付与したのと同じ動きをする。

「簡単にまとめると」とは書いたが、全然簡単じゃないよな…w

JS を書いて HTML DOM を操作したい、という一般的な用途で考えると、SPA のように全てが JS によって操作される場合なら、<script><script defer> を書いて、DOMContentLoaded 直前から JS を実行させるようにするのが良いだろう。

んで、ESModules のような新しい記法を使いたいのであれば <script type="module"> を使い、注意点は defer と同じ、と覚えておく。

最後に、<script async> は普段使わないから、覚えなくてよしw。「HTML パースを邪魔せず読み込ませつつ、即実行したい」という場面があるとすると、Google Analytics みたいにページの内容に関係なくバックグラウンドで動かしたいモノになるだろう。ウェブアプリみたいな想定だとイマイチ使いづらいというか、実行タイミングが保証されなくて安心できないと思う。w


つーワケで CSS Module Scripts から脱線したけど、JS だけで CSS まで上手い具合に扱えるようになったら、何か可能性広がりそうではあるなーと思って、まだ Chrome 系でしか動かない仕様を試してみた。以下、参考文献。