iframe から、丸型の共有ボタンと「いいね」へ。Promari SNS Share を公開しました。
作成: / 公開: / 内容更新:
執筆:tamito0201 / 掲載・運営:プロマリ
丸い共有ボタンの設計を、用語のポップアップと身近なたとえでたどります。表示・保存・計測・配信の内容を省略せず、四層の責任と設計判断を詳しく解説。全5ページ、連載予定は16章。
自分の環境で動いた。その先へ届けるには
明日も同じコードを配る――固定CDNとSRI
中の仕組みが見えてきたところで、次は配布のお話です。共有部品のコードは、公開リポジトリのPromari Toolkitのplugins/wordpress/promari-sns-share/に置いています。このリポジトリには別の道具も入っていますが、全部をまとめて同じ版で出すのではなく、共有部品だけに版の目印(タグ)を付けて、単独で公開しています。MITライセンスなので、気になった方は実装を読みながら試してみてください。記事の導入例も、特定の版に固定しています。ただし、いいねの保存や会員の仕組みはサイト側の担当なので、この部品だけで全部そろうわけではありません。
まずは丸型の共有ボタンを置いてみましょう。下のHTMLのように、部品のプログラムを1回読み込み、ボタンを出したい場所に要素を置くだけです。同じページに何か所か置く場合も、読み込みは1回で足ります。ここで紹介しているのは、版を固定したURLで読み込む例です。
外部の配信サービス(CDN)から部品を読み込む形にしたのは、サイトの更新と、部品の公開を別々に行えるからです。その代わり、サイトを何も変えていなくても、配信サービスが止まったり、古いファイルが残っていたりすると影響を受けます。そこで二つの対策をしています。読み込むURLに版を書いて固定することと、届いたファイルの中身をSRIで確かめることです。配信が止まっても、記事本文は読めるようにもしています。依存をなくしたのではなく、何に頼っていて、止まったらどこまで困るのかをはっきりさせた、と考えると運用の判断がしやすくなります。
版を固定するのは、「昨日と同じURLだから、今日も同じ中身のはず」と思い込まないためです。「いつも最新版」を指すURLは更新の手間がない反面、こちらが何も変えていないのに、ある日動きが変わることがあります。URLに版を書いておき、上げるときに変更内容を確かめる。こうしておけば、今どのコードで動いているのかを、いつでも説明できます。版を上げる作業と、記事を書き直す作業も分けられます。
SRIは、読み込んだファイルが、事前に確かめたものと同じ中身かどうかを、ブラウザに照合してもらう仕組みです。scriptタグのintegrityに、ファイルの中身から計算したハッシュ(中身の指紋のような値)を書いておくと、ブラウザは届いたファイルから同じ計算をして比べます。一文字でも違えば、そのファイルは実行されません。つまり、URLが正しくても、中身が違っていれば止まるわけです。ただし、SRIが保証するのは「確かめたときと同じ中身か」だけで、そのコードに不具合がないことまでは保証しません。
ここで一つ落とし穴です。版を上げるときにURLだけ新しくして、integrityのハッシュを古いままにすると、ブラウザは「中身が違う」と判断して読み込みを止めます。安全のための仕組みが、正しく働いて止めているわけですね。URLとハッシュは必ず一組で更新し、実際に配信されているファイルから計算した値を使います。
<script type="module"
src="https://cdn.jsdelivr.net/gh/tamito0201/promari-toolkit@promari-sns-share-v4.2.3/plugins/wordpress/promari-sns-share/dist/promari-sns-share.min.js"
integrity="sha384-nCIh364xfb7diMmCsFYnEFyebzScyVVBiPny6eZFPS8W9X+aqCAWWvzVDKRKYDl+"
crossorigin="anonymous"></script>
<promari-sns-share variant="circle"
destinations="x,line,facebook,copy,native"
secondary="hatena,linkedin,email"></promari-sns-share>上の例のintegrityの値は、この版の配布ファイルとだけ対になる値です。URLだけ差し替えれば止まりますし、止まったからといってintegrityを消してしまうと、確かめる仕組みそのものを外すことになります。版を上げるときは、新しいファイルからハッシュを計算し直し、ブラウザで実際に読み込めるところまで確かめます。
版の固定とSRIは、同じことを二重にしているわけではありません。版の固定は「どれを使うか」を決め、SRIは「届いたものが本当にそれか」を確かめます。また、別のサイト(クロスオリジン)から読み込むファイルをSRIで確かめるには、配信側がCORSに対応している必要があり、読み込む側のscriptタグにもcrossorigin="anonymous"が要ります。URLだけをコピーせず、scriptタグの属性を一式そのまま使ってください。
SRIと配信側のCORS設定の関係は、MDNのSubresource Integrityで確認できます。導入例で読み込む配布ファイルも、こちらから開けます。
ハッシュが不一致なら読み込みは失敗します。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()),
]);
}公開リリースには、WordPressプラグインZIPもあります。現在のZIPはPHPでボタンを描かず、標準表示の<promari-sns-share>を出して、固定した配布物を読み込みます。ただし、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]
destinations = ["x", "line", "facebook", "qiita", "zenn", "note", "medium", "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
ameba = 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リポジトリ: 公開プラグインを検証(部品のディレクトリで実行)
cd plugins/wordpress/promari-sns-share
pnpm install --frozen-lockfile
pnpm run verify検証は、型検査、TypeScript・Python・PHPのテスト、生成物の一致確認に加え、WordPress上のブラウザ操作でも行いました。初期導入時には丸型6項目で、いいねの同期・再読み込み・取消、コピーと端末共有、計測の重複、モバイルの収まりを確かめました。現在は書く場所への共有先が加わり、主の列は9項目と「その他」です。ボタンを増やしたあとは、追加した共有先のコピーと投稿画面の動き、狭い画面での折り返しも確認する必要があります。日々の修正でも、見た目と操作の両方を確認していきます。
さて、ここまで動けばひと安心。と言いたいところですが、もう少しだけ確認を続けます。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の項目、設定の意味も、利用側が頼っている約束になり得ます。
たとえば共有の知らせ(イベント)の名前を変えると、ボタンは見た目どおり動くのに、サイトの計測だけが止まることがあります。以前の当サイトがそうでした。部品は設定からイベント名を読むのに、受け取る計測のコードは名前を直接書いて待っていたのです。手元のWordPressで名前だけを変えて試すと、ボタンは普段どおり押せて、エラーも出ないのに、計測へ渡った記録は0件でした。そこで今は、受け取る計測のコードも、部品と同じ設定から名前を読むように直しています。同じ手順で名前を変えても、計測への記録は届き続けることを確かめました。設定の生成スクリプトは、名前の形式と、いいねの知らせと名前が重ならないことを検査します。部品を使うほかのサイトにとっては、名前の変更は受け取る側を直す必要がある変更です。変更履歴では「ファイルを整理した」より、「受け取る側がどこを直す必要があるか」を説明します。見た目が変わらない変更ほど、インターフェースへの影響を丁寧に確認したいですね。
ビルドが通っても、型検査まで通ったとは限りません。使うツールによってはTypeScriptを変換するだけで、型の不整合を別コマンドへ任せます。設定生成・型検査・テスト・バンドルは、それぞれ何を保証するかを分けて確認します。最終的なファイルができたことと、配ってよい根拠が揃ったことは別なんですよね。ここを一つの「build成功」で済ませないようにしています。
lockfileは、依存パッケージの解決結果を記録します。同じ設定を使っていても、取得する依存の版が日によって変われば、同じ成果物を作るのが難しくなります。実行するNodeの版やパッケージマネージャの条件も合わせて記録すると、手元だけ動く問題を切り分けやすくなります。それでもOSなどの違いが残ることはあるので、「同じ入力」の範囲を曖昧にしないことが大切です。
公開するときは、タグ・配布ファイル・説明の対応を見ます。ソースの版と、CDNのURLが指す版が食い違っていたら、何を検証したのか分からなくなります。ZIPで入れるWordPress側と、CDNで配るブラウザ側に分かれるなら、どの組み合わせを確認したかも残します。ファイルを公開する作業と、利用者が再現できる形で届ける作業は、少し違うのです。
確認結果を残すときも、「問題なし」の一言より、何を実測したかを書きます。たとえば「スマートフォン幅で横にはみ出さない」「同じ要求を二度送っても件数が増えない」「元の画像URLが新しい内容を返す」。次の人が同じ条件を試せる表現にしておけば、将来の変更で比較できます。通っていない検査や、環境の理由で動かせなかった検査も、隠さず分けます。
復旧の考え方も、公開前に整理しておくと落ち着いて対応できます。ファイルだけ戻せばよい変更なのか、DBの内容や設定も関係するのか。戻した後に何を確認するのか。実際の退避方法は対象や運用の指示に合わせて選びますが、「以前の状態へ戻す」と言ったとき何を指すのかは、曖昧にしないようにします。今回は小さなプラグインでも、その考え方はもっと大きな機能へ持ち帰れます。
最後は、速さについても少し。「何だか速くなった気がする」と感じたら、同じ条件で比べてみましょう。速くなったと感じたら、同じ端末や回線の条件で、何を測ったかを記録します。最初の表示と二回目の表示ではキャッシュの条件が違います。開発者の高速なPCでは気付かない待ち時間もあります。見た目を整えたこと、通信量が変わったこと、利用者の操作が速くなったことを分ければ、改善を過大に説明せずに済みます。
次の変更が怖くない、小さな部品を作る
丸いボタンから始めたのに、保存の排他制御や配布のハッシュまでたどり着きました。ずいぶん話が広がりましたが、どれも読者の一回の操作につながっているんですね。二重に増えない、失敗を成功と伝えない、上下で状態が揃う。普段は何気なく押しているボタンにも、こうして中をのぞくといろいろな工夫があるものです。私が作りたかったのは、見た目を揃えるだけでなく、次に共有先や保存先を変えるときも追いかけられる部品でした。
今回のご紹介は、ここまで。次のページには、この部品の中をもう少し細かく見ていく連載の目次をまとめました。インターフェースの切り方、型で減らせる不正な状態、応答順序、保存の一意性、テストの境界。動いている実装と、別の案を選んだ場合の差を並べながら進める予定です。「自分ならこうする」という話も歓迎です。設計ラベルからのコメントはそのページの欄にも並び、最後の5ページ目では全ページのコメントをまとめて読めます。途中で気になった点も、その場で残してもらえたら嬉しいです。それでは、続きもお楽しみに!























このページ(4ページ目)の感想・質問・設計へのコメント