「Repro Tech Meetup: Deep Dive into Browsers」に参加してきました

  • 2024/3/15
  • https://repro-tech.connpass.com/event/311742/
  • ブラウザAPIの話を聞けるのは珍しいので面白かったです
  • Service Worker static routing APIが名前だけ知ってて気になってたので詳細聞けたのがよかったです

No more parser-inserted js

script要素

  • 標準は同期実行 & parser-inserted mode
    • DLと実行待ちでHTMLパーサが停止する
  • これが標準なのは互換性のため
    • JavaScript1.0(1996)
    • document.write
    • 今は警告出るしもう使わない
  • JavaScript1.0

HTMLパーサとJS実行

  • parser-insertedな実行
    • HTMLだけなら普通のパース処理
      • 順番に処理してく
    • JSが入るとややこしくなる
      • JSでHTML入力ストリームにインサートできる
      • JSでDOMツリーを改変できる
    • 当然かなり遅い
  • 今はもうquerySelectorがあれば十分
  • DOM APIがあればパースとめて処理実行する必要ない
    • それを実現するのがdeferとasync

defer/asyncの話

  • defer
    • ダウンロードは並行
    • 入力ストリームの終端で実行
    • document.writeは虫
  • async
    • ダウンロードは並行
    • パーサを止めて実行
    • document.writeは虫

document.write再考

document.write

  • 文字列で渡されたHTMLをHTMLに追加する
    • 壊れてても入れられる

いいところ

  • レンダリングプロセスに介入できる唯一の方法
    • 画面が表示されたときには必ず処理が終わってる
    • 計測タグ
    • レンダリングまでに必ず必要な処理

悪いところ

  • レンダリングプロセスに介入してしまう
    • 同期的でブロックされる
  • 実行タイミングがずれるとページを壊す

何が問題化

  • 適切に使えば問題ないがそれが難しい
  • 文字列渡すので何でも実行されてしまう

document.writeへの介入

  • chromeは介入して止める
    • scriptが実行されない
    • ただし介入するのはいろんな条件を満たした時

Render Blocking

  • 特定の要素にたどり着くまでブロッキングする
    • レイアウトシフトを防ぐのが目的
  • ABテストで画面の表示が確定するまで止めるとか

エディタ付きのReact開発環境をブラウザだけで実装した話

  • steelydylanさん

ブラウザエディタ

  • https://mosya.dev/react
  • StackBlitzのブラウザでnode動かす製品とかあるが有料なので自分で作った
  • lintエラーとか型情報とかちゃんと表示される
    • Biomeのwasm webをブラウザで動かせる
    • workerで動かさないと重すぎる
      • ComLinkが便利

プレビュー環境

  • SWを活用
    • 通信に介入するのを利用した
    • 必要なファイルを通信しにいってSWでァイルを返す
    • 全部mjs化して返して解決させてる
  • reactなどのライブラリはimportmapで対応
  • SWでHonoを動かしてる
  • tsは@swc/wasm-webでトランスパイル

採点機能

  • ブラウザでtesting-libraryを動かしてる
  • jest-liteで実行

Memory leaks in Web Application

メモリ管理

  • アプリの変化
    • SPで滞在時間長期化JSの肥大化
    • ヒープを圧迫すると最悪タブクラッシュ
  • なぜリークするか
    • GC言語なのでメモリは自動管理
    • 到達可能だけど不要なオブジェクトがリークしてるオブジェクト
    • windowから参照されてる
    • listener
  • 頻出パターン

どうやって特定する

  • Memory heap snapshot diffing
    • snapshotをn回とって+nなオブジェクトが怪しい
  • Retainer treeを確認
  • 3snapshot
    • 2回だとキャッシュとか意図的に残ってるかも
  • ノイズが多くて大変

自動化

  • fuite
    • puppeteer使ってる
  • MemLab
    • meta製
  • BLeak
    • Proxy使ってJS書き換えて
  • LeakPair
    • ASTで解析してリークしやすいパターンを見つけてパッチあてる

モニタリング

  • フィールドデータが貴重
    • 実際のユーザがどれだけメモリ使ってるか

Service Worker static routing API

SorviceWorker

  • fetchをproxyするがその時には起動済みでないといけない
  • SWの起動はnavigationにクリティカル
    • Androidだとp95で500ms
    • cold状態からの起動だとp95で940ms
      • 30%くらいでこっちの挙動になる
  • SWが介入しなかった時のコストも無駄がある
    • SWがなにもせずブラウザから通信を改めてするので
  • navigationPreloadを使うとnavigationと起動を並行できる
    • とはいってもswの方が時間かかると待たされて遅い

SorviceWorkerのコスト改善

  • いらない時は動かさない
  • Static Routing API
    • どういうリソースにどう介入させるのかを宣言的に指定する
    • event.addRoutes に定義する
    • confition
      • URLPatternやmethodなどの指定
    • source
      • networkなのかcacheなのかなど
    • or
      • pngかjpgならchacheからといった使い方

Use case

  • navigationはキャッシュしてないとか
  • formのPOSTはキャッシュしてないとか
  • サブリソースはキャッシュにあればキャッシュがいいとか

dev tools

  • networkタブでserviceworker routerと出るようになる
  • 指定した定義も確認できる