Butterfly を 5.6.1 から 5.7.0 に上げたときのリリースノートには、「内蔵の APlayer/Meting サポートを削除」と書かれていた。アップグレード後、aplayerInject の設定は無効になり、layout/includes/third-party/aplayer.pug と plugins.yml の関連項目も一緒に消えた。

/music/ には 188 個の <meting-js> があり、inject には固定プレイヤーも 1 つある。タグ自体は残っているのに、スクリプトの注入がなくなったので、ページ上には空のカスタム要素が並ぶだけだ。

普通なら話はここで「アップグレードがプレイヤーを壊した、すぐロールバックするか inject を自分で書こう」で終わる。だがまずベースラインを測ってみたところ、結果は少し意外なものだった。

アップグレード前から、実は 0 個だった

meting-js はデフォルトで MetingJS 内蔵のバックエンド api.i-meto.com を使う。これはすでにサービスを停止しており、レスポンスに CORS ヘッダーがないため、ブラウザからのリクエストはすべて失敗する。つまり、オンラインのプレイヤーはとうの昔に 1 つもレンダリングできていなかったのであって、ただ誰もわざわざ数えていなかっただけだ。

テーマのアップグレードは犯人ではない。それは、とっくに腐っていた床板をめくったにすぎない。この区別は重要だ。もしそのままバージョンをロールバックすれば、ページは「プレイヤーのコードがあるように見えて、実際はやはり 0 個」という状態に戻り、そのまま直ったと思い込み続けることになる。

手書きの inject、順序は絶対条件

修正方法は、自分で inject の中で順番に読み込むことだ:

<link rel="stylesheet" href="https://registry.npmmirror.com/aplayer/latest/files/dist/APlayer.min.css" media="print" onload="this.media='all'">
...
<script src="https://registry.npmmirror.com/aplayer/latest/files/dist/APlayer.min.js"></script>
<script>window.meting_api="https://meting-api.saop.cc/api?server=:server&type=:type&id=:id&r=:r"</script>
<script src="https://registry.npmmirror.com/meting/latest/files/dist/Meting.min.js"></script>

window.meting_api は Meting.min.js より前に設定しなければならず、1 行遅れても効かない。CSS は media="print" + onload で all に変更し、初回描画をブロックしないようにする。

pjax の接着スクリプトは実はデッドコードだった

テーマにはもともと pjax フックの一節があり、ページ切り替え時に destroyAplayer で再構築していた。APlayer 1.10.1 を調べてみると、window.aplayers はすでにメンテナンスされておらず、その destroyAplayer にはそもそも破棄すべきものがなかった。

一方、MetingJS v2 は標準のカスタム要素であり、ページ切り替え時には connectedCallback / disconnectedCallback 自身が再構築と破棄を行う。だから今回の結論は接着剤を足さないことで、ライフサイクルは要素自身に任せる。

直したら数える

修正後の実測値:

  • プレイヤーのレンダリング数 0 → 167/188;
  • pjax の往復後、ページ内のプレイヤーはすべて再構築される;
  • 固定プレイヤーは常に同一の DOM ノードで、再生が途切れない。

残りの 21 個のうち、20 個は type="album" ——自前の Meting API がまだこのタイプに対応しておらず、400 を返す。もう 1〜2 個は、188 個の同時リクエストがレート制限を引き起こしたことによる偶発的な失敗だ。どちらも API サーバー側の問題でこのリポジトリとは無関係であり、commit message にもそう書いて、「修正済み」には含めていない。

167/188 は満点ではないが、検証し直せる数字ではある。レンダリング数、pjax の往復、固定ノードの同一性、それぞれに観測点がある。

アップグレード時はまず 2 つの勘定を分ける

依存関係のアップグレードで最もコストが高いのは設定の書き換えではなく、「今いったいどういう状態なのか」をアップグレード前に説明できる能力を持つことだ。今回のベースラインは「0 個」という一言だけだったが、その後のすべての判断を決めた:

  • もし 0 がアップグレードによるものなら、5.7.0 の変更を疑うべきで、ロールバックも視野に入る;
  • もし 0 が昔からの借金で、アップグレードはそれを可視化しただけなら、直すべきはバックエンドと注入順序であり、ロールバックではない。

ついでにデッドコードの一節も片付いた。もしそのとき手を抜いて、destroyAplayer をそのまま新しい inject に持ってきていたら、それも「動いて」はいただろう。ただ、ページを切り替えるたびに空の配列に対して無意味な処理を 1 回行うだけで、次の人はなぜそれが存在するのかさらに分からなくなる。

まず「もともとどうだったのか」を測ってから、「アップグレードが何を壊したのか」を決める。