← Learn に戻る

script 1行で配るJS SDKの設計 — 実在5製品を分解する

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往復に化けます。

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

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

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

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軸に集約されます

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 が再利用する6パターンを取り出します。
  • Part 2: iframe隔離 — Safie が160KBの親から 2.6MB を iframe に追いやり、iframe側で per-request nonce の CSP を張り、親子で SYN/ACK + AES-GCM 鍵交換を回す実装を分解します。
  • Part 3: postMessage契約 (準備中) — origin ハンドシェイク、JSON Schema vs .d.ts、AES暗号化が本当に必要な場面を切り分けます。
  • Part 4: 版とcache-control (準備中) — iframe / DOM 軸の分岐がここで最も強く出ます。
  • Part 5: 初期化スタイル3種と二重ロード耐性 (準備中) — 命令的・data属性・Web Components を比較します。5製品全社が本番で二重ロードを踏んでいます。
  • Part 6: 自分で作る (準備中) — 最小の埋め込みSDKを端から端まで組みます。分解で見た6決断を実装で明示的に選び直します。

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

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

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

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

シリーズ目次

  1. 0 script 1行で配るJS SDKの設計 — 実在5製品を分解する Pillar (現在のページ)
  2. 1 993バイト: script 1行を貼ったときに YouTube から落ちてくるすべて YouTube iframe embed API は 993バイトの loader を返し、そこから27KBの本体を fetch します。loader を1バイトずつ読むと、埋め込み型 SDK が再利用する6パターンが見えてきます。Trusted Types、nonce伝播、readyキュー、二層キャッシュ。
  3. 2 iframe隔離を実装で読む — Safie の 160KB / 2.61MB 分離を分解する Safie の SDK は 160KB の loader が 2.61MB の iframe 本体を呼び出す構造。iframe 内には per-request nonce の CSP が張られ、親子は SYN/ACK ハンドシェイク + AES-GCM 鍵交換で通信します。なぜカメラ映像 SDK がこの構造を選んだのか、実装から読み解きます。