← シリーズに戻る

Web Components と data属性: SDK 初期化3種を比較

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

5製品の分離戦略は YouTube iframe API: 993バイトを1行ずつ読む、プロトコル契約は postMessage の使い方と契約設計: 3世代を比較、版管理は cache-control 設定と版固定: 5製品で戦略が割れる で分解しました。今回は顧客が触る一番外側、「SDK をどう呼び出すか」の API 設計を扱います。5製品を並べると3スタイルに分かれます。

3スタイルの実装

2026-08-08 時点で各バンドルから抽出した初期化パターンです。

製品スタイル顧客が書くコード
YouTube命令的 (callback)<div id="player"></div> + window.onYouTubeIframeAPIReady = () => new YT.Player("player", {...})
Vimeo命令的 (constructor)<div id="p"></div> + new Vimeo.Player("p", { id: 123 })
Safie命令的 (namespace)new Safie.Player.StreamingPlayer(element, options)
Video.jsdata 属性 (auto-scan)<video data-setup='{"controls": true}'> (自動検出)
Mux PlayerWeb Components<mux-player playback-id="..."></mux-player>

命令的が3、data 属性が1、Web Components が1。それぞれ想定顧客のワークフローが違います。

SDK の初期化の3スタイルを、誰が初期化を始めるかで並べた表の図。命令的 (YouTube・Vimeo・Safie): 顧客は置き場所の要素と JS の呼び出し (new YT.Player("player", {…})) を書き、顧客の JS が SDK を呼ぶ。既存の埋め込みが多い場合に向き、YouTube は2010年頃の形を API を壊せないので残している。data 属性 (Video.js): 顧客は HTML に属性を書くだけ (video 要素に data-setup 属性) で、SDK が DOM を走査して data-setup の付いた要素をプレイヤーにする。CMS・静的サイトに向き、SPA では要素が後から作られるので videojs(id, options) を併用する。Web Components (Mux Player): 顧客は HTML にタグを書くだけ (mux-player タグに playback-id 属性) で、SDK がカスタム要素を定義し、書かれたタグがプレイヤーになる。React・Vue・Svelte のどれからも「ただの HTML タグ」として使える

順に見ます。

命令的 API: YouTube 型

<script src="https://www.youtube.com/iframe_api"></script> を貼ると、window に YT 名前空間が生えて、new YT.Player(id, config) で呼び出します。ただし SDK ロードが非同期なので、window に onYouTubeIframeAPIReady というグローバルコールバックを事前に定義しておく必要があります。

<div id="player"></div>
<script>
  window.onYouTubeIframeAPIReady = function() {
    new YT.Player("player", {
      videoId: "M7lc1UVf-VE",
      events: { onReady: onPlayerReady }
    });
  };
</script>
<script src="https://www.youtube.com/iframe_api"></script>

この設計は2010年頃の web API の書き方をそのまま2026年まで持ってきています。今から書くなら window.onYouTubeIframeAPIReady は使わず、Promise + 型定義が自然です。ただし YouTube はすでに膨大な既存埋め込みを抱えていて、API 破壊を出せません。

Vimeo は同じ命令的でも constructor を直接呼ぶ形で、顧客は ready の callback を書きません。

<iframe id="v" src="https://player.vimeo.com/video/76979871"></iframe>
<script>
  const player = new Vimeo.Player("v");
  player.play();
</script>

Vimeo の constructor は2つの形を受け付けます。上のように顧客が書いた iframe に new Vimeo.Player(iframe) を後から付ける形と、表の行のように空の要素と { id: 123 } を渡して SDK に iframe を作らせる形です (SDK が oEmbed で埋め込み用の HTML を取ってきて、要素の中に差し込みます。2026-09-30 の player.js)。どちらの形でも待ち合わせは SDK の中にあって、iframe を渡された場合は constructor が ping を送り、"ready" が返るまで play() などの呼び出しを SDK の中で待たせています。待ちを Promise の中に隠しているので、顧客側に callback が要りません。

