JavaScript SDK 設計: 実在5製品のバンドル分解
動画プレイヤーやカメラ映像、チャットを顧客のサイトに script 1行で載せる SDK を作るとき、最初に決めるのは埋め込み形態です。顧客の CSS や CSP とぶつけたくない、扱うデータが機密なら iframe 型で版は選ばせない。顧客が自分で UI を組むなら DOM 直挿しで版を選ばせる。YouTube・Safie・Vimeo・Mux Player・Video.js の配信バンドルを 2026-08-08 に取得して読み、loader の分け方、CSP、postMessage、版、初期化まで6つの決断で並べます。サイズとヘッダは実測、挙動は配信 JS を読んだもので、最終章の骨格コードは動かしていません。
読み終えるとできること
- 埋め込み形態から先に選べます。顧客の CSS や CSP と干渉させたくない、データが機密なら iframe 型にして版は選ばせず、顧客が UI を組むなら DOM 直挿しにして、版番号まで完全に指定 (exact version) できる URL を顧客に選ばせます
- YouTube の 993 バイトの loader から、二重ロード対策や
currentScript.nonceの伝播を含む7パターンを自分の loader に移せます - iframe の中を、Safie と同じ per-request nonce の CSP と
connect-srcの限定で固められます - postMessage を、origin の等値比較と
event.sourceの照合で、同じページに2箇所埋め込まれても混線しない契約にできます - 6つの決断を1つずつ選んだ最小の埋め込み SDK の骨格を読み、自分の要件に置き換えられます
シリーズ目次全 7 章
- 0 JavaScript SDK 設計: 実在5製品のバンドル分解 Pillar (現在のページ)
- 1 YouTube iframe API: 993バイトを1行ずつ読む YouTube iframe API の loader は 993 バイト。1行ずつ読むと、埋め込み SDK が再利用する7パターンが出てきます。本体は 26.8KB です。
- 2 iframe 隔離と CSP nonce: 2.61MB を別オリジンへ Safie は 160KB の loader から 2.61MB を iframe に追いやります。iframe 内は per-request nonce の CSP、親子は SYN/ACK と AES-GCM。
- 3 postMessage の使い方と契約設計: 3世代を比較 postMessage で親子を会話させるのは5製品中3つ。Vimeo・YouTube・Safie を並べると、機密性と複雑度でプロトコルの世代が決まります。
- 4 cache-control 設定と版固定: 5製品で戦略が割れる 5製品の cache-control を実測すると、iframe 型は顧客に版を選ばせず、DOM 直挿し型は版を顧客に選ばせる。なぜ真逆に割れるのか。
- 5 Web Components と data属性: SDK 初期化3種を比較 SDK の入口は3種類。YouTube の命令的 API、Video.js の data-setup、Mux の Web Components。二重ロード対策の入れ方も比べます。
- 6 埋め込みコードを自作する: 最小 SDK を6決断で組む 分解で見えた6つの決断を、最小の埋め込み SDK を書いて選び直します。script 1行のチャットウィジェットを端から端まで書く想定です。
顧客のサイトに、動画プレイヤーやカメラ映像、チャットの窓を載せたい。npm パッケージで配ると、導入できるのはビルドできる技術者のいる顧客に限られますが、CDN に置いて <script> を1行貼ってもらう形なら、CMS を使う顧客でも HTML に貼るだけで動きます。導入の手間がゼロに近いことが採用の条件になる領域では、この配り方しか選べません。
ただし、その1行の裏では、埋め込み形態、loader と本体の分け方、CSP、postMessage の契約、版とキャッシュ、初期化の入口という6つの決断が済んでいて、しかも配ったコードは顧客のページの中で、顧客のスクリプトと同じ権限で動きます。
顧客の CSS や CSP とぶつけたくない、扱うデータが機密なら iframe 型にして、版は選ばせません。顧客が自分で UI を組む、フレームワークに溶け込ませたいなら DOM 直挿しにして、版番号まで完全に指定 (exact version) できる URL を顧客に選ばせます。 埋め込み形態を決めた時点で、版の方針と postMessage の要否までほぼ決まります。実在の5製品を並べると、iframe 型の YouTube・Safie・Vimeo は3製品とも顧客に版を選ばせておらず、DOM 直挿し型の2製品はどちらも postMessage を使わずに版を顧客に選ばせていて、Video.js は URL に版を必須にし、Mux Player は版を書けば1年 immutable でキャッシュされます。

