採用技術問わずWebサイトに設置できるソーシャルシェアボタンを Web Component として開発する

先日、「”地上最強”のエンジニア向け情報サービス」を謳うTechFeed Proが新規アップデート。様々な機能追加がなされ、更に多くの情報収集が可能となりました。

今回はそんな TechFeed のアップデートで追加された URL 共有機能の導線として、 techfeed-share-button パッケージを OSS として公開した記録を紹介します。

本記事では、

  • 依存側の技術要件が不明または多岐に渡るケースの選択肢としての Custom Elements
  • NPM パッケージとして提供と CDN SDK としての提供の両立

あたりの情報について紹介できればと思います。

※ TechFeed との関係性について

TechFeed Pro については構想段階で相談をいただき、以後 Vue.js Expert として関わっています。
本開発及びエントリは私的に行った活動の記録であり、対価を得ておこなっている PR 記事の類ではありません。

techfeed-share-button パッケージについて

NPM 並びに UNPKG CDN にて提供されている、ソーシャルシェアボタンとなります。

今この下に存在しているボタンが動作サンプルであり、 Custom Element として展開されます。

基本的には以下のように、 url を所持したカスタムタグを設置するだけで動作します。

<!-- URL attribute がない場合は console.warn にて警告 -->
<techfeed-button url="https://github.com"></techfeed-button>

<!-- フレームワーク側で適切に ignore できていれば defer で問題ない -->
<script defer src="https://unpkg.com/techfeed-share-button@0.1.1/dist/web/widget.js"></script>

ソーシャルシェアボタンのパッケージとしては以下のような特徴が存在します。

  • LitElement にて開発されている Custom Element として提供
  • CDN からの展開にも対応しており、CMSへの埋め込みも容易
  • CJS/ESM両方をNPMパッケージとして専用のバンドルを用意し、React/Vueから適切に利用も可能

ソーシャルシェアや Embed となると iframe か直 script タグでの操作しか存在せず、ハンドリングしづらいケースが非常に多いですが、TechFeed は利用者も開発者も技術者であり、開発者が組み込みやすい形であることは強く求められるため、このような提供手段を用意してみました。

触ってみたいかたは、以下の CodeSandbox もどうぞ。

ソースコードも MIT ライセンスのもと GitHub にて公開しており、パッチなどはいつでも受け付けています。

アイコンについては、 TechFeed 公認を得て本パッケージに限り利用が許諾されているものとなります。その他の目的への転用はお控えください。

TechFeed ボタン作成の経緯

簡単に techfeed-share-button パッケージについて紹介したところで、ここからはそもそもの作成の経緯に時間を巻き戻し、順番に紹介していきます。

冒頭で紹介したとおり、 TechFeed は 2020/12/08 にアップデート行いました。

今回のアップデートでは様々な機能が追加されましたが、その一つに、URL共有機能が存在します。

これはもはや日常となった Twitter の intent や facebook の share.php などのような、任意の URL の情報をそのメディアで拡散する機能の TechFeed 版。つまりは普段 Twitter ボタンや Facebook シェアボタンを押した先の遷移ページ相当が実装されたことになります。

これによってこれまではソーシャルクローラー+αという存在だった TechFeed それ自体がソーシャル色を持つようになります。

ですが TechFeed として現状実装されているのはシェア先のページだけ。TechFeed Pro の Web 版または Chrome Extension からシェア画面を呼び出すことができるものの、利用者側の環境に依存するものでした。

Expert 限定のβテスト参加時にこの機能を見かけた時、「今は存在しないシェア導線へのシェアボタンを作ってメンテナンスするのは技術者的に面白いかもな」という好奇心(と、Chrome Extension というものが好きじゃないという個人的な理由)が生まれ、「どんなWebサイトでも埋め込める」「他のシェアボタンくらいに楽」な TechFeed シェアボタンを作って、URL 共有の一般公開時にコミュニティ製品としてリリースすることにしました。

早速提案したらβテスト初日に受け入れられて開発がはじまる図

ちなみにこの後ロゴの利用まで快諾していただき、ボタン作成が非常に楽になりました。ありがとうございました!

技術の選定

せっかく珍しいソーシャルシェアボタンを開発するという機会ができたことなので、真剣に考えてみます。

ソーシャルシェアボタンとして要求される要件は、ざっくり言うと以下のようなものとなります。

  • 技術問わず利用できる
    • React専用/Vue専用では話にならない
    • ときには JavaScript フレームワークがないケースも考慮する
      • 技術メディアでも中身が WordPress なケースはよくある
  • NPMから利用できる/CDNから利用できる
    • 技術的には CDN から利用できるだけで要件を満たすことはできるが、現実的な利用体験を考えると NPM パッケージの提供も必須
    • document.createElement を重ねてマウントするようなパッケージはリアクティブと相性が悪い
    • できれば内部的な事情がフレームワークと切り離されている方が良い
    • CDN は個人でコストを払いすぎない形で提供したい
  • その他
    • ssr: false にした Nuxt.js plugin として扱いやすいようにする
      • 個人的に Custom Element は SSR と共存しつつ役割分担できることが必須と考えている
      • そのベンチマークとしてこれがやりやすいかを指標としている

