← Learn に戻る

JavaScript SDK 設計: 実在5製品のバンドル分解

基板に並んだ電子部品を背景にした、シリーズ「JavaScript SDK 設計 — 実在5製品のバンドル分解」のカード

YouTube・Safie・Vimeo・Mux Player・Video.js の配信バンドルを実際に取得して分解します。5本を並べて読むと、loader/実体分離・iframe隔離・postMessage契約・版固定という設計軸が浮かび上がってきます。

読み終えるとできること

  • YouTube の 993 バイトを1行ずつ読んで、埋め込み SDK が揃って使う7パターンを自分のコードに移せます
  • iframe に隔離するか DOM へ直挿しするかを、CSP・Cookie・postMessage の往復コストから選べます
  • 同じページに2箇所埋め込まれても壊れない postMessage の契約を設計できます
  • 版と cache-control を、iframe 型と DOM 直挿し型のどちらを採るかに合わせて決められます
  • script 1行で埋め込むウィジェットを、最小構成で端から端まで書けます

script 1行を貼るだけで何かが動きます。その裏では5種類の設計のどれかが動いていて、その選択がすべてを縛ります。どう版を切るか、どう隔離するか、どうハンドシェイクするか、そもそも顧客のCSPが動作を許すか。

このシリーズでは実在の5製品のバンドル(<script src="…"> で実際に落ちてくるファイル)を読み、5社の意見が割れる設計軸を取り出します。分解が先で、抽象は後です。ファイルを取ってきて、コードを読み、レスポンスを測ります。読み終わったときに、自分で作れるようになっているのが目標です。

script 1行が刺さる場面

顧客のサイトに機能を埋め込みたい場面は決まっています。動画プレイヤー、カメラ映像、チャットウィジェット、決済フォーム、地図。npm パッケージにして配ると、顧客はビルドできる技術者に限られます。CDN + <script> 1行なら、CMS 利用者や非エンジニア顧客でも HTML に貼るだけで動きます。「導入の難易度がゼロに近いこと」が採用の条件になる領域では、この配布形態しか選択肢がありません。

CDN 側の要件はシンプルです。ユーザーに地理的に近い配信拠点 (エッジ、CDN が世界各地に持っているサーバー) から低レイテンシで配れて、キャッシュが効いて、URL でバージョンを切り替えられる。ただし配ったコードは顧客サイトの中で、顧客と同じ権限で動きます。ここが次の問題になります。

iframe を選ぶ理由

自分のスクリプトを顧客サイトに直挿しすると、顧客の CSS に上書きされ、顧客の JS から書き換えられ、顧客の Cookie 空間で動きます。ボタンの色が意図せず変わり、内部状態が読まれ、認証トークンが混ざります。

<iframe> で自分のオリジン (player.vimeo.com など) に描画すると、この4つがまとめて隔離されます。CSS は iframe 内で完結し、JS は Same-Origin Policy で分離され、Cookie は自分のオリジンに閉じ、CSP を自分側で張り直せます。ブラウザの標準機能で境界を引ける、いちばん安いやり方です。

代償は2つです。iframe の描画で1リクエスト増え、機能呼び出しはすべて postMessage RPC を経由します。「動画を再生してください」という一文が非同期メッセージ1往復に化けます。

顧客のページでDOM 直挿しiframe 隔離
CSS顧客の CSS に上書きされるフレーム内で完結する
JS顧客の JS から書き換えられるSame-Origin Policy が分ける
Cookie顧客の Cookie 空間を共有する自分のオリジンに閉じる
CSP顧客の CSP に従うフレーム内で自分で張り直せる
払う代償なし描画で +1 リクエスト、全メソッド呼び出しが postMessage RPC

script 1行が顧客に渡しているもの

<script src="…"> を顧客が書いた瞬間、そのスクリプトは顧客サイトで XSS と同じ権限を持ちます。DOM を全て読める、Cookie を読める、フォームに割り込める、別のリクエストを裏で送れる。悪意なく書いていても、CDN が改竄されたり、依存先が乗っ取られると、そのまま顧客のユーザーに届きます。

iframe 隔離はこのリスクを構造的に減らします。iframe 内で何が動いても、親ドキュメントの DOM や Cookie には触れません。ただし親子間の postMessage で event.origin を検証しなかったり、* で送ったりすると、攻撃側 iframe から命令を送れます。隔離を選んでもプロトコル層で気を抜くと、同じ穴が空きます。

script が持つ権限iframe 隔離で止まるか
顧客ページの DOM を全て読む✅ 親の DOM に触れない
Cookie を読む✅ 自分のオリジンに閉じる
フォームに割り込む✅ 親のフォームは見えない
裏でリクエストを送る❌ フレーム内からは送れる
postMessage の event.origin を検証しない❌ プロトコル層の問題で、隔離は無関係

止まらない2つは、どちらもフレームの外側の話です。フレーム内から出ていくリクエストをブラウザが止める理由はありませんし、origin を検証するかどうかは自分で書くプロトコルの設計判断です。

DOM 直挿し型 (Mux Player, Video.js) は隔離を諦める代わりに、そもそも顧客の DOM に組み込まれることが機能要件になっています (Web Component として顧客の CSS を継承したい、独自 UI を組みたい)。この選択は「顧客が自分のスクリプトを信用している」ことを前提にしています。

