Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ブラウザの制約条件から考えるフロントエンドのリソース設計/Frontend Performance How to

forrep
September 26, 2019

ブラウザの制約条件から考えるフロントエンドのリソース設計/Frontend Performance How to

forrep

September 26, 2019
Tweet

More Decks by forrep

Other Decks in Programming

Transcript

  1. ブラウザの制約条件から考える フロントエンドのリソース設計 ~ブラウザができること、できないこと~ 1 株式会社ラクーンホールディングス 羽山 純

  2. 自己紹介 2

  3. • 名前 ◦ 羽山 純 • 所属 ◦ 株式会社ラクーンホールディングス 技術戦略部

    • 技術領域 ◦ サーバーサイド開発 ◦ パフォーマンス改善 ◦ AI開発 (企業審査のAI) • 趣味 ◦ 旅行(ダイビング) ◦ アプリ開発 ? 自己紹介 3
  4. ラクーンホールディングスとは? 事業者間取引(BtoB)の領域で問屋のECサイトや 決済サービス・保証事業を展開 Ruby bizグランプリ2017 「Fintech賞」を受賞 Ruby biz 2017 4

  5. 講演について 5

  6. 講演について 本日の講演は 以下の寄稿記事をベースとしています。 Software Design 2019年10月号 "速い"Webアプリケーションの作り方 フロントエンド編 「第2章 パフォーマンスを落とさないリソース設計」

    本日の講演を面白いと感じていただけた方は 是非、本誌も手に取っていただけたらと思います。 6
  7. デモ環境について 7

  8. デモ環境について 本日はデモ環境で実際に動かしながら進めます。 Node.js製の簡易ウェブサーバを利用します。 https://github.com/forrep/web-performance 覚えておくルールは1つだけ。 ファイル名パターンで転送遅延させてます。 hoge.html ⇒ 遅延なし hoge_10s.html

    ⇒10秒遅延してから転送(TTFB =10秒) hoge_10s_g.html ⇒10秒間で徐々に転送(TTFB = 0秒) 8
  9. デモ環境について 実際に試してみたい方 ※Node.js v4以降が必要、追加ライブラリ不要 本日のイベントで利用する環境には以下のURLで アクセスできます。 http://localhost:8080/event/ 9 $ git

    clone https://github.com/forrep/web-performance.git $ cd web-performance $ node server.js Starting on http://localhost:8080
  10. では、始めましょう 10

  11. DOM(Document Object Model)構築とは • HTMLをブラウザがパースしてDOMを構築する • 構築されたDOMは、そのまま画面に表示される • このHTMLファイルを10秒で徐々に転送してみる <!DOCTYPE

    html> <head> <meta charset="utf-8"> <title>巨大なHTMLドキュメント</title> <style> body { font-size: 5pt; } </style> </head> <body> <span>▪</span> <span>▪</span> <span>▪</span> <span>▪</span> ・ ・ <span>▪</span> </body> 10,000行 11
  12. DOM(Document Object Model)構築とは デモを見る ※ 2. 巨大なHTMLを10秒で徐々に転送 12

  13. DOM(Document Object Model)構築とは DOM構築 = 画面表示 DOM構築されたら、 即時に画面表示されます。 (実は半分ウソ、真実は後述) 13

  14. JavaScriptとDOM構築のブロック • <script>タグで外部JavaScriptを読み込む。 • <script>読み込みでサーバー側の遅延処理が入る、 転送に5秒間かかる。 <!DOCTYPE html> <head> <meta

    charset="utf-8"> <title>03 JavaScriptはDOM構築をブロック</title> </head> <body> <div>この文章はすぐに表示される。</div> <script src="time_5s.js"></script> <div>この文章はJavaScriptが実行された後(5秒後)に表示される</div> </body> 14 time_5s.js ⇒ 5秒遅延
  15. JavaScriptとDOM構築のブロック • time.js は以下の内容で、実行した時間を <body>に「Exec: ◦◦sec」と追記するだけ。 (function() { var tag

    = '<div style="color: red;">Exec: ' + (performance.now()/1000).toFixed(2) + 'sec</div>'; var insertTag = function() { document.body.insertAdjacentHTML('beforeend', tag); }; if (document.body) { insertTag(); } else { document.addEventListener("DOMContentLoaded", insertTag); } })(); 15 <body>のDOMを構築済みならそのまま追記 <body>のDOMが未構築なら DOMContentLoadedのタイミングで追記
  16. JavaScriptとDOM構築のブロック デモを見る ※ 3. JavaScriptとDOM構築のブロック 16

  17. JavaScriptとDOM構築のブロック HTML転送 ≠ DOM構築 = 画面表示 <script>はDOM構築をブロックする 「HTML転送済み」≠「DOM構築済み」 17

  18. <script>タグの動的生成(Script-Inject) • Script-InjectはDOM構築をブロックしない = 非同期化 <!DOCTYPE html> <head> <meta charset="utf-8">

    <script> (function() { var tag = document.createElement('script'); tag.src = 'time_5s.js'; document.head.appendChild(tag); })(); </script> <title>Script-Injectによる非同期化</title> </head> <body> <div>Script-InjectはDOM構築をブロックしない、この文章はすぐに表示される。</div> <div>「5秒後」にJavaScriptが実行される</div> </body> 18 5秒遅延
  19. <script>タグの動的生成(Script-Inject) デモを見る ※ 4. Script-Injectによる<script>タグ生成 19

  20. <script>タグの動的生成(Script-Inject) Script-InjectはDOM構築をブロックしない 素晴らしいように思えます が、大きなデメリットが2つあります 20

  21. CSS/CSSOMとレンダリングブロック • <head>で読み込んだCSSは CSSOM(CSS Object Model)が構築されるまで レンダリング(=画面表示)をブロックする • bold.cssは太字・アンダーライン付きにするだけ <!DOCTYPE

    html> <head> <meta charset="utf-8"> <link href="bold_5s.css" rel="stylesheet"> <title>CSSOM構築によるレンダリングブロック</title> </head> <body> <div>この文章はCSSOM構築を待つため「5秒後」に表示される。</div> </body> 21 5秒遅延 body { font-weight: bold; text-decoration: underline; }
  22. CSS/CSSOMとレンダリングブロック デモを見る ※ 5. CSSOM構築によるレンダリングのブロック 22

  23. CSS/CSSOMとレンダリングブロック CSSOMの構築完了まで レンダリングはブロックされる ※<head>内で読み込んだ場合のみ CSSOMの構築は DOM構築をブロックしない (DOMContentLoadedはすぐに発火する) 23

  24. 画面表示に必要な2つの鍵 画面表示には2つの鍵が必要 DOM構築(都度表示可能) <head>内のCSSOMを完全に構築済み 24

  25. <link>タグの動的生成(CSS-Inject) • CSS-Injectならレンダリングをブロックしない = 非同期化 <!DOCTYPE html> <head> <meta charset="utf-8">

    <script> (function() { var tag = document.createElement('link'); tag.href = 'bold_5s.css'; tag.rel = 'stylesheet'; document.head.appendChild(tag); })(); </script> <title>CSS-Injectによるブロック解除</title> </head> <body> <div>この文章はすぐに表示される。</div> <div>「5秒後に」強調表示に変化する。</div> </body> 25 5秒遅延
  26. <link>タグの動的生成(CSS-Inject) デモを見る ※ 8. CSS-InjectによるCSS読み込み 26

  27. <link>タグの動的生成(CSS-Inject) CSS-Injectなら レンダリングをブロックしない Flash of Unstyled Content(FOUC)に注意 ※CSS未適用の画面が表示される現象 27

  28. メディアクエリでレンダリングのブロックを解除 • <link media=print>でレンダリングブロックを解除 = 非同期化 <!DOCTYPE html> <head> <meta

    charset="utf-8"> <link href="bold_5s.css" rel="stylesheet" media="print" onload="this.media='all'"> <title>CSSのメディアクエリによるブロック解除</title> </head> <body> <div>この文章はすぐに表示される。</div> <div>「5秒後」に強調表示に変化する。</div> </body> 28 5秒遅延 onloadでallに戻す
  29. メディアクエリでレンダリングのブロックを解除 デモを見る ※ 9. メディアクエリをprintとしてCSS読み込み 29

  30. メディアクエリでレンダリングのブロックを解除 <link media=print>なら レンダリングをブロックしない しかし、転送優先度が最低(Lowest)になってしまう 30 Lowest!!

  31. CSSとJavaScriptの読み込み • CSSとJavaScriptを読み込み • CSSOMの構築はDOM構築をブロックしない <!DOCTYPE html> <head> <meta charset="utf-8">

    <link href="bold_3s.css" rel="stylesheet"> <script src="time_5s.js"></script> <title>JavaScriptとCSS読み込み</title> </head> <body> <div>この文章は「5秒後」に表示される。</div> <div>JavaScriptも同じタイミング(5秒後)で実行される。</div> </body> 31 3秒遅延 5秒遅延
  32. CSSとJavaScriptの読み込み デモを見る ※10. CSSとJavaScript読み込み 32

  33. CSSとJavaScriptの読み込み CSSOM構築完了は3秒後。 しかし <script>で5秒間DOM構築が停止、 画面表示は5秒後まで待たされます。 33

  34. CSS読み込みとScript-Inject • JavaScriptをScript-Injectで非同期化 <!DOCTYPE html> <head> <meta charset="utf-8"> <link href="bold_3s.css"

    rel="stylesheet"> <script> (function() { var tag = document.createElement("script"); tag.src = 'time_5s.js'; document.head.appendChild(tag); })(); </script> <title>CSS読み込みとScript-Inject</title> </head> <body> <div>この文章は「3秒後」に表示される。</div> <div>JavaScriptは「8秒後」に実行される。</div> </body> 34 3秒遅延 5秒遅延
  35. CSS読み込みとScript-Inject デモを見る ※ 11. CSS読み込みとScript-Inject 35

  36. Script-Injectによる非同期化で 画面表示は3秒後に高速化 しかし Script-Injectの実行がCSSOM構築を待つので 3秒 + 5秒 = 8 秒後にJavaScriptが実行される

    これがScript-Injectの問題点の1つです。 CSS読み込みとScript-Inject 36
  37. CSS読み込みとScript-Inject JavaScriptの実行はCSSOMの構築を待つ (外部ファイル・インライン共に) 37

  38. リソースのプリロード機能 ≠ rel=preload • JavaScriptを、通常 ⇒ Script-Injectの順に読み込み <!DOCTYPE html> <head>

    <meta charset="utf-8"> <script src="time_5s.js?no=1"></script> <script> (function() { var tag = document.createElement("script"); tag.src = 'time_5s.js?no=2'; document.head.appendChild(tag); })(); </script> <title>通常⇒ScriptInjectで読み込み</title> </head> <body> <div>この文章は「5秒後」に表示されます。</div> <div>JavaScript(1つ目)は「5秒後」に実行されます。</div> <div>JavaScript(2つ目)は「10秒後」に実行されます。</div> </body> 38 5秒遅延 5秒遅延
  39. リソースのプリロード機能 ≠ rel=preload デモを見る ※ 12. JavaScriptを通常読込⇒Script-Inject 39

  40. リソースのプリロード機能 ≠ rel=preload • JavaScriptを、通常読み込み ⇒ 通常読み込み <!DOCTYPE html> <head>

    <meta charset="utf-8"> <script src="time_5s.js?no=1"></script> <script src="time_5s.js?no=2"></script> <title>通常⇒ScriptInjectで読み込み</title> </head> <body> <div>この文章は「5秒後」に表示されます。</div> <div>JavaScript(1つ目)は「5秒後」に実行されます。</div> <div>JavaScript(2つ目)も「5秒後」に実行されます。</div> </body> 40 5秒遅延 5秒遅延
  41. リソースのプリロード機能 ≠ rel=preload デモを見る ※ 13. JavaScriptを通常読込⇒通常読込 41

  42. <script src=...>はDOM構築が停止中でも ブラウザのプリロード機能が先読みする。 Script-Injectはプリロード機能が効かない これが2つめの問題点です。 リソースのプリロード機能 ≠ rel=preload 42

  43. • 元々は2008年頃にIE8で実装されたもの • ブラウザによって名称が異なる ◦ Lookahead Downloader (IE8) ◦ Pre-loader

    ◦ Preload Scanner (WebKit) • 同じ時期にIEは同時Download数を2個⇒6個へ ◦ 現在は同時DL数6個がデファクトとなった • <link rel=preload href=...>と使い分けが必須 ◦ 基本はプリロード機能に任せる ◦ 動的に読み込まれるものには rel=preload リソースのプリロード機能 ≠ rel=preload 43
  44. <script>をasync属性で非同期化 • <script>にasync属性を付けると非同期化する <!DOCTYPE html> <head> <meta charset="utf-8"> <link href="bold_3s.css"

    rel="stylesheet"> <script src="time_5s.js" async></script> <title>JavaScriptをasync指定</title> </head> <body> <div>この文章はCSSOM構築を待って「3秒後」に表示されます。</div> <div>JavaScriptは「5秒後(表示されてから2秒後)」に実行されます。</div> </body> 44 3秒遅延 5秒遅延 async指定
  45. <script>をasync属性で非同期化 デモを見る ※ 14. JavaScriptをasync指定 45

  46. <script async>はDOM構築をブロックしない Script-Injectによる非同期化の上位互換 さらにDOM構築停止時のプリロード対象になる ついでにCSSOM構築も待たない 良いことずくめ しかし インライン<script>は非同期化できない問題 <script>をasync属性で非同期化 46

  47. インラインの<script>を非同期化 • async属性はインライン<script>に適用できない <!DOCTYPE html> <head> <meta charset="utf-8"> <link href="bold_3s.css"

    rel="stylesheet"> <script> (function() { setTimeout( function(){document.body.style.color = 'red'}, 5000 ) })(); </script> <title>インライン&lt;script&gt;によるDOM構築ブロック</title> </head> <body> <div>この文章は「3秒後」に表示される。</div> <div>「8秒後(表示から5秒後)」に文字色が赤に変化する。</div> </body> 47 3秒遅延 5秒後にフォントを赤に変更
  48. インラインの<script>を非同期化 デモを見る ※ 16. インライン<script>によるDOM構築ブロック 48

  49. インラインのJavaScriptでもCSSOM構築を待つ 1. CSS読み込み ⇒ CSSOM構築待ち 2. インライン<script> この順で登場するとCSSOM構築が完了するまで JavaScript実行とDOM構築がブロックされる インラインの<script>を非同期化

    49
  50. インラインの<script>を非同期化 • srcにデータURLで指定することでasyncを有効化 • data:text/javascript, に続けてJavaScriptコードを エスケープした文字列を貼り付け • エスケープはJavaScriptならencodeURI() <!DOCTYPE

    html> <head> <meta charset="utf-8"> <link href="bold_3s.css" rel="stylesheet"> <script async src="data:text/javascript,(function()%7BsetTimeout(function()%7Bdo⏎ cument.body.style.color='red'%7D,5000)%7D)()"></script> <title>インライン&lt;script&gt;の非同期化</title> </head> <body> <div>この文章は「3秒後」に表示される。</div> <div>「5秒後(表示から2秒後)」の文字色が赤に変化する。</div> </body> 50 3秒遅延 インラインJavaScriptを データURLに変換 ※実際には空白なしで1行
  51. インラインの<script>を非同期化 デモを見る ※ 17. インライン<script>の非同期化 51

  52. インライン<script>も遅延原因になる 実行にはCSSOMの完全な構築が必要 可能な限り非同期化しましょう もしくは CSS読み込み前に<script>を移動するだけでもOK インラインの<script>を非同期化 52

  53. まとめ 53

  54. まとめ • HTML転送 ≠ DOM構築 = 画面表示 • <script>はDOM構築をブロックする •

    CSSOM構築完了までレンダリングをブロックする ※ただし<head>内で読み込んだCSSのみ • 画面表示は2つの鍵が必要 ◦ DOM構築(部分的な構築でもOK) ◦ <head>内のCSSOMを完全に構築済 • JavaScript実行はCSSOM構築を待つ • インラインJavaScriptでもCSSOM構築を待つ 54
  55. 最後に 55

  56. 56 https://www.raccoon.ne.jp/company/recruit/index.html

  57. ご清聴ありがとうございました。 https://www.raccoon.ne.jp/company/recruit/index.html