NGW開発BLOG
← 記事一覧へ
·#014

337箇所を書き換えずに SweetAlert2 を大更新する互換シム

ダイアログ表示ライブラリ SweetAlert2 を古いv3系から最新のv11系へ更新した。約337箇所・173ファイルの旧API呼び出しを書き換えず、ライブラリ末尾に薄い互換シムを1枚足して橋渡しした話。

問題:更新したいが、呼び出しが約337箇所ある

ダイアログ表示ライブラリ SweetAlert2 を、非常に古い v3系(3.3.6)から最新の v11系(11.26.25)へ上げたかった。理由はセキュリティと保守だ。だが困ったことに、旧APIの呼び出しがコードベース全体に約337箇所・173ファイルも散らばっていた。しかも旧v3と新v11ではAPIが根本的に違う。関数名も、引数の渡し方も、結果の受け取り方も変わっている。

素直にやるなら、337箇所を全部書き換える。だがこれは工数もリスクも大きい。書き換え漏れが1つでもあれば、その画面のダイアログは静かに壊れる。テストで全経路を踏み切れる保証もない。「更新したい」という動機の割に、代償が重すぎた。

そこで別の道をとった。呼び出し側を一切触らず、ライブラリファイルの末尾に『互換シム(compatibility shim)』を1枚だけ足す。旧APIを新APIへ橋渡しする薄い適応層だ。

なぜヘッダを触らずに全体へ効くのか

前提として、このシステムは全ページが同じJS/CSSを読み込む構成になっている。だから新しいライブラリ本体の末尾にシムを書き足すだけで、その変更は全ページへ一斉に届く。各ページのヘッダやincludeを編集する必要はない。1ファイルの追記が、173ファイルの呼び出しをまとめて生かす。

シムがやることは大きく4つだ。旧v3の type キーを新v11の icon へ変換する。本文 text に含まれる改行を <br> へ直す。v11で削除されたキーを落とす。そして旧の静的メソッドを新オブジェクトへ引き継ぐ。ここまでは機械的なマッピングで済む。

問題は5つ目、結果の受け取り方だった。

肝:本物ではない「thenable」を返す

旧v3の呼び出し規約はこうだった。

swal({ title: "削除しますか?", type: "warning", showCancelButton: true })
  .then(function (isConfirm) {
    if (isConfirm) doDelete();
  });

.then のコールバックには、OKなら真、キャンセルなら偽が渡ってくる。つまり「確定したときだけ処理する」という意味論だ。

ところが新v11は、結果を { isConfirmed, isDismissed, ... } というオブジェクトで返す。このオブジェクトは、たとえキャンセルでも常にtruthyだ。だから旧コードの if (isConfirm) にそのまま流し込むと、キャンセルもOK扱いになる。「削除しますか?」でキャンセルを押したのに削除が走る、という最悪の罠がここにある。

そこでシムは Swal.fire をラップし、Promiseそのものではなく『カスタムなthenable』を返す。.then(cb) を持つが、確定したときだけ cb を呼び、キャンセルやdismissでは呼ばない。旧v3の意味論を正確に再現するのだ。

function swal(opts) {
  const p = Swal.fire(translate(opts)); // type→icon 等を変換
  return {
    then(cb) {
      p.then(function (r) { if (r.isConfirmed) cb(true); });
      return this;
    },
    catch() { return this; }
  };
}

素朴に本物のPromiseを返さなかったのは意図的だ。本物を返すと、キャンセル時にも解決値が渡り、旧コードの分岐を誤らせる。「成功時だけ通す」という細い橋を、あえて非Promiseなthenableで架けた。

この判断が安全に成立したのは、事前の棚卸しがあったからだ。コードベースを調べると、呼び出しは swal(...) に単一の .then.catch を繋ぐ形しか無く、複雑なPromiseチェーンや async/await との合流が存在しなかった。パターンが限定的だと確認できたから、非Promiseなthenableでも破綻しないと踏めた。

運用ルールも決めた。新規のダイアログも旧形式で書き続ける。直接 Swal.fire を呼ばない。シムを外さない。境界の意味論を1箇所に閉じ込め続けるためだ。

AIとの協働作業での学び

巨大なコールサイトを持つ依存を上げるとき、全書き換えは必ずしも正解ではない。多くの場合、境界に薄い適応層を1枚置くほうが費用対効果で勝つ。337箇所を触らず、追記1ファイルで更新を通せたのがその証だ。

ただし条件がある。

  1. 呼び出しパターンが限定的であることを実際に棚卸しして確かめること。「たぶん単純だろう」で架けた橋は、想定外のチェーンで折れる。
  2. 互換の意味論——とりわけ成功とキャンセルの判定——を旧仕様へ正確に合わせること。ここを1つ取り違えるだけで、シムは静かに逆の挙動をする。

薄いアダプタは怠慢ではない。成立条件を棚卸しした上で選ぶなら、それは最小の労力で最大の安全を買う、れっきとした設計判断だ。