5社の妥協点

配布容易性 (<script> 1行)、隔離 (iframe or 直挿し)、セキュリティ (プロトコル層の甘さ) の3制約が同時にかかります。全部を最大化する解はないので、5社は違う場所で線を引いています。Safie はカメラ映像なので iframe + プロトコル暗号化まで払う、Video.js はライブラリなのでセキュリティを顧客責任にする、Mux は「Web Component として直挿し・全部入り」でトレードオフを丸呑みする。この線の引き方こそが読みどころで、以下で順に分解していきます。

顕微鏡下の5製品

製品loader URLloader (raw / gzip)実体版固定できるか
Safiesafie.link/sdk/js/api/v1/latest/160 KB / 37 KBiframe内 2.61 MB❌ (URL に latest)
YouTubeyoutube.com/iframe_api993 B / 572 Bwww-widgetapi.js 26.8 KB実体URLがハッシュ付きなので間接的にのみ
Vimeoplayer.vimeo.com/api/player.js24.4 KB / 7.9 KB単一ファイル❌ (無版)
Mux Playercdn.jsdelivr.net/npm/@mux/mux-player1.06 MB / 292 KB単一ファイル✅ (npm semver)
Video.jsvjs.zencdn.net/8.23.4/video.min.js690 KB / 201 KB単一ファイル✅ 必須 (/8/ は 403)

測定日は2026-08-08です。サイズは実際のHTTPボディ、gzipは実際に線に流れる量です。全て curl で認証なしで取得しました。

すべてはこの2軸に集約されます

script 1行 SDK の設計軸。iframe 隔離型 / DOM 直挿し型 と loader 分離 / 単一ファイルの4象限に5製品を配置した図。左上に Safie と YouTube、左下に Vimeo、右下に Mux Player と Video.js、右上の DOM 直挿し×分離だけが空

iframe型 vs DOM直挿し型。Safie・YouTube・Vimeo(埋め込みモード)は <iframe> 内に自分のオリジンで描画します。Mux Player と Video.js は顧客のDOMに Web Components もしくは通常要素として直挿しします。iframe隔離を選ぶと、CSS containment・独自CSP・オリジン境界のスクリプト隔離がタダで手に入ります。代わりに、あらゆるメソッド呼び出しに postMessage RPC 層が必要になります。

loader/実体分離 vs 単一ファイル。Safie の loader は 160 KB で本体は 2.61 MB の iframe です。YouTube の loader は 993 B で本体は 27 KB。Video.js と Mux は分割なしの単一ファイルで配信されます。

この2軸はセットになりがちですが、完全には一致しません。iframe を選べば分離はほぼ必須です(顧客側スクリプトに postMessage shim が要る、iframe 側には UI コードが要る)。DOM直挿しでは分離のうまみが薄く、追加リクエストのコストだけが残ります。ただし版固定の挙動は iframe / DOM 軸にきれいに沿います。このサンプルの iframe 型は全社が顧客に版を固定させません。理由は怠慢ではなく、iframe SDK は親子でプロトコルバージョンを合わせ続ける必要があり、顧客をアップグレードのトレッドミルから降ろすとその整合が破綻するからです。

読む順序

6本のspoke と このpillar の構成です。順に読むと論の流れがつながります。個別の疑問があれば該当spokeへ:

  • Part 1: 993バイトを1バイトずつ読む — YouTube の loader は端から端まで読める小ささです。script 1行 SDK が再利用する7パターンを取り出します。
  • Part 2: iframe隔離 — Safie が160KBの親から 2.6MB を iframe に追いやり、iframe側で per-request nonce の CSP を張り、親子で SYN/ACK + AES-GCM 鍵交換を回す実装を分解します。
  • Part 3: postMessage契約 — Vimeo・YouTube・Safie の3世代を並べて比較。origin ハンドシェイク、JSON Schema vs .d.ts、AES暗号化が本当に必要な場面を切り分けます。
  • Part 4: 版とcache-control — 5製品のヘッダーを実測すると、iframe 型は版を切らせない、DOM 型は exact-version 必須。この二分の理由を読み解きます。
  • Part 5: 初期化スタイル3種と二重ロード耐性 — 命令的・data 属性・Web Components を比較。5製品すべての loader バンドルに二重ロード対策が入っています。
  • Part 6: 自分で作る — 最小の埋め込み SDK を端から端まで組みます。分解で見た6つの決断を実装で明示的に選び直します。

このシリーズがやらないこと

「どのSDKが一番いいか」の順位付けはしません。5製品はそれぞれ違う制約に合わせて調整されています。Safie はユーザ毎にキーがローテートする認証付きリアルタイム映像が要ります。Video.js は自分でプレイヤーを書きたい人のためのライブラリです。Mux は全部入りの Web Component です。おもしろい研究対象は設計軸そのものであって、その軸の上でどこに点を置いたかではありません。

公式ドキュメントを歩き直す記事でもありません。ここに載っている測定値は全てレスポンスボディから引き出しました。ドキュメントは API 表面を記述し、バンドルは 1リクエストで何が来るかを記述します。この2つは別の文書で、興味深いところで食い違います。

測定日は2026-08-08時点です。ビルドID と実測バイト数は動きますが、設計空間の形は動きません。