0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Chrome拡張の更新で「前回データが消える」問題を、上書きコピー手順で解決する

0
Posted at

自作のChrome拡張を社内やクライアントに配るとき、こんな相談をよく受けます。

「修正版を渡したら、前回の比較結果が消えて、またゼロから比較するはめになった」

機能追加のたびにユーザーが過去データを失うのは、拡張の出来とは別のところにある更新手順の設計ミスです。本記事では、なぜデータが消えるのかをChromeの仕組みから説明し、データを残したまま更新する手順と、その手順を支える実装をまとめます。

環境・前提

  • OS: macOS 14 / Windows 11(両方の手順を記載)
  • Chrome: 120 以降(Manifest V3)
  • 配布形態: 社内・取引先へのUnpacked配布(デベロッパーモードで読み込み)
  • 拡張の機能: 対象ページの情報を収集し、前回スナップショットと差分を比較表示
  • 使用API: chrome.storage.local(sync でも同様の話)

なぜ「いったん削除して読み込み直す」とデータが消えるのか

結論から言うと、Chrome拡張のストレージは「拡張ID」に紐づいて保存されるためです。

Unpacked拡張のIDは、読み込んだフォルダの絶対パスから導出されます。パスが同じなら同じID、パスが違えば別IDです。

つまり、

  1. ユーザーが旧バージョンのフォルダを削除する
  2. 新しいバージョンを別のフォルダ(例: my-ext-v2)に置いて読み込む
  3. パスが変わったので拡張IDが変わる
  4. Chromeは「別の拡張」として扱うため、chrome.storage.local の前回データは見えなくなる

という流れです。データ自体はプロファイル内に残っていますが、新しいIDからは参照できません。ユーザーから見ると「消えた」ようにしか見えません。

回避策として manifest.json に key を書いてIDを固定する方法もありますが、鍵の管理が必要になるため、社内配布のようなカジュアルな配布には向きません。Web Store配布ならIDは不変なので、この問題自体が起きません。

解決策:上書きコピー+更新ボタン

発想を変えます。フォルダを差し替えるのではなく、中身を上書きするのです。

手順(macOS / Linux)

# ビルド成果物を、ユーザーが読み込んでいるフォルダへ同期する
rsync -a --delete ~/dev/my-ext/dist/ ~/extensions/my-ext/

手順(Windows)

robocopy C:\dev\my-ext\dist C:\extensions\my-ext /MIR

コピー後、ユーザーは chrome://extensions を開き、該当拡張のカードにある更新ボタン(↻)を押すだけです。パスが変わらないため拡張IDは同じまま、chrome.storage.local のデータも保持されます。

注意点

  • --delete / /MIR は「コピー先にしかないファイル」を削除します。ストレージは拡張フォルダ内ではなくChromeのプロファイル側にあるので、この削除でデータは消えません。
  • ただし、フォルダ内のJSONファイルを fetch() してデータ代わりにしている実装は消えます。保存は必ず chrome.storage を使ってください。
  • 読み込んでいるフォルダのパスをユーザーに変えさせないこと。これが守れないと元の木阿弥です。

手順ごとの比較

更新手順 拡張ID 保存データ 向いている場面
削除 → 別フォルダから読み込み 変わる 参照不能 初回セットアップのみ
削除 → 同じパスに再配置 同じ 残る 手数が多く事故りやすい
上書きコピー → ↻ 同じ 残る 日常の更新(推奨)
Chrome Web Store 経由 同じ 残る 一般配布

同じパスなら削除→再配置でもIDは維持されますが、ユーザーがつい別フォルダに置いてしまうのが現実です。「削除」という言葉自体を使わない運用にしておくのが安全です。

実装側の備え:スキーマ変更に耐えるマイグレーション

更新手順を整えても、保存データの形式を変えると結局壊れます。schemaVersion を持たせて、起動時にマイグレーションを走らせましょう。

const SCHEMA_VERSION = 3;