この辺りの事情を考えると、総合して「Custom Element を利用し、フレームワークフレンドリーに開発はするが専用パッケージは作らない」ことがベストという結論をここでは出しました。

Web Components に傾向しすぎると React/Vue との親和性が劣悪になり(世の多くの Custom Element はその宗派からこれになりがちな気がする)、Stencil のような Custom Element/React/Vue それぞれ向けのランタイムを吐くのは個人の余力でメンテナンスするには厳しいという判断からです。

TechFeed は長らく Ionic を利用しているのを知っていたことあり、贔屓目に Stencil の採用を考えなかったわけではないですが、利用者体験と開発者のコストどちらからとっても最善とはいえなさそうです。

Custom Element の作成手段としては、最も良好な API のもとに薄く開発できる LitElement を採用しました。

配信の仕組みについて

内部的な実装は数十行程度のものなので直接ソースコードをみていただくとして、CJS/ESM/CDN版のそれぞれのビルドの使い分けを紹介しておきます。

今回、実用性のある範囲で可能な限り薄い技術としてLitElementを採用しましたが、LitElementのオフィシャルテンプレートはWeb標準でスマートにと言った思想とは真逆をゆく環境全部盛りボイラープレートだったので、自分で環境を整えてしまうことにしました。

といっても依存しているパッケージは開発環境として Parcel、本番ビルドとして Rollup を採用し、その他の依存は TypeScript だけというシンプルな構成を採用。Web Standard で軽量にいくなら依存も増やしたくないのは世の常なので、最小で進めます。

ということで開発環境は Zero-Configuration の Parcel なので書くものは tsconfig と rollup の設定ファイルだけ。

今回は単純な TypeScript のビルド結果を dist に用意しつつ、コンポーネント単体とボタンウィジェットを別途 ESM/CDN ビルドとして用意することにしました。

それぞれ "main" 相当と "module"、最後に <script> から呼び出すものに対応しています。

import resolve from '@rollup/plugin-node-resolve';
// import { terser } from 'rollup-plugin-terser';
import typescript from '@rollup/plugin-typescript';
import minifyHTML from 'rollup-plugin-minify-html-literals';

export default [
  {
    input: 'src/components/techfeed-button.ts',
    output: {
      dir: 'dist/components/',
      format: 'es',
    },
    plugins: [
      minifyHTML(),
      typescript({
        tsconfig: './tsconfig.el.json'
      }),
      resolve(),
    ],
  },
  {
    input: 'src/widget.ts',
    output: {
      dir: 'dist/web/',
      format: 'es',
    },
    plugins: [
      minifyHTML(),
      typescript({
        tsconfig: './tsconfig.cdn.json'
      }),
      resolve(),
    ],
  },
];

それぞれの tsconfig は対象のランタイムを最小限にすることで余計な変換を減らす目的、出力パス変更の目的で、それぞれ軽微な調整を加えています。

また、CDN 版については専用の widget.ts を用意しています。

通常、<script> タグからソーシャルボタンを呼び出す場合は特にユーザー側でのコード記述を必要とせずボタンが表示されることが期待されているはずなので、本体をラップした上で Custom Elements の登録までを行うための IIFE ようなものとして利用してもらうことを目的としてエントリポイントを分割しました。

冒頭で紹介したコードでボタン表示までができるのは、この専用エントリポイントによるものです。

// widget.ts
import TechFeedButton from './components/techfeed-button'

customElements.define('techfeed-button', TechFeedButton)

一方、フレームワーク、特にSSR/SSGから利用する場合は、 process.browser などのフラグによって分岐したいケースが大半であるため、export は以下のような register するだけの関数と、コンポーネント本体となっています。

import _TechFeedButton from './components/techfeed-button'

export const TechFeedButton = _TechFeedButton

export default function define() {
  customElements.define('techfeed-button', TechFeedButton)
}

このように設定することで、React/Vueとかち合わず、なおかつCDNからの利用も可能なバンドルを作成できます。

また、 npm publish に含んでおくことで、それぞれで快適に利用できるだけでなく、npm package を自動的に CDN に公開してくれる Web サイトである UNPKG の恩恵を受けることもできます。

実際に、TechFeed は unpkg からの読み込みを推奨しています。

一応対案として UMD としてパッケージを提供する手段もありましたが、どのみち Rollup 環境を作るのであれば、それぞれで最適化ができるこのケースのほうが幾分か筋が良さそうです。

おわりに

ということで今回は 12/08 に公開された TechFeed ソーシャルシェア機能のためのボタンの開発について紹介しました。

冒頭でも言及した通り、私は TechFeed Pro Vue.js Expert としてプロジェクトに参画しています。

エキスパートへの期待値としては直接は多くの開発者に情報が入ってこないような海外ベースの新鮮な情報を発信してほしい。ということなので、主に TechFeed 側では、Nuxt.js 周りの新規の情報が入ってきたら、早め早めにアナウンスしていく。といった具合です。

GitHub のフィードから得た情報を噛み砕いて発信しているので、もし楽にそういった情報を追いたいというかたはぜひ。

https://techfeed.io/people/@potato4d

LitElement などの Web Component ライブラリについて

Stencil 以外にも JSX をくれ!!!