Skip to content

Instantly share code, notes, and snippets.

@x7ddf74479jn5
Last active April 10, 2023 06:37
Show Gist options
  • Save x7ddf74479jn5/e6ce8898a2c503f1ad1cb54d1b1034f2 to your computer and use it in GitHub Desktop.
Save x7ddf74479jn5/e6ce8898a2c503f1ad1cb54d1b1034f2 to your computer and use it in GitHub Desktop.
Plasmoメモ

Plasmoメモ

概要

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 Page

popup.tsxpopup/index.tsxの命名規則。拡張のアイコンをクリックしたとき表示されるポップアップをReactで書いていく。

Example: with-popup

Background Page

background.tsbackground/index.tsの命名規則。Service Worker側のスクリプトを書く場所。PlasmoがラップしたMessaging APIを経由してPopup PageやContent Scripts (Content Scripts UI) と通信する。Service Worker内の検証を有効化するために、拡張機能をリロードしてポップアップ内のService Worker inspectorを開く。

Background Service Worker – Plasmo

Content Scripts (Content Scripts UI)

Life Cycleの理解が一番難しい。

Example: with-content-scripts-ui

CSS

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...

Assets

画像のインポート方法は2種類ある。

  • スタティックにインポートする方法
  • manifestに指定する方法

スタティックにインポートする方法

assetsフォルダの画像パスをそのまま指定してやればいい。base64形式でinlineに埋め込まれる。

import someCoolImage from "data-base64:~assets/some-cool-image.png"
 
...
 
<img src={someCoolImage} alt="Some pretty cool image" />

manifestに指定する方法

package.json内manifestをオーバーライドして公開するリソースを列挙する方法。

  "manifest": {
    "web_accessible_resources": [
      {
        "resources": [
          "~raw.js",
          "assets/pic*.png",
          "resources/test.json"
        ],
        "matches": [
          "https://www.plasmo.com/*"
        ]
      }
    ],
    "host_permissions": [
      "https://*/*"
    ]
  }

Storage

Module Description
@plasmohq/storage background用
@plasmohq/storage/secure API Key用 (Web Crypto APIでセキュアに格納する)
@plasmohq/storage/hook コンポーネント用
pnpm add @plasmohq/storage

Example: with-storage

Storage API

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" }

React Hook API

import { useStorage } from "@plasmohq/storage/hook"

function IndexPopup() {
 const [hailingFrequency, setHailingFrequency] = useStorage("hailing", "42")
// ...
<input value={hailingFrequency} onChange={(e) =>
  setHailingFrequency(e.target.value)
  }/> // "42"
// ...
}

Storage API – Plasmo

Messaging

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の引数オブジェクト内でnamepingを指定する。

// 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

Plasmoでは実現不可能な機能

Context Menu

右クリック時のメニュー。

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
      }
    }
  }
})

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment