- 概要
- 環境
- 初期設定
- Popup Page
- Background Page
- Content Scripts (Content Scripts UI)
- CSS
- Assets
- Storage
- Messaging
- 環境変数
- Plasmoでは実現不可能な機能
- References
Chrome Extension開発用フレームワーク PlasmoHQ/plasmo: 🧩 The Browser Extension Frameworkの実装メモ。
ManifestはV3。
"plasmo": "0.67.4",
"react": "18.2.0",
"react-dom": "18.2.0",
pnpm create plasmo
HMR導入済みの開発サーバーが起動する。
pnpm dev
Chromeの方の拡張管理ページで開発者モードを有効化する。build
ディレクトリに吐かれるのでディレクトリを指定して読み込む。
"manifest": {
"host_permissions": [
"https://*/*"
],
"permissions": [
"storage",
"tabs",
"contextMenus"
]
}
.
├── .github
│ └── workflows
│ └── submit.yml
├── .gitignore
├── .prettierrc.cjs
├── README.md
├── assets
│ └── icon.png
├── background
│ ├── index.ts
│ └── messages
│ └── .ts
├── contents
│ ├── Icon.tsx
│ └── index.tsx
├── package.json
├── pnpm-lock.yaml
├── popup
│ └── index.tsx
├── postcss.config.js
├── style.css
├── tailwind.config.js
└── tsconfig.json
8 directories, 18 files
popup.tsx
かpopup/index.tsx
の命名規則。拡張のアイコンをクリックしたとき表示されるポップアップをReactで書いていく。
Example: with-popup
background.ts
かbackground/index.ts
の命名規則。Service Worker側のスクリプトを書く場所。PlasmoがラップしたMessaging APIを経由してPopup PageやContent Scripts (Content Scripts UI) と通信する。Service Worker内の検証を有効化するために、拡張機能をリロードしてポップアップ内のService Worker inspectorを開く。
Background Service Worker – Plasmo
Life Cycleの理解が一番難しい。
Example: with-content-scripts-ui
css周りが分かりづらい…。Tailwind CSSなのでどこでも使える。Content Scriptでの読み込み方法が独特で辛い。
In Extension Page
import { useReducer } from "react"
import "./style.css"
function IndexPopup() {
const [count, increase] = useReducer((c) => c + 1, 0)
return (
<button
onClick={() => increase()}
type="button"
className="inline-flex items-center px-5 py-2.5 text-sm font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
Count:
<span className="inline-flex items-center justify-center w-8 h-4 ml-2 text-xs font-semibold text-blue-800 bg-blue-200 rounded-full">
{count}
</span>
</button>
)
}
export default IndexPopup
In Content Script
import cssText from "data-text:~style.css"
import { useReducer } from "react"
export const getStyle = () => {
const style = document.createElement("style")
style.textContent = cssText
return style
}
import type { PlasmoGetStyle } from "plasmo"
export const getStyle: PlasmoGetStyle = () => {
const style = document.createElement("style")
style.textContent = `
p {
background-color: yellow;
}
`
return style
}
CSS-in-JSの場合、この記述をすべてのコンポーネントで書く必要があり、めんどい。
import IconUrl from "data-base64:~assets/icon.png"
import cssText from "data-text:~style.css"
import { sendToBackground, sendToContentScript } from "@plasmohq/messaging"
const styleElement = document.createElement("style")
const styleCache = createEmotionCache({
key: "plasmo-emotion-cache",
prepend: true,
container: styleElement
})
styleCache.sheet.insert(cssText)
export const getStyle = () => styleElement
// Component...
画像のインポート方法は2種類ある。
- スタティックにインポートする方法
- manifestに指定する方法
assetsフォルダの画像パスをそのまま指定してやればいい。base64形式でinlineに埋め込まれる。
import someCoolImage from "data-base64:~assets/some-cool-image.png"
...
<img src={someCoolImage} alt="Some pretty cool image" />
package.json
内manifestをオーバーライドして公開するリソースを列挙する方法。
"manifest": {
"web_accessible_resources": [
{
"resources": [
"~raw.js",
"assets/pic*.png",
"resources/test.json"
],
"matches": [
"https://www.plasmo.com/*"
]
}
],
"host_permissions": [
"https://*/*"
]
}
Module | Description |
---|---|
@plasmohq/storage | background用 |
@plasmohq/storage/secure | API Key用 (Web Crypto APIでセキュアに格納する) |
@plasmohq/storage/hook | コンポーネント用 |
pnpm add @plasmohq/storage
Example: with-storage
import { Storage } from "@plasmohq/storage"
const storage = new Storage()
await storage.set("key", "value")
const data = await storage.get("key") // "value"
await storage.set("capt", { color: "red" })
const data2 = await storage.get("capt") // { color: "red" }
import { useStorage } from "@plasmohq/storage/hook"
function IndexPopup() {
const [hailingFrequency, setHailingFrequency] = useStorage("hailing", "42")
// ...
<input value={hailingFrequency} onChange={(e) =>
setHailingFrequency(e.target.value)
}/> // "42"
// ...
}
pnpm add @plasmohq/messaging
- Messaging Flow
- Relay Flow
- Ports
の3種類あるがMessaging Flow以外はpublic alphaなので安定していない。
Example: with-messaging
background/messages/<任意のAPI名>.ts
の形式でエンドポイントを配置する。
// background/messages/ping.ts
import type { PlasmoMessaging } from "@plasmohq/messaging"
type PingRequestBody = {
id: number
}
type PingRequestResponse = { message: string }
const handler: PlasmoMessaging.MessageHandler<PingRequestBody,PingRequestResponse> = async (req, res) => {
const message = await querySomeApi(req.body.id)
res.send({
message
})
}
例えばPopup Pageからpingエンドポイントにメッセージを送る場合は、sendToBackground
の引数オブジェクト内でname
にpingを指定する。
// popup.tsx
import { sendToBackground } from "@plasmohq/messaging"
...
const resp = await sendToBackground({
name: "ping",
body: {
id: 123
}
})
console.log(resp)
Content Scripts UIでの利用
import { useMessage } from "@plasmohq/messaging/hook"
import type { PingRequest } from "~background"
// export type PingRequest = {
// name: "ping"
// tabId: number
// body: {
// id: number
// }
// }
const Container = () => {
const { data } = useMessage<PingRequest, undefined>(async (_req, _res) => { })
const { message } = data.body
// Component...
}```
[Messaging API – Plasmo](https://docs.plasmo.com/framework/messaging)
## manifestの拡張
基本的には、Plasmoがmanifestを意識しないでよしなに設定してくれる。PlasmoにないChrome拡張APIの利用に関して、いくつかの`permissions`は自分で手動設定する必要がある。`chrome.contextMenus`など。
```json
"manifest": {
"host_permissions": [
"https://*/*"
],
"permissions": [
"contextMenus"
]
}
package.json
のプロパティを読み取ってmanifest.json
のプロパティに転記してくれる。
packageJson.version
->manifest.version
packageJson.displayName
->manifest.name
packageJson.description
->manifest.description
packageJson.author
->manifest.author
packageJson.homepage
->manifest.homepage_url
Next.jsとほぼ同じ。.env.local
や.env.development
などの中から適切な場所を選んで書いていく。PLASMO_PUBLICの接頭辞をつければクライアントに公開される環境変数になる。余談だが、Chrome Extensionはブラウザにインストールして使うため環境変数は基本的にパブリックなものとして扱う。サーバー用の環境変数の使い所があるのか?
PLASMO_PUBLIC_SHIP_NAME=ncc-1701
PLASMO_PUBLIC_SHIELD_FREQUENCY=42
PRIVATE_KEY=xxx # Undefined in extension
Environment Variables – Plasmo
右クリック時のメニュー。
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: "send-message",
title: "選択したテキストを送信",
contexts: ["selection"]
})
})
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
if (!!tab) {
switch (info.menuItemId) {
case "send-message": {
const selectedText = info.selectionText ?? ""
await sendToContentScript({
name: "message",
tabId: tab.id,
body: {
message: selectedText
}
})
break
}
}
}
})