残りの決断は、形態ほど一斉には割れません。loader と本体を分けるかは版を自社の都合で切り替えたいかどうかで決まり、初期化の入口は顧客が静的 HTML を書くのか SPA を書くのかに合わせます。CSP と postMessage をどこまで重くするかは、扱うデータの機密性と親子で共有する状態の量で決まると私は読んでいます。二重ロード対策だけは5製品中4製品が入れていて、割れる決断というより最初から入れておくものです。
このシリーズでは5製品の配信バンドルを取ってきて読み、決断ごとに章を立てています。分解が先で、抽象は後です。最終章で、分解で見た6つの決断を選び直して最小の埋め込み SDK を組みます。
揃える課題
比較が意味を持つのは、5製品に同じ仕事をさせたときだけです。
- 仕事: 顧客のページに
<script>を1行貼ってもらい、映像を再生する UI を出す (Safie はカメラ映像、残りの4製品は動画) - 起点: 顧客が貼る URL (loader)。そこから読み込まれるファイルを本体まで追う
- 取得: 2026-08-08 に
curlで認証なしで取得。サイズは HTTP ボディ、gzip は実際に線上を流れる量 - 読むもの: 配信された JS とレスポンスヘッダ (公式ドキュメントは根拠にしない)
- 取り直し: Safie の鍵交換、Vimeo の待ち合わせ、二重ロード対策の有無は 2026-09-30 に取り直して読み直した

題材を映像に揃えたので、差が出たらそれは題材より設計の違いから来たものと読めます。揃っていないのは扱うデータの機密性です。Safie は録画映像へのアクセス権を含み、YouTube が流すのは誰でも見られる公開動画なので、この差が postMessage と CSP の列に出ていると私は読んでいます。
どの SDK が一番いいかの順位は付けません。5製品は違う制約に合わせて作られていて、見たいのは制約から決断への対応のほうです。ドキュメントを読まずにバンドルを読むのは、ドキュメントが API の表面を書くのに対して、バンドルは1リクエストで実際に何が届くかを示すからです。この2つはときどき食い違います。

5つの製品
| 製品 | 埋め込むもの | 顧客が貼る URL | ひとことで |
|---|---|---|---|
| Safie | カメラ映像 | safie.link/sdk/js/api/v1/latest/ | 160 KB の loader から、2.61 MB の本体を iframe に隔離する |
| YouTube | 動画 | youtube.com/iframe_api | 993 バイトの loader が、ビルド ID 付きの本体を差し込む |
| Vimeo | 動画 | player.vimeo.com/api/player.js | iframe 型だが単一ファイル。顧客が書いた iframe に後から付けるか、要素の中に iframe を作る |
| Mux Player | 動画 | cdn.jsdelivr.net/npm/@mux/mux-player | 全部入りの Web Component として顧客の DOM に入る |
| Video.js | 動画 | vjs.zencdn.net/8.23.4/video.min.js | 顧客の <video> 要素をプレイヤーに変えるライブラリ |
上の3つが iframe 型、下の2つが DOM 直挿し型です。
5製品 × 6つの決断