同じ命令的 API でもロードを待つコードを書くのが誰かを YouTube と Vimeo で比べた図。YouTube は顧客が待ち合わせを書く: 顧客が window.onYouTubeIframeAPIReady を先に定義しておき、SDK が iframe_api を非同期でロードし、ロードが終わると SDK がその関数を呼び、顧客は関数の中で new YT.Player(…) を呼ぶ。Vimeo は待ち合わせが SDK の中: 顧客が new Vimeo.Player(…) に自分で書いた iframe か、要素と id を渡し、SDK は iframe なら ping を送り、要素なら中に iframe を作り、顧客はすぐ player.play() と書いてよく、SDK は "ready" が届くまで待ってから play を送る。待ちを Promise の中に隠しているので、顧客は callback を書かずに済む

Safie は namespace パターンで、window.Safie = { Auth, Users, Devices, Player, UIControl, Config } を loader が生やします。実装では:

window.Safie = { Auth: wn, Users: Dn, Devices: _n, Player: xn, UIControl: zn, Config: Qn }

Safie.Auth.initialize() → new Safie.Player.StreamingPlayer(element, ...) のような多段構造で (2026-10-01 に取り直した loader)、SDK の機能領域が広い(認証・デバイス管理・再生・UI 制御・設定)ことに対応した名前空間分割です。

window.Safie の6つの名前空間と、呼び出しが届く iframe を結んだ図。認証・利用者・デバイスは見えない中継の iframe へ送り、プレイヤーとタイムラインは画面に置く iframe を自分で作る。Auth (initialize() / setToken(token, type))、Users (queryInformation())、Devices (queryDevices() / queryThumbnail() / …) の3つは、見えない #safie-sdk-proxy の iframe へメソッド名を付けて invoke を送り、結果を待つ。この iframe は最初の呼び出しのときに作る。Player の new StreamingPlayer(element, options) は、渡した要素の中に UI の #safie-sdk-streaming-player の iframe を作ってライブ再生する。UIControl の new Timeline(element, config) は、渡した要素の中に UI の #safie-sdk-timeline の iframe を作って録画のタイムラインを出す。Config (Theme) は設定の値で、iframe は作らない。safie.link/sdk/js/api/v1/latest/ の loader (2026-10-01 取得)、主な関数だけを載せた

data 属性: Video.js 型

Video.js は HTML 側に data 属性を書くだけで自動的にプレイヤーに変換します。

<video data-setup='{"controls": true, "autoplay": false}'>
  <source src="movie.mp4" type="video/mp4">
</video>
<script src="https://vjs.zencdn.net/8.23.4/video.min.js"></script>

バンドル側の実装:

autoSetup) {
  var e = Array.prototype.slice.call(document.getElementsByTagName("video"));
  // data-setup を持つ video 要素を走査してプレイヤー化
}

DOM を自動スキャンする挙動なので、CMS (WordPress 等) や静的サイトジェネレータで JS を書きたくない環境に強いです。反面、SPA (React/Vue) では DOM が JS で動的生成されるため、data 属性が正しく認識される保証がなく、videojs(id, options) の命令的 API を併用することになります。

Video.js の data-setup の自動走査がいつまで働くかを、静的 HTML と SPA の2つの流れで示した図。題は「自動走査が拾うのは、走査の時点で DOM にある要素だけ」。Video.js は読み込まれた直後に DOM を走査し、後から JS が作った要素は自動ではプレイヤーにならない。静的 HTML・CMS (要素が最初から HTML にある) では、video data-setup が HTML にあり、video.js が読まれて 1ms 後に走査し、プレイヤーになって走査はそこで終わる。SPA (React・Vue、要素は JS が後から作る) では、走査の時点で要素がまだ無く、window の load までは 1ms ごとに走査し直し、その後に作った要素は拾われないので、videojs(id, options) を命令的 API で自分で呼ぶ。走査で video・audio・video-js 要素が1つでも見つかれば、そこで止まる。見つからなければ load まで繰り返し、load 後はやめる。vjs.zencdn.net/8.23.4/video.js の autoSetup (2026-10-01 取得)

Web Components: Mux Player 型

Mux Player はカスタム要素を大量に定義しています。

customElements.define("media-airplay-button", ...)
customElements.define("media-audio-track-menu", ...)
customElements.define("media-captions-button", ...)
customElements.define("media-cast-button", ...)
// 他多数

顧客が書くコードは:

<mux-player
  playback-id="qxb01i6T202018GFS02vp9RIe01icTcDCjVzQpmaB00CUisJ4"
  metadata-video-title="Test"
></mux-player>
<script src="https://cdn.jsdelivr.net/npm/@mux/mux-player@3.7.0"></script>

