🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

DOM ready events considered harmful | HTTP 203 の要点まとめ

なかなか気になるタイトルの動画が出てたので。

DOM ready events considered harmful | HTTP 203 - YouTube

こういうコード、ほんとに懐かしいな・・・w

// コレとか
$(() => {});

// コレとか
document.addEventListener("DOMContentLoaded", () => {}, false);

これまではめちゃめちゃよく使われてきたけど、実はよろしくないよっていう話。

TL;DR

  • あるあるコードの問題
  • `bundle.js`内で、DCLを待って処理するようにしてた場合
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css" />
    <script src="bundle.js"></script>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
// bundle.js
document.addEventListener("DOMContentLoaded", () => {
  // 処理...
}, false);
  • コードが運用されるにつれ、こう改修されるかもしれない
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css" />
    <script src="bundle.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <!-- ↓追加したよ -->
    <script defer src="https://cdn.example.com/analytics.js"></script>
  </body>
</html>
  • こんな風に3rdの`defer`なスクリプトが追加されたりするとどうなるか
  • メインで実行すべきコード(DCL)がどんどん後回しになる
  • どうすればいいか?
  • DCLを待つのをやめて、`bundle.js`にも`defer`をつけるのがよい
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css" />
    <script defer src="bundle.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script defer src="https://cdn.example.com/analytics.js"></script>
  </body>
</html>
// bundle.js

// 処理...
  • 基本的にはコレでよい

DOMContentLoadedへの道のり

  • ブラウザのしくみとして、ページがパースされていくにつれ、何が起きるか
  • 1: `readyState: interactive`
  • 2: 既存のstyle(media matches含む)の処理
  • 3: `defer`な`script`すべてを順番に実行
  • 4: `DOMContentLoaded`イベント発火
  • 5: `readyState: complete`
  • 6: `load`イベント発火
  • 7: `pageshow`イベント発火
  • こうしてみると、`DOMContentLoaded`とは?という感もある
  • なので基本的に`defer`を使えばよい
    • `type=module`にすれば、デフォルトで`defer`になる
    • 記述された順に実行されるので、依存ライブラリなどは先に

deferのこれまで

  • 1997年にIE4にはいった
  • しかしバグがあって、複数のdeferの実行順序が変わるケースがあった・・・
  • 2012年のIE10までバグってた・・・

asyncもあるよ

  • いつでも`defer`でいいわけではない
  • `async`のほうがいいときもある
    • 3rdのスクリプトのように優先度が低いもの
    • 単体で完結するようなもの
    • `defer`みたく読み込み順を待つ必要がないもの
  • `async`で、その実行タイミングをブラウザに任せてしまう
    • 優先度を上げたいならpreloadするとか
  • 本当に必要だったのは特定の要素がreadyかどうかのはず
  • 例えばWebComponentsで書けば、`connectedCallback`が使える
  • ただし、`innerHTML`みたいに子要素を使って何かしたい場合、`async`では不都合が起きるかもしれない
    • `async`なスクリプトが実行されるタイミングで、子要素がちゃんと存在する保証がないから
    • 親はあるので、親要素に対して動作するWebComponentsを書くというテクもある
  • `MutationObserver`も使える
  • DOMのアップデートを検知できるので、それで特定の要素の存在を検知する

というわけで、`defer`を啓蒙する動画でした!