Skip to content

Chrome拡張機能公式チュートリアルをPlasmoで実装する

Updated: at 07:14
plasmoロゴ

Chrome 用の拡張機能を作ってみたくなったので公式チュートリアルをやってみました。
今回はPlasmoというブラウザ拡張機能開発用のフレームワークを使って実装していきます。

目次

目次を開く

Plasmo とは

Plasmo は、拡張機能の構築・テスト・デプロイを支援するブラウザ拡張プラットフォームです。ブラウザ拡張機能に対して深い知識がなくてもすぐに構築できます。
Chrome 公式通りに実装を進めると Vanilla.js なので型が気になりますし、TypeScript を使用するにも Vite.js などでパース環境を 1 から構築する必要があります。また、ブラウザ拡張機能開発では manifest.json という拡張機能に関する情報を記述したファイルを設置しなければならないのですが、v2 と v3 が存在するので振る舞いに注意が必要です。
しかし、Plasmo なら TypeScript 環境がコマンド一発で立ち上がりますし、UI に TSX を使用できます。また manifest.json も自動生成してくれるので、バージョンによる振る舞いの違いをユーザーが気にする必要がないのも魅力です。

公式チュートリアル

では実装していきます ٩(  ᐕ)و ローカルの拡張機能を有効化する手順はドキュメントを参照してください。
また、基本的に公式チュートリアルで説明されていることには触れないので、公式チュートリアルも並行して見てもらえると理解しやすいかもしれません 🙇🏻‍♂️

1.Reading time

このチュートリアルでは、記事を読み終えるまでに必要な時間を計算し、サイト上に差し込むプログラムを作ります。
まずは Plasmo でプロジェクトを立ち上げます。src ディレクトリも作りたいのでテンプレートも指定します。

pnpm create plasmo --with-src

step1、step2 は manifest.json の作成とアイコン設置についての解説なので飛ばします。plasmo では manifest.json は package.json に記述できますが、現状デフォルトの設定で大丈夫です。また、plasmo はアイコンも自動的に設置してくれます。今回はチュートリアルなので特に変更の必要はないですが、自分で作成したアイコンを設置したい場合はドキュメントを参照してください。

step3 では特定のサイトでスクリプトを実行するために、実行させたいサイトの URL を指定しています。
plasmo では contens.ts 内で指定します。今回は作成したプロジェクトの src/contents/plasmo.tsx に記述します。また、デフォルトでは plasmo.ts と拡張子が.tsですが、UI を JSX で記述したいので.tsxに変更しています。

// src/contents/plasmo.tsx
export const config: PlasmoCSConfig = {
    matches: [
        "https://developer.chrome.com/docs/extensions/*",
        "https://developer.chrome.com/docs/webstore/*",
    ],
};

これで上記 URL に当てはまるサイト以外ではスクリプトが走らなくなりました。

step4 も続けて記述します。

// src/contents/plasmo.tsx
const article = document.querySelector("article");

const PlasmoInline = () => {
  let readingTime: number | string = "?";
  if (article) {
    const text = article.textContent;
    const wordMatchRegExp = /[^\s]+/g; // Regular expression
    const words = text.matchAll(wordMatchRegExp);
    // matchAll returns an iterator, convert to array to get word count
    const wordCount = [...words].length;
    readingTime = Math.round(wordCount / 200);
  }

  return <Badge readingTime={readingTime} />;
};
export default PlasmoInline;
// src/features/badge.ts
export const Badge = ({ readingTime }: { readingTime: string | number }) => {
    return (
        <p className="color-secondary-text type--caption">
            {`⏱️ ${readingTime} min read`}
        </p>
    );
};

特段変わったことはしていませんが、.createElement()で記述していた UI 部分を切り出してコンポーネントにしています。
また、src/popup.tsx が存在していると contents.tsx のスクリプトが起動しないことがある(私だけ?)ので、削除しましょう。 「⏱️6 min read」と表示された画像

うまく挿入されました乁( ˙ω˙ 乁)ウィー!

2.Focus mode

このチュートリアルでは、ページにスタイルを挿入するスクリプトを作成します。 1 のプロジェクトをそのまま流用します。

step1 は例の如く割愛し、step2 から見ていきます。
plasmo では manifest.json に Service Worker の登録は不要で、background.ts をルートに配置し任意のコードを記述すれば動きます。

chrome.runtime.onInstalled.addListener(() => {
  chrome.action.setBadgeText({
    text: "OFF",
  });
});

これで拡張機能アイコンに「OFF」テキストが表示されたはずです。

step3
activeTabパーミッションの追加ですが、plasmo では package.json 内で manifest.json をオーバーライドします。
解説が前後しますが。step5 で動的に Style 書き換えを行うのに必要なscriptingパーミッションも一緒に追加しておきましょう。

// package.json
...
"manifest": {
    "host_permissions": [
        "https://*/*"
    ],
    "permissions": [
        "activeTab",
        "scripting"
    ]
}

step4、step5 は同時に解説します。まずは拡張機能をクリックした際の ON/OFF テキストの切り替えとスタイルの変更処理を追加します。
focus-mode.css は公式チュートリアル通りに設置しておきましょう。

// src/background.ts
import css from "data-text:~focus-mode.css"
...
const extensions = "https://developer.chrome.com/docs/extensions"
const webstore = "https://developer.chrome.com/docs/webstore"


// When the user clicks on the extension action
chrome.action.onClicked.addListener(async (tab) => {
    if (tab.url.startsWith(extensions) || tab.url.startsWith(webstore)) {
        // We retrieve the action badge to check if the extension is 'ON' or 'OFF'
        const prevState = await chrome.action.getBadgeText({ tabId: tab.id })
        // Next state will always be the opposite
        const nextState = prevState === "ON" ? "OFF" : "ON"


        // Set the action badge to the next state
        await chrome.action.setBadgeText({
            tabId: tab.id,
            text: nextState
        })


        if (nextState === "ON") {
            // Insert the CSS file when the user turns the extension on
            await chrome.scripting.insertCSS({
                css: css,
                target: { tabId: tab.id }
            })
        } else if (nextState === "OFF") {
            // Remove the CSS file when the user turns the extension off
            await chrome.scripting.removeCSS({
                css: css,
                target: { tabId: tab.id }
            })
        }
    }
})

公式チュートリアルと違う部分は、CSS の挿入/削除部分で CSS ファイルのスタイルをテキストとして処理している点です。
最初は公式と同じようにファイルで読み込もうとしていたのですが、plasmo ではビルド後のファイルにはハッシュ値がファイル名に付くため、ファイル名指定で読み込むタイプのこのメソッドとは相性が悪いようです。ビルド後のファイル名を取得する方法がわからなかったので、テキストとしてスタイルを指定することにしました。

これで拡張機能が期待通りに動くはずです!やったね ᐠ( ᐛ )ᐟ

余談なんですが、動作テストで background.ts にてconsole.log()等は普段使っている開発者ツールのコンソールには出力されません。
拡張機能を管理ページの「ビューを検証 Service Worker」をクリックして表示されるコンソールに出力されます。 拡張機能管理ページの「ビューを検証 Service Worker」と表示してある部分

3.Tabs manager

後日やります。

終わりに

ちょっと作りたいものがあったので軽い気持ちで Chrome 拡張機能開発に入門してみましたが、Plasmo を使うことによって快適な開発体験が得られそうで大満足です。
3 つ目のチュートリアルやってないけど、悪くないよね(。´・ω・)


Previous Post
nodenvからvoltaへ移行した
Next Post
ブログ作りやした