iframe から、丸型の共有ボタンと「いいね」へ。Promari SNS Share を公開しました。
作成: / 公開: / 内容更新:
執筆:Takaomi Murasaki / 掲載・運営:プロマリ
丸い共有ボタンの設計を、用語のポップアップと身近なたとえでたどります。表示・保存・計測・配信の内容を省略せず、四層の責任と設計判断を詳しく解説。全5ページ、連載予定は16章。
自分の環境で動いた。その先へ届けるには
明日も同じコードを配る――固定CDNとSRI
中の仕組みが見えてきたところで、次は配布のお話です。コードは公開リポジトリのPromari Toolkitに置き、plugins/promari-sns-share/で管理しています。モノレポ全体をまとめて同じ版で出すのではなく、共有プラグイン用のタグで独立リリースする形です。MITライセンスで公開していますので、気になった方は実装を読みながら試してみてください。記事の導入例は1.1.0に固定しています。サイト側の会員・コメント連携は別の責任なので、この配布物だけで全部揃うわけではありません。
まずは丸型の共有ボタンを置いてみましょう。下のHTMLで、部品を1回読み込み、出したい場所へ要素を置きます。同じページへ複数並べる場合も、スクリプトの読み込みは1回です。ここで紹介しているのは1.1.0の例になります。
CDNを使うと、サイトの配備とブラウザ部品の配布を分けられます。一方で、ホスト側のコードが変わっていなくても、取得先の可用性やキャッシュに影響されます。そこで配信URLは版を固定し、取得内容はSRIで確認します。外部の配信が止まったときも記事本文は読める構成にする。依存をなくしたわけではなく、依存先と失敗時の影響範囲を明示したと捉えるほうが、運用時の判断に役立ちます。
バージョンを固定する理由は、「昨日と同じURLだから今日も同じ中身」と思い込まないためです。最新版を指す可変のURLは更新が楽な一方、サイトを変更していないのに動作が変わる可能性があります。版を指定し、更新するときに変更内容を確認するようにすれば、どのコードで動いているか説明しやすくなります。新しい版へ上げる作業と、記事を編集する作業も切り分けられます。
SRIでは、取得したファイルのバイト列とintegrityのハッシュをブラウザが照合します。URLが合っていても内容が違えば実行しません。便利なのは、取得できたことと、採用した内容であることを分けて検証できる点です。ただし、最初にレビューしたコードの正しさまでは保証しません。更新時はURL・配布ファイル・ハッシュを一組で確認する。この単位を崩すと、自分たちの更新で読み込みを止めてしまいます。
ここで一つ落とし穴です。URLだけ新しい版へ変えて、SRIを古いままにすると、ブラウザは不一致として読み込みを拒否します。安全のための機能が、正しく働いて止めているわけですね。URLとハッシュを一組として更新し、実際の配信ファイルで確認します。SRIは取得内容の一致を検査しますが、その内容に不具合がないことまでは保証しません。
<script type="module"
src="https://cdn.jsdelivr.net/gh/tamito0201/promari-toolkit@promari-sns-share-v1.1.0/plugins/promari-sns-share/dist/promari-sns-share.min.js"
integrity="sha384-olzdsOR0ONB5EJcWwUhxBD6WAAMzJpq0D7pCHUXhvNy0g3IOktN8eOBSBLmACd3o"
crossorigin="anonymous"></script>
<promari-sns-share variant="circle"
services="x,line,facebook,copy,native"
secondary="hatena,linkedin,email"></promari-sns-share>導入例のintegrityは、その版の配布ファイルと対になる値です。URLだけ差し替えると不一致になりますし、古いハッシュを削除して動かしてしまうと確認の仕組みを外したことになります。更新手順では、まず取得したファイルからハッシュを計算し、ブラウザでも読み込めるところまで確かめます。
バージョン固定とSRIは、同じ対策を二重にしているわけではありません。前者は何を採用したかを指定し、後者は実際に受け取った内容を照合します。さらにクロスオリジンのSRIでは配信側のCORS対応も関わります。例のcrossorigin="anonymous"も含めて、URLだけコピーするのではなく読み込み条件を一式で確認したいところです。
SRIと配信側のCORS設定の関係は、MDNのSubresource Integrityで確認できます。導入例で読み込む1.1.0の配布ファイルも、こちらから開けます。
ハッシュが不一致なら読み込みは失敗します。CDNに障害が起きる場合もあります。そのため共有部品の読み込みに成功しないと記事本文も表示できない構成にはしません。また、scriptを1回読み込むことと、カスタム要素を必要な場所へ複数置くことを区別します。ページごとに古い配布物と新しい配布物を混在させないよう、読み込みを管理する場所も一つにします。
type="module"で読み込むコードと、ページへ複数置くカスタム要素は別々に管理します。要素の数だけ定義を登録する必要はありません。むしろ異なる版のスクリプトが同じ要素名を登録しようとすると、初期化の競合を招きます。テンプレートごとにscriptタグを追加するより、読み込みの所有者を一つにして、配置側には要素を出す責任だけを残します。ここでも、配置と初期化を分ける判断が効いてきます。
導入時は、まず一つの要素で表示を確かめ、次に複数配置、最後にいいねの保存連携へ進むと、どの段階で問題が起きたかを追いやすくなります。最初から共有・保存・計測を全部つないでしまうと、ボタンが反応しない原因が、部品の読み込みなのか、APIなのか、同意管理なのか分かりにくくなります。
開発者ツールでは、NetworkでJavaScriptが取得できたか、Consoleで登録やSRIのエラーがないかを見ます。DOMにタグがあることだけでは、部品の定義が完了しているとは言えません。逆に、スクリプトがHTTP 200でも、ブラウザが内容の検査で実行を止める場合があります。取得・実行・登録・表示を一段ずつ分けて確認するのが近道です。
ちなみに、サンプルはどこで開くかにも気を付けてみてください。file://で直接開く場合と、ローカルのHTTPサーバーから開く場合では、モジュールやブラウザAPIの条件が変わることがあります。実際に使う配信方法に近い環境へ置き、権限や通信の条件も含めて確かめます。「サンプルが悪いのか、自分の環境が違うのか」を分けられるよう、動いた条件も一緒に控えておきたいですね。
プラグインを止めても、記事は読めるようにする
それでは、普段記事を載せているWordPressには、どんな仕事を任せるのでしょうか。今回は、ブラウザ部品と記事データをつなぐアダプターとして使っています。記事URLやタイトル、共有設定を渡し、いいねの要求をサイト側のAPIへ接続する。ブログ・ニュース・インタビューのテンプレートは、決めた位置から同じ描画関数を呼びます。ブラウザ部品に投稿タイプやWordPressの認証方法まで覚えさせないので、別のホストへ持ち出すときは接続部分を差し替える、という見通しが立ちます。
<?php
if (function_exists('promari_sns_share')) {
promari_sns_share([
'url' => get_permalink(),
'title' => wp_strip_all_tags(get_the_title()),
'placement' => 'article_bottom',
'like' => true,
'caption' => 'この気づきを、誰かにも。',
'reaction_endpoint' => function_exists('pmc_reaction')
? add_query_arg([
'action' => 'pmc_api',
'op' => 'reaction',
'post' => get_the_ID(),
], admin_url('admin-ajax.php'))
: rest_url('pm-news/v1/reaction/' . get_the_ID()),
]);
}公開リリースには、PHPで従来表示を描画するWordPressプラグインZIPもあります。ただし、ZIPを有効にするだけで当サイトの丸型・いいね保存APIが揃うわけではありません。同じ丸型共有欄を導入する場合は上のWeb Componentを利用し、いいねを使う場合はサイト側の保存処理を接続してください。標準表示、丸型表示、サイト側の接続処理を区別して導入することが大切です。
function_existsで囲んでいるのは、プラグインが止まったときの依存をここで切るためです。関数がない状態でも本文の描画は続ける。共有欄を必須依存にするなら別の判断もありますが、このサイトでは記事を読めることを優先しました。ただし、停止を黙って成功扱いにするわけではありません。配備後の確認では共有欄が出ていることも別に検査し、読者への影響を抑えることと、不具合を見つけることを両立させます。
WordPressのフックは処理を追加できる便利な仕組みですが、自動挿入とテンプレートからの明示呼び出しを同時に使えば、同じ部品が二重に出る可能性があります。登録された処理が何回、どの文脈で呼ばれるかを確認し、表示場所の決定者を一つに揃えます。WordPressへ導入したことだけで、いいねのサーバー側保存まで自動的に付くわけではない点にも注意してください。
フックを使う場面では、actionとfilterを区別します。actionは節目で処理を実行し、filterは渡された値を加工して返します。the_contentは投稿本文に対するfilterです。本文を受け取って共有欄を付ける場合でも、本文がどの画面や処理で使われるのかを確認します。記事テンプレートから明示的に置く方式と混ぜると、どちらも正しく動いているのに二重表示になることがあります。
WordPressでの役割は、action、filter、the_contentの公式説明に対応します。
actionとfilterの違いを覚えるだけでは、二重表示は防げません。the_contentのfilterで挿入する処理と、テンプレートからの明示呼び出しが両方有効なら、どちらも正しく動いて重複します。フックの選択に加えて、どのレイヤーが配置を所有するかを決める必要があります。今回のようにテーマが冒頭と末尾を管理するなら、自動挿入を無効にする理由も設定の近くへ残す。後から善意で有効化して戻してしまうのを避けたいんです。
the_contentへ処理を足した場合、「記事ページだから一回だけ呼ばれるはず」と決め付けないようにします。テーマや別のプラグインが本文を取得して加工することもありますし、一覧・抜粋・フィードなど文脈も違います。対象の記事種別、メインのループか、公開状態はどうか。表示する条件を確かめるのは、見た目の問題だけでなく、意図しない場所へ機能を出さないためでもあります。
アダプターとしてWordPressを使う、というのは、WordPressの都合を全部ブラウザ部品へ持ち込まないことです。記事のURLやタイトルを取り出し、必要な設定を渡し、保存APIへつなぐ。その翻訳を接続側へ寄せれば、表示する部品は記事IDの保存場所やテーマのファイル構成を知らずに済みます。
停止時のことも考えておきます。部品が存在しないときは本文を読めるようにし、配置のための見出しだけが取り残されないか確認する。逆にプラグインが有効でも、非公開記事やパスワード保護記事へ反応APIを開いてよいとは限りません。公開ページで動く確認と、公開してはいけない状態で動かない確認は、対になっています。
動くところを見たら、今度は止めてみましょう。もちろん、試すのはローカル環境です。ローカル環境でプラグインを止めても本文が読めるか。再び有効にして、共有欄が二重にならず戻るか。表示されたときだけを見るより、停止と復帰を一往復すると、依存の置き方がよく分かります。止める順序でサイト全体が壊れるなら、任意の部品として切り離せていない場所があるのかもしれません。これは、どんな機能を外部の部品へ任せるときにも使える確認です。
見た目が正常でも、古いコードかもしれない
当サイトでは、共有先やUTMなどの設定をTOMLから生成しています。記事の冒頭と末尾はテーマから明示するため、自動挿入・サイドバー・固定バーの配置を無効にしています。 設計判断:表示する場所はテーマが決める次はサイト設定の抜粋です。
[share]
services = ["x", "line", "facebook", "copy", "native"]
heading = ""
[share.placements]
post_types = ["post"] # 記事の冒頭・末尾はテーマが配置する。
article_top = false
article_bottom = false
sidebar = false
floating = false
[share.secondary]
hatena = true
linkedin = true
email = true
copy = false # 主役側にあるため二重に出さない。
native = false
[share.tracking]
attribute = "data-share"
event_name = "promari-sns-share"設定例を読むときは、まず「配置」の部分へ目を向けてみてください。自動挿入をfalseにしていても、このサイトの共有欄が消えないのは、テーマが決めた場所から明示的に呼んでいるからです。設定の値と、サイト全体の見た目を一対一で結び付けると混乱します。設定が制御している処理の範囲まで読むと、同じfalseの意味がつかめます。
設定生成の–checkは、画面を見て「良さそう」と判断する検査ではありません。正本から作るべき内容と、保存されている成果物が一致しているかを確かめます。コードレビューで生成物を大量に読むのが大変でも、差分の入口が正本の変更と対応しているかは確認したいところです。生成物を手で直した形跡があれば、次の生成で消えてしまうかもしれません。
設定ドリフトは、エラーになるとは限らないのが面倒です。TOMLと生成物、リポジトリと配備先、記事のコード例と公開タグ。どれも少しずれていても、それらしく動いてしまいます。そこで一致を機械的に確認できるところはコマンドへ寄せます。生成物は再生成との比較、配備先はファイルのハッシュ比較。目視でしか判断できない表示や操作へ時間を使うためにも、同じかどうかの確認は機械に任せたいですね。
下のコマンドは、サイト側とToolkit側で実行場所を分けています。サイトのTOMLを検査することと、配布用の部品を型検査・ビルドすることは別だからです。CIへ組み込むときも、どの入力を読んでどの成果物を検証したかを固定します。別の作業ディレクトリにある古い生成物を確認して「通った」としてしまうと、緑の結果がかえって紛らわしいんですよね。コマンドの成功だけでなく、その対象までレビューします。
# WordPressリポジトリ: サイト設定を生成し、更新漏れを確認
python3 scripts/share-config.py --write
python3 scripts/share-config.py --check
# promari-toolkitリポジトリ: 公開プラグインを検証
pnpm install --frozen-lockfile
pnpm --filter @promari/sns-share verify検証は、型検査、TypeScript・Python・PHPのテスト、生成物の一致確認に加え、WordPress上のブラウザ操作でも行いました。丸型6項目、いいねの同期・再読み込み・取消、コピーと端末共有、計測の重複、モバイルの収まりを確かめています。日々の修正でも、見た目と操作の両方を確認していきます。
さて、ここまで動けばひと安心。と言いたいところですが、もう少しだけ確認を続けます。URL生成や入力の判断は小さな単位で、設定生成器は正本と出力の整合を確かめます。保存APIには同じ要求を再送し、不正な入力も渡してみます。最後にブラウザで、キーボード操作、メニュー、上下の同期、モバイルの表示を確認します。型検査が通っただけでは、共有画面が開くか、フォーカスがどこへ移るかまでは分かりません。確かめたいことに合った方法を選ぶのも、実装の一部だと思っています。
テストの一覧がずらりと緑になると、ほっとしますよね。でも、ブラウザを何度も動かすだけでは見つけにくい間違いもあります。URLの組み立てなら、入力と出力を短い単位で確かめるほうが速く、失敗した理由も見えます。保存APIなら、同じ要求の再送や不正な入力を含めて、HTTPと保存結果を見ます。キーボードのフォーカスやメニューの位置なら、実際のブラウザが必要です。何を知りたいかによって、確かめ方も変わるんですね。同じ道具で全部を済ませようとせず、見たいところに合わせて選んでいます。
ユニットテストは小さな判断を、統合テストは部品どうしの接続を、E2Eテストは利用者に近い一連の流れを確かめます。境界の引き方はプロジェクトによって違いますが、「何を本物として動かしているか」を説明できると、テスト結果の読み方がはっきりします。保存先を代役にしたテストだけでは、実際のデータベースの制約まで通ったとは言えません。
Playwrightでは、読み手が実際に行う順番を試します。用語へ移動する、説明を開く、長い説明を最後まで読む、閉じて元の用語へ戻る。マウスのクリックだけを通すと、キーボードで開けない問題や、狭い画面で閉じる操作が隠れる問題を見逃します。確認する操作を具体的な言葉にしてから、自動化の手順へ変えます。
Playwrightのようなブラウザ自動化では、見出しやラベルを使って要素を探し、クリック後の状態を待てます。待つ時間を適当に長くするだけでは、遅い環境で失敗し、速い環境でも無駄に待つことになります。件数が更新された、メニューが開いた、といった意味のある状態を待つほうが、何を確認したか分かりやすいですね。
失敗のテストも用意します。APIが返らない、壊れたJSONが届く、同じ要求が二度届く、部品を外して再接続する。普段は起きにくい条件を自分で作ると、実装が「成功する一本道」だけになっていないか分かります。テストは正しさを宣言する儀式ではなく、思い込みを見つける実験として使いたいと思っています。
本番へ出した後も、もう一度見てみましょう。手元で確認したファイルと、実際に読み込まれているファイルは同じでしょうか。キャッシュに前の版が残っていないでしょうか。CDNのバージョンとSRIは合っているでしょうか。HTTP 200なら取得はできていますが、画像やイベント、APIの動作まで正しいとは限りません。配ったものを読み戻し、読者が触れる操作も試す。ここまで確かめて、ようやく今回の作業を区切れると考えています。
CIで気にしているのは、緑か赤かに加えて、そもそも何が実行されたかです。ジョブが起動前に止まった状態と、テストが実行されて落ちた状態では対応が違います。ローカルで通った検査を補足することはできますが、それをCIの通過と書き換えない。地味ですが、配布する版へどの検証結果を結び付けられるかに関わります。テストが増えるほど、実行条件も結果の一部として残しておきたいところです。
DockerでPHPやWordPressの実行条件を揃えても、本番との差は残ります。ファイル権限、キャッシュ、ネットワーク、データ量。だからローカルの再現環境と、本番の読み戻しは別の確認として持ちます。たとえばローカルでは最新のJSを読んでいるのに、本番だけ古いキャッシュを返すケースは、コンテナのテストだけでは見えません。環境を揃える努力と、揃わない部分を観測する仕組みをセットにしています。
配備の読み戻しでは、送ったファイルのハッシュと本番にあるファイルを照合します。記事本文はDBに入っているので、テーマの配備だけでは更新されません。画像も原本を置くだけで、既存の縮小画像まで自動で置き換わるとは限りません。読者へ届くものが、ソース・DB・生成画像・キャッシュのどこから来ているかを追います。
HTTP 200は入口の確認です。その後に、見出しが読めるか、リンク先が合っているか、上下のいいね表示が揃うか、スマートフォンではみ出さないかを見ます。今回のようにページを分割した記事なら、正規URLや目次の移動先も確認します。配備コマンドが終わった瞬間ではなく、読み手が触れる状態を確認できたところで、ひと区切りにしたいですね。
モノレポで便利なのは、共有する契約の変更を同じ差分で追えることです。ただ、同じ場所で管理することと、同じタイミングでリリースすることは別。Toolkitでは共有プラグインを識別するタグを使い、他のツールの更新と区別します。そのぶん、タグに含まれるソースと配布成果物が対応しているかは確認が必要です。「リポジトリの最新」と「サイトが採用している版」を混同しない形にしておきます。
バージョン番号には、利用者へ変更の性質を伝える役割があります。SemVerでは、互換性を壊す変更、新しい機能、修正を、決めた規則で番号へ反映します。ただし、数字を三つに分けるだけでは意味が揃いません。何を公開APIとみなすかが必要です。メソッド名だけでなく、属性、イベント名、detailの項目、設定の意味も、利用側が頼っている約束になり得ます。
たとえばイベント名を変えたら、ボタンは見た目どおり動いても、サイトの計測が止まるかもしれません。これを内部だけの整理として扱うと、利用者は予想できません。変更履歴では「ファイルを整理した」より、「受け取る側がどこを直す必要があるか」を説明します。見た目が変わらない変更ほど、契約への影響を丁寧に確認したいですね。
ビルドが通っても、型検査まで通ったとは限りません。使うツールによってはTypeScriptを変換するだけで、型の不整合を別コマンドへ任せます。設定生成・型検査・テスト・バンドルは、それぞれ何を保証するかを分けて確認します。最終的なファイルができたことと、配ってよい根拠が揃ったことは別なんですよね。ここを一つの「build成功」で済ませないようにしています。
lockfileは、依存パッケージの解決結果を記録します。同じ設定を使っていても、取得する依存の版が日によって変われば、同じ成果物を作るのが難しくなります。実行するNodeの版やパッケージマネージャの条件も合わせて記録すると、手元だけ動く問題を切り分けやすくなります。それでもOSなどの違いが残ることはあるので、「同じ入力」の範囲を曖昧にしないことが大切です。
公開するときは、タグ・配布ファイル・説明の対応を見ます。ソースの版は1.1.0なのに、CDNのURLが別の版を指していたら、何を検証したのか分からなくなります。ZIPで入れるWordPress側と、CDNで配るブラウザ側に分かれるなら、どの組み合わせを確認したかも残します。ファイルを公開する作業と、利用者が再現できる形で届ける作業は、少し違うのです。
確認結果を残すときも、「問題なし」の一言より、何を実測したかを書きます。たとえば「スマートフォン幅で横にはみ出さない」「同じ要求を二度送っても件数が増えない」「元の画像URLが新しい内容を返す」。次の人が同じ条件を試せる表現にしておけば、将来の変更で比較できます。通っていない検査や、環境の理由で動かせなかった検査も、隠さず分けます。
復旧の考え方も、公開前に整理しておくと落ち着いて対応できます。ファイルだけ戻せばよい変更なのか、DBの内容や設定も関係するのか。戻した後に何を確認するのか。実際の退避方法は対象や運用の指示に合わせて選びますが、「以前の状態へ戻す」と言ったとき何を指すのかは、曖昧にしないようにします。今回は小さなプラグインでも、その考え方はもっと大きな機能へ持ち帰れます。
最後は、速さについても少し。「何だか速くなった気がする」と感じたら、同じ条件で比べてみましょう。速くなったと感じたら、同じ端末や回線の条件で、何を測ったかを記録します。最初の表示と二回目の表示ではキャッシュの条件が違います。開発者の高速なPCでは気付かない待ち時間もあります。見た目を整えたこと、通信量が変わったこと、利用者の操作が速くなったことを分ければ、改善を過大に説明せずに済みます。
次の変更が怖くない、小さな部品を作る
丸いボタンから始めたのに、保存の排他制御や配布のハッシュまでたどり着きました。ずいぶん話が広がりましたが、どれも読者の一回の操作につながっているんですね。二重に増えない、失敗を成功と伝えない、上下で状態が揃う。普段は何気なく押しているボタンにも、こうして中をのぞくといろいろな工夫があるものです。私が作りたかったのは、見た目を揃えるだけでなく、次に共有先や保存先を変えるときも追いかけられる部品でした。
今回のご紹介は、ここまで。次は、その中をもう少し細かく見ていきたいと思います。契約の切り方、型で減らせる不正な状態、応答順序、保存の一意性、テストの境界。動く実装と、別の案を選んだ場合の差を並べながら進める予定です。「自分ならこうする」という話も歓迎です。設計ラベルからのコメントはそのページの欄にも並び、最後の5ページ目では全ページのコメントをまとめて読めます。途中で気になった点も、その場で残してもらえたら嬉しいです。それでは、続きもお楽しみに!
このページ(4ページ目)の感想・質問・設計へのコメント