async function migrate() {
  const { schemaVersion = 1, lastSnapshot } =
    await chrome.storage.local.get(['schemaVersion', 'lastSnapshot']);

  if (schemaVersion >= SCHEMA_VERSION) return;

  if (schemaVersion < 2 && Array.isArray(lastSnapshot)) {
    // 旧形式: { url, hash }
    // 新形式: { url, hash, capturedAt }
    const migrated = lastSnapshot.map((row) => ({ ...row, capturedAt: 0 }));
    await chrome.storage.local.set({ lastSnapshot: migrated });
  }

  if (schemaVersion < 3) {
    // 例: スナップショットの保持件数を上限200件に切り詰める
    if (Array.isArray(lastSnapshot)) {
      await chrome.storage.local.set({
        lastSnapshot: lastSnapshot.slice(-200),
      });
    }
  }

  await chrome.storage.local.set({ schemaVersion: SCHEMA_VERSION });
}

chrome.runtime.onStartup.addListener(migrate);
chrome.runtime.onInstalled.addListener(migrate);

ポイントは、onInstalled だけでなく Service Worker起動時にもチェックすることです。Unpacked拡張を↻で更新した場合、onInstalled が期待どおりに発火しないケースがあり、そのまま古いスキーマで動き続けてしまいます。起動時チェックを入れておけば、どのタイミングで更新されても一度は必ず通ります。

manifest.json 側も忘れずにバージョンを上げておきましょう。

{
  "manifest_version": 3,
  "name": "Diff Snapshot",
  "version": "1.4.0",
  "permissions": ["storage", "activeTab"],
  "background": { "service_worker": "background.js" }
}

初回納品時に伝えておくこと

データを持つ拡張は、機能と同じ重さで更新手順を設計してください。初回の納品時に以下をチェックリストとして渡しておくと、後の問い合わせが激減します。

  • 読み込むフォルダのパスは固定。以降は移動しない
  • 更新時は「削除」ではなく「上書きコピー」
  • コピー後に chrome://extensions で↻を押す
  • 拡張IDが前回と同じかを目視確認する(変わっていたら過去データは引き継げない)
  • データを明示的に消したいときは、拡張側に用意したリセットボタンを使う

FAQ

Q. 消えたように見えたデータを復元できますか?

A. プロファイル内の Local Extension Settings/<旧拡張ID> に残っている可能性はありますが、Chrome内部のLevelDBなので直接触るのは推奨しません。復元を狙うより、更新手順を直して運用で防ぐほうが現実的です。

Q. chrome.storage.sync ならアカウント同期されるので消えませんか?

A. いいえ。sync も拡張IDに紐づくため、IDが変われば別物として扱われます。また同期は非同期で遅延するため、更新直後に読むと空に見えることがあります。

Q. Web Storeに出す予定はありませんが、key を設定すべきですか?

A. 社内配布で鍵管理のコストを払えるなら有効です。ただし key をリポジトリに含めると公開リスクがあるため、CIで注入するなど別の管理が必要になります。上書きコピー運用で足りるなら不要です。

Q. 更新後に「前回データが壊れている」と表示されます。

A. マイグレーション漏れの典型です。schemaVersion を導入し、Service Worker起動時に必ず走らせるようにしてください。

まとめ

  • 拡張のストレージは拡張IDに紐づく。IDはUnpackedの場合フォルダパス由来
  • 「削除して読み込み直す」手順はIDを変え、保存データを参照不能にする
  • 更新は「同じフォルダへ上書きコピー → ↻」で行う。IDが変わらないのでデータは残る
  • 実装側は schemaVersion と起動時マイグレーションで形式変更に備える
  • この手順は最初の納品時に、機能の説明と同じ粒度で伝える

拡張に限らず、状態を持つツールを配るときは「どう更新させるか」までがプロダクトです。機能だけ作って更新手順を後回しにすると、ユーザーは毎回データを失いながら使うことになります。


この記事を書いた人

BENTEN Web Works — 業務自動化・システム開発のフリーランスエンジニアです。

GAS / Python / RPA を使った業務自動化や、Web制作・システム開発のご相談を承っています。
「こんなこと自動化できる?」というご質問だけでもお気軽にどうぞ。

👉 BENTEN Web Works — 詳細・お問い合わせはこちら
🐦 X(旧Twitter) — 日々の知見を発信中

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?