Web Components なので React/Vue/Svelte のどれからでも「ただの HTML タグ」として使えます。SPA との親和性が高く、コンポーネント指向のフレームワークが主流の2020年代に自然な設計です。

トレードオフは、Web Components を理解しない古いブラウザ (IE11 等) が対象外になること、shadow DOM のスタイル境界を顧客が理解して使う必要があること、SEO クローラーで shadow DOM 内が index されない可能性があることです。今から新規に SDK を作るなら第一選択ですが、法人顧客の要件次第で命令的 API を追加提供する場合があります。

二重ロード問題

YouTube・Vimeo・Mux Player・Video.js の4製品で、二重ロード対策のコードを確認できました。Safie の loader は window.Safie を無条件に代入し直す作りで、同じ種類のガードは見当たりませんでした (2026-09-30 に取り直した版)。「顧客が誤って同じ <script> タグを2回書く」「SPA でルート遷移のたびにロードしてしまう」「CMS プラグイン2つが同じ SDK を独立に読み込む」といった状況が実際に発生します。

命令的 API の場合 (YouTube):

if (!window["YT"]) YT = { loading: 0, loaded: 0 };
if (!YT.loading) {
  YT.loading = 1;
  // 実際のロード処理
}

window.YT の存在チェックで、2回目以降は初期化をスキップします。YT.loading フラグで「ロード中」と「ロード済み」も区別しています。

Web Components の場合 (Mux Player):

customElements.define("mux-player", ...) を同じ名前で2回呼ぶと、2回目で NotSupportedError (DOMException) が投げられます。Mux Player のバンドルは customElements.get("mux-player") || customElements.define("mux-player", ...) の形で、定義済みなら define を飛ばします。中で使う media-* の要素も同じ形です。命令的 API のフラグ管理と本質的に同じ問題が、Web Components の仕様で強制されるだけです。

data 属性の場合 (Video.js):

DOM スキャンが2回動くと、同じ video 要素が2回プレイヤー化されようとして壊れます。Video.js はプレイヤー化した要素に player プロパティを付け、自動セットアップはそれが付いた要素を飛ばします。videojs(id) を2回呼んでも、2回目は既存のプレイヤーを返します。副作用の冪等性を要素側に持たせる設計です。Vimeo も印は要素側で、iframe を差し込んだ要素に data-vimeo-initialized を付け、印の付いた要素には差し込み直しません (2026-09-30 の player.js)。

「読み込み済み」の印をどこに置くかを5製品で並べた表の図。YouTube は window に window.YT と YT.loading を置き、2回目は初期化を飛ばして本体を2回注入しない。Vimeo は要素に data-vimeo-initialized を付け、印の付いた要素には iframe を差し込み直さない。Video.js は要素に player プロパティを付け、自動セットアップが飛ばし、videojs(id) は既存のプレイヤーを返す。Mux Player はカスタム要素の登録簿を customElements.get(…) || で見て、定義済みなら define しない (2回 define すると NotSupportedError)。Safie は見当たらず、window.Safie を無条件に代入し直し、同じ種類のガードは無い (2026-09-30 に取り直した loader)

自作するときの選び方

想定顧客適した初期化スタイル二重ロード対策
静的 HTML / CMS ユーザーdata 属性要素側フラグ (player 相当)
SPA (React/Vue) 開発者Web ComponentscustomElements.get() 事前確認
古い既存埋め込みが多い命令的 (callback)window.YOUR_SDK 存在チェック
新規開発、多様な顧客Web Components + 命令的の両対応上記2つ両方

スタイルを1つに絞ると API 表面が狭くなり、ドキュメント・テスト・互換性維持のコストが下がります。5製品はどれも1〜2種類に絞っています。全部提供する SDK は少ないです。想定顧客のワークフローを最初に決めて、そこから逆算して選ぶのが良い設計です。

次: 全部組み合わせて自分で作る

これで5つの spoke が揃いました。埋め込み形態(iframe / DOM 直挿し)、loader/本体の分離、CSP と iframe 隔離、postMessage 契約、cache-control 戦略、初期化スタイル。次で最小の埋め込み SDK を端から端まで組み、6つの決断を実装で明示的に選び直します。

実測日は 2026-08-08。初期化 API は破壊的変更を出しにくいため長期安定します。二重ロード対策の実装は年単位で変わりません。