ブラウザ拡張機能を作るためのReactフレームワーク『Plasmo』
Plasmoとは
Plasmoはブラウザ拡張機能を作成するためのReactフレームワークです。
テストや自動デプロイするための機能なども提供しておりオールインワンなフレームワークとなっています。
公式サイトに行くと「ブラウザ拡張機能におけるNext.js」という強気なワードが確認できます。
It's like Next.js for browser extensions!
Plasmoの特徴
Plasmoは以下のような特徴を持ったフレームワークです(公式より一部抜粋)。
- ファーストクラスのReact+Typescriptサポート
- ライブリローディング + React HMR
- .env* ファイルサポート
- BPP による自動デプロイメント
- Svelte と Vue をオプションでサポート
またゼロコンフィグで開発を始めることができ、ブラウザ拡張機能の設定ファイルであるmanifest.json
をほとんど意識することなく開発を進めることができます。
WebpackやViteなどのバンドラーの設定をする必要がなく、v2とv3の情報が混在しているmanifest.json
の複雑性に悩まされることもないので、これだけでもある程度恩恵が感じられそうです。
本記事では簡単なChrome拡張を作成しながらPlasmoの機能を紹介してみたいと思います。
今回使用するソースコードは以下のリポジトリにあります。
セットアップ
プロジェクトの作成
早速プロジェクトの作成から初めてみましょう。
以下のコマンドで作成することができます。
pnpm create plasmo
# OR
yarn create plasmo
# OR
npm create plasmo
パッケージマネージャーはどれでも良いですが公式ではpnpm
を推奨しているので本記事ではpnpm
を使用していきます。
また、Node.jsのバージョンは16.14.x以上を推奨しています。
拡張機能の名前や説明などが質問されるので適当に答えていくと、以下のようなディレクトリ構成のプロジェクトが作成されます。
├── .github
│ └── workflows
│ └── submit.yml
├── README.md
├── assets
│ └── icon.png
├── node_modules
├── .gitignore
├── .prettierrc.cjs
├── package.json
├── pnpm-lock.yaml
├── popup.tsx
└── tsconfig.json
拡張機能をデベロッパーモードで読み込む
プロジェクトが作成できたら該当のディレクトリに移動して以下のコマンドを実行してみましょう。
pnpm dev
数秒待つとホットリロード機能を備えたdevelopment serverが起動しbuild
ディレクトリとその中にchrome-mv3-dev
というディレクトリが生成されます。
このchrome-mv3-dev
が開発用にバンドルされたChromeの拡張機能になります。
本番用の拡張機能はpnpm build
を実行することで生成されます。
次にchrome-mv3-dev
をブラウザで読み込み実際に拡張機能として使用できる状態にしてみましょう。
まずChrome拡張の設定ページ(chrome://extensions/)へ移動します。
この時ページ右上にあるデベロッパーモードがOFFになっている場合はONにしておいてください。
ページ左上にある「パッケージ化されていない拡張機能を読み込む」というボタンを押下するとフォルダを選択する画面になるので先程生成したchrome-mv3-dev
を選択します。
選択し読み込みが完了するとブラウザ右上のツールバーにある拡張機能の一覧の中にDEV | (作成した時の名前)
という拡張機能があるはずです。
クリックすると以下のようなシンプルなポップアップが表示されます。
試しにこのポップアップを少し編集してみたいと思います。
ポップアップのソースコードが書いてあるファイルはpopup.tsx
になります。
以下のようになっているので適当に文字やCSSを編集し保存してみます。
保存するとホットリロードが機能し、再度拡張機能を表示することで編集した箇所が反映されていることが確認できるはずです。
import { useState } from "react"
function IndexPopup() {
const [data, setData] = useState("")
return (
<div
style={{
display: "flex",
flexDirection: "column",
padding: 16
}}>
<h2>
Welcome to your{" "}
<a href="https://www.plasmo.com" target="_blank">
Plasmo
</a>{" "}
Extension!
</h2>
<input onChange={(e) => setData(e.target.value)} value={data} />
<a href="https://docs.plasmo.com" target="_blank">
View Docs
</a>
</div>
)
}
export default IndexPopup
以上でセットアップは完了です。
以降でPlasmoの持つ機能について触れていきたいと思います。
様々なページの作成
Chrome拡張には先程のポップアップをはじめ様々なページがあります。
順を追って一つずつ見ていきたいと思います。
Popup Page
Chrome拡張といえばポップアップをイメージする方も多いのではないでしょうか。
セットアップの章でも少し触れましたがPlasmoでポップアップを追加するにはプロジェクトのルートにpopup.tsx
を作成しReactコンポーネントをdefault export
します。
もしくはpopup
というディレクトリを作成しその配下にindex.tsx
を作成する方法もあります。
import { useState } from "react"
function IndexPopup() {
const [data, setData] = useState("")
return (
...
)
}
export default IndexPopup
ポップアップでのみ使用するコンポーネントなどを入れておくこともできるので個人的にはこちらの方が好みです。
画像を扱う
ポップアップの作成を進めていると画像を使用したくなる場面もあるかと思います。
PlasmoにはNext.jsのpublic
のようなディレクトリはなく、画像を個別にインポートして使用します。
またインポートする際、パスの先頭にdata-base64
という接頭辞を付ける必要があります。
import someCoolImage from "data-base64:~assets/some-cool-image.png"
...
<img src={someCoolImage} alt="Some pretty cool image" />
もしくはChrome拡張の設定ファイルであるmanifest.json
のweb_accessible_resources
に使用する画像を宣言し、chrome.getURLというChrome拡張のAPIで取得する方法もあります。
Plasmoではpackage.json
のmanifest
に設定を記述することで生成される拡張機能のmanifest.json
をオーバーライドすることが可能です。
{
"manifest": {
"web_accessible_resources": [
{
"resources": [
"assets/pic*.png",
],
"matches": [
"https://*/*"
]
}
]
}
}
const srcList = Array.from({
length: 3
}).map((_, i) => chrome.runtime.getURL(`assets/pic${i}.png`))
function IndexPopup() {
return (
<div>
{srcList.map((src, i) => (
<img key={i} src={src} style={{ width: 44, height: 44 }} />
))}
</div>
)
}
export default IndexPopup
Options Page
Options Pageは拡張機能における設定画面です。
拡張機能を使用する上で必要な情報をOptions Pageで入力しChromeのStorage APIを利用してストレージに保存するといった操作がよく行われるのではないかと思います。
Plasmoではoptions.tsx
を作成するかoptions
ディレクトリの中にindex.tsx
を作成することでOptions Pageが追加されます。
実際に先程作成したプロジェクトにOptions Pageを追加してみましょう。
import { useState } from "react"
function OptionsIndex() {
const [data, setData] = useState("")
return (
<div>
<h1>This is the Option UI page!</h1>
<input onChange={(e) => setData(e.target.value)} value={data} />
</div>
)
}
export default OptionsIndex
Options Pageは拡張機能の詳細ページからアクセスすることができます。
「拡張機能のオプション」をクリックすると別タブでページが開き、記述した内容が確認できるはずです。
Storageを利用する
Chrome拡張では拡張機能で使用するデータや状態を保持しておくためのStorage APIが用意されています。
localStorageやsessionStorageと似たようなものですが、ブラウザのキャッシュや閲覧履歴を消去してもデータが残り続けるといった特徴があるようです。
Storage APIをそのまま利用することも可能ですが、PlasmoではよりシンプルにStorageを扱えるようuseStorage
というhooksを提供しています。
先程作成したOptions Pageでこちらのhooksを利用しデータをストレージに保存できるようにしてみたいと思います。
まずはhooksを提供しているライブラリをインストールします。またフォームを扱うためreact-hook-form
も追加しておきます。
pnpm add @plasmohq/storage react-hook-form
インストールが完了したらOptions Pageを以下のように書き換えてみましょう。
import { SubmitHandler, useForm } from "react-hook-form"
import { useStorage } from "@plasmohq/storage/hook"
function OptionsIndex() {
const [name, setName] = useStorage("name")
const {
register,
handleSubmit,
formState: { errors }
} = useForm<{ name: string }>()
const onSubmit: SubmitHandler<{ name: string }> = (data) => {
setName(data.name)
}
return (
<div>
<h1>This is the Option UI page!</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<label>
Name:
<input
{...register("name", {
required: "This is required"
})}
defaultValue={name}
style={{
border: errors.name ? "1px solid red" : "1px solid black"
}}
/>
</label>
{errors.name && <p style={{ color: "red" }}>{errors.name.message}</p>}
<button
type="submit"
style={{
marginTop: "20px",
display: "block"
}}>
Save
</button>
</form>
</div>
)
}
export default OptionsIndex
useStorage
の使い方はとてもシンプルで現在のストレージの状態とそれを更新するための関数をタプル型で受け取ります。ほとんどuseState
と同じような書き方です。
useStorage
に渡す引数がuseState
とは若干異なっており、第一引数にはストレージに保存する際のKeyを指定し、第二引数で任意の初期値を指定します。
上記のコードの例だとname: [入力した値]
のような感じでストレージに保存されます。
この状態でSaveボタンをクリックすると入力した値がストレージに保存されるはずです。
余談ですがStorage Area Explorerという拡張機能を利用すると現在のストレージの状況がわかりやすくなるのでおすすめです。
New Tab Page
New Tab Pageはブラウザで新しいタブを開いた時に最初に表示される画面のことです。
デフォルトだとGoogleの検索画面がありその下にいくつかのショートカットが表示されているかと思います。Chrome拡張でNew Tab Pageを作成することでこの画面をカスタマイズすることができます。
Daily.devなどは利用している方も多いのではないでしょうか。
これまでと同じですがNew Tab Pageを追加するにはnewtab.tsx
かnewtab/index.tsx
を追加します。
import { useState } from "react"
function IndexNewtab() {
const [data, setData] = useState("")
return (
<div>
<h1>This is the New Tab Page!</h1>
<input onChange={(e) => setData(e.target.value)} value={data} />
</div>
)
}
export default IndexNewtab
New Tab Pageが追加できたらブラウザで新しいタブを開いてみましょう。特に問題がなければnewtab/index.tsx
に記述した内容が表示されるはずです。
元に戻したい時はChromeの設定画面の起動時のページ(chrome://settings/onStartup)から「無効にする」を選択します。
Dev Tools Page
Dev Tools Pageはブラウザ組み込みの開発ツール(Dev Tools)を拡張するための機能です。
最近リリースされたSWRのChrome拡張などもdevtools APIを利用して開発されたものになるかと思います。。
Dev Tools Pageを追加するにはdevtools.tsx
かdevtools/index.tsx
を追加します。
また、これまでのページとは異なりDev Tools PageではReactコンポーネントをexportするのに加えて通常どおりdevtools APIの利用が必要になります。
ここではDev Toolsに簡単なパネルを追加してみたいと思います。
まずはパネルに表示させるHTMLを用意します。作成したdevtools
ディレクトリの中に以下のようなpanel.html
を追加しましょう。
<!DOCTYPE html>
<html>
<head>
<title>My Panel</title>
<meta charset="utf-8" />
</head>
<body>
<div id="panel">
<h2>My Panel</h2>
<p>HELLO WORLD</p>
</div>
</body>
</html>
続いてdevtools/index.tsx
(またはdevtools.tsx
)を以下のように記述します。
import myPanelHTML from "url:~/devtools/panel.html"
chrome.devtools.panels.create("My Panel", "", myPanelHTML)
function IndexDevtools() {
return <></>
}
export default IndexDevtools
ここでは追加したHTMLをimportし、devtools APIを利用してMy Panel
というパネルを追加しています。
HTMLをimportする際はurl
という接頭辞を付ける必要があるようです。
create
メソッドは第一引数にパネル名、第二引数に表示させるアイコン、第三引数に表示させるHTMLファイル、第四引数に作成したパネルの中で実行させるコールバック関数を指定することができます。
exportしたReactコンポーネントはその内容がDev Toolsに表示されることはないので空にしておいて問題ありません。ただ、現状何らかのReactコンポーネントをexportしておかないとエラーになるようでした。
ここまでできたら適当なWebページでDev Toolsを開き一番上にあるパネルの一覧(Elements, Console, Networkなどがあるところ)を確認してみましょう。
一覧の下の方にMy Panel
というパネルが存在し、クリックするとpanel.html
の内容が表示されているのが確認できるはずです。
Dev Toolsでのみ利用できるAPIを使った実装も試してみたいと思います。
Networkパネルのrequestとresponseの内容を取得しMy Panel
へ表示させてみましょう。
devtools/index.tsx
を以下のように編集します。
import myPanelHTML from "url:~/devtools/panel.html"
chrome.devtools.panels.create("My Panel", null, myPanelHTML, (panel) => {
panel.onShown.addListener((panelWindow) => {
let reqElem = ``
chrome.devtools.network.onRequestFinished.addListener((request) => {
const { url, method, headers } = request.request
const { status } = request.response
reqElem += `
<div class="request" style="margin-top: 20px;">
<div class="request__url">${url}</div>
<div class="request__method">${method}</div>
<div class="request__status">${status}</div>
<div class="request__headers">${JSON.stringify(headers)}</div>
</div>
`
panelWindow.document.getElementById("panel").innerHTML = `
<div class="requests">
${reqElem}
</div>
`
})
})
})
function IndexDevtools() {
return <></>
}
export default IndexDevtools
この例ではchrome.devtools.panels.create
のコールバック関数内でパネルが表示される度に実行されるpanel.onShown
イベントのリスナーを追加しています。
その中でchrome.devtools.network API
を利用してNetworkパネルで発生したrequestとresponseを取得し、いくつかの項目をHTMLに埋め込んでいます。
この状態で再度My Panel
を表示すると閲覧しているWebページで発生したrequestとresponseの内容が表示されているのが確認できると思います。
Content Scriptsを追加する
Content Scriptsは閲覧しているWebページのCSSやJavaScriptを拡張機能から操作するための機能です。
PlasmoでContent Scriptsを追加するにはcontent.ts
を作成するか、contents
ディレクトリを作成しその中に任意の名前のtsファイルを作成します。
試しに表示しているWebページにconsole.log
を表示させるContent Scriptsを追加してみましょう。
ここではcontents
ディレクトリの中にsample.ts
というファイルを追加しています。
export {}
console.log("This is content script")
適当なWebページででconsoleを開くとsample.ts
に記述したconsole.log
の内容が表示されています。
この状態だと表示した全てのページでContent Scriptsが実行されていますが、Content Scriptsを利用する場合、特定のページでのみ実行させたいといったケースがほとんどだと思います。
特定のページで実行させたい場合はconfig
という変数の中に設定を記述しexportする必要があります。
config
で設定できる項目についてはChromeのDocumentationを参照してください。
試しにZennのページでのみ実行されるContent Scriptsを作成してみたいと思います。
contents
ディレクトリの中にzenn.ts
を作成し、以下のように記述してみましょう。
import type { PlasmoCSConfig } from "plasmo"
export const config: PlasmoCSConfig = {
matches: ["https://zenn.dev/*"]
}
window.addEventListener("load", () => {
document.getElementById("tech-trend").style.backgroundColor = "#333333"
})
見れば分かるかと思いますが、上記はZennにアクセスしページが読み込まれた段階でTOPページのTechトレンドの背景を黒に変更するContent Scriptsになります。
実際にアクセスすると背景が黒になっているのが確認できるはずです。
頑張ればダークモードで表示するための拡張機能も作れそうですね(Zennさんダークモード対応お願いいたします🙏)
Content Scripts UIを追加する
Content Scripts UI(以降CSUIと呼称します)はPlasmoが提供しているもので、React(またはSvelte, Vue)コンポーネントをContent Scriptsと同じようにWebページへ表示させる機能です。
Shadow DOMを利用し通常のDOMとは独立したところにコンポーネントがマウントされるため、Webページに埋め込んであるスタイルが影響したり、逆にCSUIのスタイルがWebページに影響したりといった事態が防げる仕組みになっているようです。
CSUIを追加するにはcontent.tsx
かcontents
ディレクトリ配下にtsxファイルを作成します。
試しにgithubのページ上にボタンを追加するCSUIを作成してみたいと思います。
contents
ディレクトリにgithub.tsx
を作成し以下のように記述してみましょう。
import type { PlasmoCSConfig } from "plasmo"
export const config: PlasmoCSConfig = {
matches: ["https://github.com/*"]
}
const CustomButton = () => {
return <button>Custom button</button>
}
export default CustomButton
無事Reactで作成したボタンをgithubに表示させることができました。
Background Service Workerを扱う
Background Service Worker(以降BSWと呼称します)は拡張機能のバックグラウンドにあるJavaScriptの実行環境です。名前の通りService Workerを利用しているので常にバックグラウンドで起動しているわけではなく特定のタイミングで読み込み実行され、それ以外はアイドル状態となりスクリプトはアンロードされます。
Service Workerについては以下の記事がわかりやすかったのでこちらに説明を委ねます。
BSWが実行されるタイミングは以下の通りです。
- 拡張機能が起動した時
- 拡張機能が初めてインストールされた時、もしくは新しいバージョンに更新された時
- 登録したイベントリスナーが発火した時(参考)
- Content Scriptsや他のページからメッセージが送信された時
PlasmoでBSWを実行するにはbackground.ts
を追加します。
実際にBSWが起動していることを確認するためにconsole.log
を実行してみましょう。
background.ts
に以下のコードを記述します。
export {}
console.log("This is background service worker")
バックグラウンドで実行されたconsole.log
を確認するには以下のように拡張機能の設定ページにある「service worker」の部分をクリックします。するとDev Toolsが開くのでConsoleのパネルへ移動するとbackground.ts
のconsole.log
が実行されていることが確認できるはずです。
Messaging APIを利用する
「BSWが実行されるタイミング」のところでも少し触れましたがContent ScriptsやポップアップなどのページからBSWにメッセージを送信し、それをトリガーにバックグラウンドでJavaScriptを実行することができます。
通常、chrome.runtime.sendMessage APIを利用してメッセージを送信しますが、Plasmoではより宣言的かつ型安全にメッセージの送受信が行えるようにMessaging APIを提供しています。
ここではMessaging APIを利用してポップアップからBSWにメッセージを送信し、受信したタイミングで外部のAPIからデータを取得してポップアップへ返却する処理を試してみたいと思います。
BSWからAPIへのリクエストを行うことでCORSを回避できるといったメリットもあります。
まずは以下のライブラリをインストールします。
pnpm add @plasmohq/messaging
続いてポップアップにBSWへメッセージを送信する処理を先に書いておきましょう。
ここでは先程インストールした@plasmohq/messaging
のsendToBackground
を利用してボタンがクリックされたらposts
という名前のメッセージを送信し、BSWから返却された結果をstateに保存しています。
import { useState } from "react"
import { sendToBackground } from "@plasmohq/messaging"
function IndexPopup() {
const [posts, setPosts] = useState([])
return (
<div
style={{
width: "300px",
height: "300px"
}}>
<button
onClick={async () => {
const res = await sendToBackground({
name: "posts"
})
setPosts(res.posts)
}}>
Get posts
</button>
{posts.length > 0 && (
<div>
{posts.map((post) => (
<div
key={post.id}
style={{
marginTop: "20px"
}}>
<div>{post.title}</div>
<div>{post.body}</div>
</div>
))}
</div>
)}
</div>
)
}
export default IndexPopup
次にバックグラウンドで外部のAPIからデータを取得しポップアップへ返却する処理を書いていきます。
先程はルートにbackground.ts
を作成しましたが、Messaging APIを利用するにはbackground/messages
ディレクトリを作成しその配下にtsファイルを配置する必要があるようです。また、tsファイルのファイル名はメッセージを送信するときにname
に指定した名前にする必要があります(今回の例だとposts
)
Plasmoの規約に従いbackground/messages/posts.ts
を作成します。
作成したtsファイルに以下のコードを記述してみましょう。
import type { PlasmoMessaging } from "@plasmohq/messaging"
const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
const posts = await fetch(`https://jsonplaceholder.typicode.com/${req.name}`)
.then((res) => res.json())
.then((json) => json.slice(0, 10))
res.send({
posts
})
}
export default handler
ここではリクエストに指定された名前を元にjsonplaceholderから記事のデータを取得し、先頭10件を抽出してレスポンスとして返しています。
最後にこのhandler
関数をエクスポートすることでBSWからこのモジュールを呼び出し、ポップアップにデータを返却することができるようになりました。
期待通りに動作するか確認してみましょう。
ポップアップを開きGet posts
ボタンを押下するとjsonplaceholderから取得したデータが表示されることが確認できるはずです。
これでポップアップとBSWとでメッセージを送受信する実装ができました。
Chrome Web Storeへ自動デプロイする
PlasmoではBrowser Platform PublisherというGitHub Actionを提供しています。
これによって簡単に拡張機能を(chrome拡張の場合は)Chrome Web Storeへ自動デプロイすることができます。
以降で自動デプロイに必要な作業の手順をまとめていきたいと思います。
Google APIにアクセスするためのアクセストークンの取得
Browser Platform Publisherは内部でChromeのWeb Store Publish APIを利用しています。
Web Store Publish APIを利用するためにはGoogle Cloudでプロジェクトを作成し、APIの有効化とアクセストークンの取得を行う必要があります。
まず初めにGoogle Cloudのプロジェクトを作成しましょう。
Google Cloud Consoleへ移動し「新しいプロジェクト」からプロジェクトを作成します。
この時のプロジェクト名は何でも良いです。
プロジェクトの作成が完了したらそのプロジェクトを選択後、OAuth 同意画面へ移動します。
移動した先にUser Typeという選択項目があるのでExternalを選択し作成ボタンをクリックします。
作成ボタンを押すとアプリ情報などの入力画面に遷移します。
ここでは必須項目となっている、
- アプリ名
- ユーザーサポートメール
- デベロッパーの連絡先情報
の3つのみ入力すればokです。
この後スコープという画面へ移りますがここでは特に何もせず「保存して次へ」をクリックします。
次にテストユーザーという画面が表示されるので「ADD USERS」をクリックし、出てきた入力欄に自身のメールアドレスを追加しましょう。
続いてChrome Web Store APIへ移動しAPIを有効化します。有効化できたら認証情報の画面へ遷移しましょう。
認証情報画面でCreate credentialsをクリックしその中にあるOAuth client IDを選択します。
選択するとApplication typeとNameの入力欄が現れるのでApplication typeでは「Desktop app」を選択しNameには適当な名前を入力します。
作成ボタンをクリックすると数秒後にOAuthのクライアントIDとクライアントシークレットが発行されるのでJSONでダウンロードしておいてください。
ダウンロードできたら再度OAuth 同意画面へ移動します。
公開ステータスがテストになっているので「アプリを公開」をクリックして公開しておきましょう。
これでGoogle Cloudでの作業は完了になります。以下のドキュメントにも同じ内容が記載されているのでもし分かりにくいところがあればこちらも参照してみてください。
リフレッシュトークンの取得
Chrome Web Store APIを利用するために必要な情報のうちrefreshToken
がまだ足りていないのでこちらを取得していきます。
Plasmoで作成したプロジェクトに戻り、先程ダウンロードしたJSONファイルをkey.json
という名前でプロジェクトのルートへ配置します。
key.json
の中身は以下のようになっているはずです。
{
"installed": {
"client_id": "xxxxxxx.apps.googleusercontent.com",
"project_id": "Google Cloudで作成したプロジェクトのID",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "xxxxx-xxxxxxx-xxxxxxxxxxx",
"redirect_uris": ["http://localhost"]
}
}
配置できたら以下のコマンドを実行しましょう。
別のパッケージマネージャーを利用している時はnpx
かyarn dlx
に置き換えてください。
pnpm dlx gcp-refresh-token
コマンドを実行すると、ブラウザでOAuthの同意画面が開きます。
警告も表示されますが手順に従い続行していくと最後に以下のメッセージが表示されます。
Code retrieved 🚀 on port: 1701. Please close this tab and return to the console.
メッセージに従いタブを閉じたらエディターに戻りkey.json
を確認してみましょう。
installed
の下に以下の情報が追加されているのが確認できると思います。
"chrome": {
"clientId": "xxxxxxx.apps.googleusercontent.com",
"clientSecret": "xxxxx-xxxxxxx-xxxxxxxxxxx",
"refreshToken": "xxxxxxxxxxxxxxxxxxxxxxxxxx"
}
これでChrome Web Store APIの利用に必要なトークンを揃えることができました。
トークンをGithub Secretsに登録する
Browser Platform Publisherを実行するにあたり、これまで取得したトークンをJSON形式でGithub Secretsに登録しておく必要があります。
Chrome拡張を公開する際のJSONは以下の通りです。
{
"$schema": "https://raw.githubusercontent.com/plasmo-corp/bpp/v2/keys.schema.json",
"chrome": {
"clientId": "xxxxxxx.apps.googleusercontent.com",
"refreshToken": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
"extId": "xxxxxxxxxxxxxxxxxxxxxxxxx",
"clientSecret": "xxxxx-xxxxxxx-xxxxxxxxxxx",
}
}
clientId
, refreshToken
, clientSecret
はkey.json
で生成されたものをそのまま入力してもらえば問題ありません。
extId
は拡張機能に割り当てられているIDになります。Chrome Web Storeのダッシュボードから対象の拡張機能の編集画面へ遷移するとIDが記載されているはずです。
もしくはそのページのURLでも確認できます。
https://chrome.google.com/webstore/devconsole/[ユーザーID]/[拡張機能のID]/edit
全ての項目が埋められたらこちらのJSONをSUBMIT_KEYS
という名前でGithub Secretsに登録しましょう。名前をSUBMIT_KEYS
にしているのは.github/workflows/submit.yml
で指定されているためです。
Github Secretsの登録の仕方は以下を参照してください。
Browser Platform Publisherの実行
Secretsの登録ができたらBrowser Platform Publisherを実行してChrome Web Storeへデプロイしてみましょう。
該当のリポジトリのActions
タブへ移動しSubmit to Web Store
というworkflowを選択します。
右側にRun workflow
というボタンが表示されるのでこちらをクリックしてworkflowを実行します。特にエラーが発生しなければ1分前後で実行が完了します。
Browser Platform Publishでエラーが発生する場合
私の環境ではworkflowの中のBrowser Platform Publishを実行中に以下のエラーが発生しました。
ERROR | Error: chrome: Extension bundle file doesn't exist: /home/runner/work/hoge/hoge/build/chrome-mv3-prod.zip
エラーの内容は書いてある通り、Web Storeの審査に提出するためのzipファイルが存在しないというものです。
このworkflowの中でzipファイルの生成はBuild and zip extension artifact
の部分で行われています。
- name: Build and zip extension artifact
run: pnpm package
原因を調査したところpnpm package
のみではzipファイルが生成されず直前にpnpm build
を実行することでzipファイルが生成されるようになりました。
- name: Build and zip extension artifact
- run: pnpm package
+ run: |
+ pnpm build
+ pnpm package
原因はシンプルですが、こんなバグが放置されているとも考えにくかったので一旦このような形で記録を残しています。
Plasmoにissueを立てたので進展があればこちらに追記したいと思います。
2/16 追記
私が使用しているPlasmoのバージョンが少し古かったことが原因でした。
最新のバージョンでは解消されているようです。
無事拡張機能をWeb Storeにデプロイすることができました🎉
Chrome Web Storeのダッシュボードでも拡張機能のステータスが「審査待ち」になっていることが確認できるはずです。
まとめ
長くなってしまいましたがここまで読んでいただきありがとうございました。
ブラウザ拡張機能開発というニッチな分野であるが故にあまり日の目を見ることのないPlasmoですが、扱いやすいAPIや自動デプロイの仕組みがあり、manifest.json
をほとんど意識せず開発できるDXの良さも備えているなど、ブラウザ拡張機能におけるNext.jsを謳うのも納得の完成度のように感じました。
今後もブラウザの拡張機能を作る機会があればPlasmoは候補に上がりそうです。
本記事はPlasmoの機能の全てを紹介できてはいないので興味のある方は是非公式のドキュメントも読んでみてください。
また、間違っている情報やtypoなどあればコメントやプルリクなどいただけるととても助かります。
Discussion