| 製品 | 形態 | loader と本体 | CSP | postMessage の世代 | 版 | 初期化 |
|---|---|---|---|---|---|---|
| Safie | iframe (4枚を hash で役割分け) | 分離 160 KB → 2.61 MB | iframe 内で per-request nonce、connect-src を自社に閉じる | 3 暗号化まで (syn → ack → connected、親 → 子を AES-GCM) | 選ばせない (/latest/ を no-cache) | 命令的 (namespace)。二重ロード対策は見当たらない |
| YouTube | iframe | 分離 993 B → 26.8 KB | loader が Trusted Types と nonce の伝播で顧客の CSP に合わせる | 2 状態同期 (listening → initialDelivery、origin は等値比較) | 選ばせない (loader は max-age=0、本体はビルド ID 付き URL) | 命令的 (callback)。window.YT で二重ロードを防ぐ |
| Vimeo | iframe (顧客が書いた iframe に後付けするか、要素の中に SDK が作る) | 単一 24.4 KB | 未確認 | 1 最小 (ping → ready、初回の origin を固定し event.source も照合) | 選ばせない (版なし URL を max-age=1800) | 命令的 (constructor)。要素に初期化済みの印 |
| Mux Player | DOM 直挿し (Web Components) | 単一 1.06 MB | 顧客の CSP の下で動く (構造上。個別には未確認) | 使わない | 選ばせる (@3.7.0 は1年 immutable) | Web Components。customElements.get で二重定義を防ぐ |
| Video.js | DOM 直挿し (<video> 要素) | 単一 690 KB | 顧客の CSP の下で動く (構造上。個別には未確認) | 使わない | exact 必須 (/8/ は 403) | data 属性。プレイヤー化済みの要素を飛ばす |
postMessage と版の2列は、形態の列とそのまま同じ線で割れます。iframe 型の3製品はどれも顧客に版を選ばせず、DOM 直挿し型の2製品はどれも postMessage を使わずに版を顧客に選ばせています。
なぜ版の方針が形態にここまで沿うのかは、バンドルには書かれていません。ここからは私の推論です。iframe 型は親 (顧客のページ) と子 (自社オリジンの iframe) が postMessage で話すので、プロトコルの版を親子で揃え続ける必要があり、顧客に古い loader を貼りっぱなしにされるとその整合が崩れます。DOM 直挿し型には親子の会話が無く、代わりに API の変更が顧客のコードを壊すので、アップグレードの時期を顧客が選べる形に落ち着いたのだと考えています。
CSP と postMessage の世代が形態の中でさらに割れるのは、扱うデータの機密性の差から来ていると私は読んでいます。公開動画の YouTube と Vimeo は暗号化を入れず、録画映像へのアクセス権を扱う Safie は iframe の中に per-request nonce の CSP を張り、親から子への命令を AES-GCM で暗号化しています。Video.js はライブラリなのでセキュリティを顧客の側に置き、Mux は全部入りの Web Component として顧客の DOM に入る代わりに隔離を持ちません。
表の列が見ているもの
表の列に並べた6つが、比べる物差しです。
| 決断 | 何を見るか | 詳しい章 |
|---|---|---|
| 形態 | 自社オリジンの iframe に描画するか、顧客の DOM に直接入れるか。隔離と引き換えに、全部の呼び出しが postMessage を通るようになります | 第2・4章 |
| loader と本体 | 顧客が貼る URL (loader) と、実際の機能 (本体) を別のファイルに分けるか。分けると、顧客の URL を変えずに本体だけ差し替えられます | 第1章 |
| CSP | 顧客のページの CSP の下で動けるか、iframe の中で自分の CSP を張れるか。張れるなら、乗っ取られたときの持ち出し先まで絞れます | 第1・2章 |
| postMessage の世代 | 親子の会話をどこまで重くするか。1 最小 (準備完了を待つだけ)、2 状態同期 (初期状態を流す)、3 暗号化まで、の3つに分けています | 第3章 |
| 版 | 顧客に版を選ばせるか、自社で切り替えるか。loader と本体の cache-control が、この方針をそのまま表します | 第4章 |
| 初期化 | 顧客が SDK を呼ぶ入口。命令的、data 属性、Web Components の3種と、同じ SDK が2回読まれたときの守り方 | 第5章 |
形を決める2つ

6つのうち、SDK の外形を決めるのは形態と loader の分け方の2つです。iframe 型を選ぶと分けたくなります (顧客のページには postMessage の橋渡し役、iframe の中には UI 一式が要る) が、Vimeo は iframe 型でも player.js 1本で配っていて、このサンプルでは例外です。右上の象限は空いています。DOM 直挿しで分けても、顧客の URL を固定したまま本体を差し替えるうまみが薄く、追加のリクエストだけが残るからだと私は読んでいます。
形態の選択で何が変わるかは、顧客のページで起きることを並べると見えます。
| 顧客のページで | DOM 直挿し | iframe 隔離 |
|---|---|---|
| CSS | 顧客の CSS に上書きされる | フレーム内で完結する |
| JS | 顧客の JS から書き換えられる | Same-Origin Policy が分ける |
| Cookie | 顧客の Cookie 空間を共有する | 自分のオリジンに閉じる |
| CSP | 顧客の CSP に従う | フレーム内で自分で張り直せる |
| 払う代償 | なし | 描画で +1 リクエスト、全メソッド呼び出しが postMessage RPC |
<script src="…"> を顧客が書いた瞬間、そのスクリプトは顧客のページで XSS と同じ権限を持ちます。iframe に隔離すると親の DOM・Cookie・フォームには触れなくなりますが、フレームの中から外へ出ていくリクエストは止まらず、postMessage で event.origin を検証しなければ攻撃側の iframe から命令を送れるので、隔離を選んでもプロトコル層で気を抜けば同じ穴が空きます。これが第2・3章の中心です。

自分ならどれか
最終章「埋め込みコードを自作する: 最小 SDK を6決断で組む」の末尾にある表と同じものです。
| 状況 | 選ぶもの | 効いている理由 |
|---|---|---|
| 顧客サイトの CSS・JS・Cookie と干渉させたくない | iframe 型。loader と本体を分け、版は選ばせない | 顧客のスタイルや window.fetch の書き換えが届かない。loader を毎回取り直させれば、親子の版がずれない |
| 顧客が自分で UI を組む、フレームワークに溶け込ませたい | DOM 直挿し (Web Components)。版番号まで指定できる URL を顧客に選ばせる | 版を書いてもらえば、API が変わってもアップグレードの時期を顧客が選べる (Video.js は版が必須、Mux Player は書けば1年 immutable) |
| 認証トークンや録画映像のように、漏れると困るものを扱う | iframe の中に per-request nonce の CSP を張り、connect-src を自社に閉じる | 乗っ取られても外へ持ち出せない (Safie)。静的ホスティングだけでは組めない |
| 親子で状態を共有する | 子の準備完了の合図を待ってから、初期状態を流す | YouTube の listening → initialDelivery |
| 同じページに2箇所以上埋め込まれうる | origin の等値比較に加えて event.source を照合する | 2つの iframe は origin が同じなので、origin だけでは自分の iframe か区別できない |
| 顧客が JS を書かない静的 HTML や CMS | 初期化は data 属性 | Video.js の data-setup のように、貼るだけで動く |
| 顧客の環境が静的 HTML と SPA に分かれている | 初期化は 命令的 + 遅延実行キュー | ロード前の呼び出しを配列に積み、ロード後に巻き戻すので、どちらでも同じ書き方で動く |
| どの形でも | 二重ロード対策を最初のコミットから入れる | タグマネージャはスニペットを2回発火する。5製品中4製品が入れている |
上の2行で形態が決まると、版の方針もそこで決まります。残りの行は、選んだ形態の中で何を足すかの判断で、機密性と顧客の書き方が決め手になります。

このシリーズの検証状態
サイズとヘッダは実測、挙動は配信された JS を読んだもので、最終章のコードは動かしていません。
| 章 | 実測 (curl で取ったサイズ・ヘッダ) | ソース読み (配信 JS を grep・読解した挙動) | 未実行 |
|---|---|---|---|
| この入口のページ | 5製品の loader と本体のサイズ (Vimeo 24.4 KB、Mux Player 1.06 MB、Video.js 690 KB を含む。2026-08-08 に curl で実測) | マトリクスの CSP・postMessage・初期化のセル (詳しくは第2〜5章) | ― |
| 1 loader と本体 | YouTube の loader 993 B / gzip 572 B、本体 26.8 KB、cache-control | loader の7パターン (Trusted Types、二重ロード対策、currentScript.nonce の伝播 等) | ― |
| 2 iframe 隔離 | Safie の loader 160 KB、本体 2.61 MB の内訳、iframe.html の CSP ヘッダ | 4つの iframe の役割分け、SYN/ACK と AES-GCM の鍵の渡し方 (2026-09-30 に取り直して確認) | ― |
| 3 postMessage | ― | 3製品のメッセージ形式・origin 検証・ハンドシェイク (Vimeo の待ち合わせは 2026-09-30 に取り直して確認) | ― |
| 4 版と cache-control | 5製品の cache-control、Video.js /8/ の 403 | ― | ― |
| 5 初期化 | ― | 3つの初期化スタイル、二重ロード対策 5製品中4製品 (2026-09-30 に取り直して確認) | ― |
| 6 自作 | ― | ― | loader と iframe 本体の骨格コード。ブラウザで動かしていない |
表に入っていない推論の中心は2つで、iframe 型が版を選ばせない理由 (親子のプロトコルを揃え続けるため) と、CSP と postMessage の重さが機密性で割れる理由 (Safie は録画映像へのアクセス権を扱うため) です。どちらもバンドルには書かれていないので、本文ではこの2つと、空いている象限の理由を「私の推論です」「と私は読んでいます」と断って書いています。Vimeo の CSP と、Mux Player・Video.js が顧客の CSP の下でどう振る舞うかは、このシリーズでは読んでいません。

読む順
このページのあとに6つの章が続きます。順に読むと論がつながりますが、疑問が1つに絞れているなら、その章から読んでも構いません。
- 第1章 YouTube iframe API: 993バイトを1行ずつ読む: YouTube の loader は端から端まで読める小ささなので、二重ロード対策や
currentScript.nonceの伝播など、自分の loader に移せる7パターンを取り出します。 - 第2章 iframe 隔離と CSP nonce: 2.61MB を別オリジンへ: Safie が 160 KB の loader から 2.61 MB の本体を iframe に移し、iframe の中で per-request nonce の CSP を張り、親子で SYN/ACK と AES-GCM の鍵交換を回す実装を、順に分解します。
- 第3章 postMessage の使い方と契約設計: 3世代を比較: Vimeo・YouTube・Safie の postMessage を3つの世代として並べ、origin の検証とハンドシェイク、メッセージの型の書き方 (JSON Schema と
.d.ts)、暗号化が要る場面を切り分けます。 - 第4章 cache-control 設定と版固定: 5製品で戦略が割れる: 5製品のヘッダを実測すると、iframe 型は版を選ばせず、DOM 直挿し型は顧客に版を選ばせていて、Video.js は版番号まで完全に指定しないと 403 を返します。
- 第5章 Web Components と data属性: SDK 初期化3種を比較: 命令的・data 属性・Web Components の3つの入口を比べます。二重ロード対策は YouTube・Vimeo・Mux Player・Video.js の4製品で確認できました。
- 第6章 埋め込みコードを自作する: 最小 SDK を6決断で組む: チャットウィジェットを題材に、分解で見た6つの決断を1つずつ選び直して、最小の埋め込み SDK の骨格を書きます。

測定日は2026-08-08時点です。ビルドID と実測バイト数は動きますが、設計空間の形は動きません。
この記事は役に立ちましたか?