こんにちは!カスタマーエンジニアの圓山です。
microCMSはコンテンツAPIや拡張フィールドなどを使って、外部サービスと連携することでより便利に使っていただけます。
拡張フィールドを使えば、microCMSの入稿画面を外部サービスと連携するなどして拡張できますが、時にはmicroCMSの入稿を「microCMSではない別の場所」でやりたくなるケースもあるかもしれません。
今回は、「microCMSではない別の場所」からmicroCMSへデータを登録する手段として、Chrome拡張機能を使うサンプルを実装していきます。
はじめに
以下の機能を持つChrome拡張機能と、登録したデータを表示するフロント部分を開発します。
Chrome拡張機能
- 開いたウェブサイトのタイトルとURLを登録できる
- microCMS上で管理しているカテゴリーを選択できる
- ウェブサイトに関するコメントも登録できる
フロント部分
- microCMS上に登録したデータを表示できる
以下のようなイメージで動作します。
Chrome拡張機能の開発をスムーズに進めるにあたり、ホットリロードなどに対応しているWXTを使用します。
https://wxt.dev/
なお、実装したサンプルはGitHubに公開しておりますので、実際のコードで全体像を確認してみたい方はご参照ください。
https://github.com/ibulog/microcms-clip-chrome-extension
Chrome拡張機能の開発にあたって
実装に入る前に、Chrome拡張機能の開発で登場する用語について簡単にご説明します。
Chrome拡張機能には、以下の3つのコンテキストがあります。Chrome拡張機能の開発では、これらのコンテキストを用途に応じて使い分けながら実装を進めていきます。
Content Script
表示しているウェブページ上で実行されるスクリプトです。ページのDOM操作や情報取得ができます。
ボタンの色を変えたり、タイトルやURLといったページの情報を取得したりといった用途があります。
Popup
ユーザーが拡張機能を操作するUIにあたります。ReactなどのUIコンポーネントライブラリで開発ができます。
Service Worker
ブラウザーのイベントを監視したり、他のコンテキストと連携したりする際に使用します。
タブが作成されたら任意の処理を実行したり、Content ScriptとPopupの橋渡しをしたりといった用途があります。
それでは、実装を進めていきましょう!
APIスキーマの定義
まずはmicroCMS上でAPIスキーマを定義します。今回作成するAPIは2つです。
カテゴリー用API
カテゴリーを管理するAPIです。
クリップ用API
拡張機能でクリップしたウェブサイトのタイトルやURLなどを管理するAPIです。カテゴリー用APIをコンテンツ参照します。
カテゴリー用APIを作成
APIの名称とエンドポイントを設定します。エンドポイントは categories
とし、リスト形式を選択します。
APIスキーマは、以下のように定義します。
- フィールドID:
name
- 表示名:カテゴリー
- 種類:テキストフィールド
APIスキーマを定義できたら、使用したいカテゴリーをコンテンツとして追加しておきます。
クリップ用API
APIの名称とエンドポイントを設定します。エンドポイントは clips
とし、リスト形式を選択します。
APIスキーマは、以下のように定義します。
タイトル用フィールド
- フィールドID:
title
- 表示名:タイトル
- 種類:テキストフィールド
URL用フィールド
- フィールドID:
url
- 表示名:URL
- 種類:テキストフィールド
カテゴリー用フィールド
- フィールドID:
category
- 表示名:カテゴリー
- 種類:コンテンツ参照(カテゴリー用APIを参照)
コメント用フィールド
- フィールドID:
comment
- 表示名:コメント
- 種類:テキストエリア
以上で、APIスキーマの定義は完了です。
Chrome拡張機能の実装
WXTによる開発環境の構築
下記コマンドを実行して、WXTで開発環境を構築します。コマンドを実行すると利用するフレームワークとパッケージマネージャーの選択を促されますが、今回はReactとnpmを選択しました。npx wxt@latest init microcms-clip-chrome-extension
環境の構築が完了すると、カレントディレクトリ下に新しくディレクトリが作成され、下記のようにファイルが配置されます。
.
├── README.md
├── assets
├── entrypoints
│ ├── background.ts
│ ├── content.ts
│ └── popup
│ ├── App.css
│ ├── App.tsx
│ ├── index.html
│ ├── main.tsx
│ └── style.css
├── package.json
├── public
├── tsconfig.json
└── wxt.config.ts
開発時に主に編集するのは、entrypoints
内のファイルです。
タイトルとURLを取得し、ポップアップに表示する実装
Content Scriptにあたるentrypoints/content.ts
で、ページの情報を取得する処理を実装します。
WXTがひな形を作ってくれているので、主にmain関数部分の処理を変更します。
export default defineContentScript({
matches: ["<all_urls>"],
main() {
type PageInfo = {
title: string;
url: string;
};
const pageInfo: PageInfo = {
title: document.title,
url: location.href,
};
const message = {
action: "savePageInfo",
pageInfo: pageInfo,
};
try {
chrome.runtime.sendMessage(message, (response) => {
console.log("Response from background:", response);
});
} catch (error) {
console.error("Failed to send message:", error);
}
},
});
matches
ですべてのURLを拡張機能の動作対象としています。
Content Scriptは閲覧中のページ上で動作するので、document
やlocation
といったAPIを使って、タイトルやURLを取得できます。
取得したデータは chrome.runtime.sendMessage()
を使って、Service Workerに渡しています。
続いて、Service Workerからデータを取得し、拡張機能のUIに表示するPopup部分を実装します。entrypoints/popup/App.tsx
のApp()
関数の中に、以下のように処理を記述します。
async function getPageInfo() {
try {
const response = await new Promise((resolve, reject) => {
chrome.runtime.sendMessage({ action: "getPageInfo" }, (response) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve(response);
}
});
});
if (response) {
setPageInfo({
title: response.title || "No title",
url: response.url || "No URL",
});
} else {
console.error("Failed to fetch page info");
}
} catch (error) {
console.error("Error fetching page info:", error);
}
}
ここでも chrome.runtime.sendMessage()
を使って、Service WorkerからのレスポンスとしてタイトルとURLを受け取ります。
取得した値は、stateで管理してUI上に表示します。
最後に、entrypoints/background.ts
を編集し、Service WorkerでContent ScriptからのデータをPopupに橋渡しします。
export default defineBackground(() => {
let pageInfo: { title: string; url: string } | null = null;
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log("Message received:", request, sender);
// コンテンツスクリプトからのメッセージ処理
if (request.action === "savePageInfo") {
pageInfo = request.pageInfo;
console.log("Page info saved:", pageInfo);
sendResponse({
status: "success",
message: "Page info received",
});
return true;
}
// ポップアップからのメッセージ処理
if (request.action === "getPageInfo") {
sendResponse(pageInfo);
return true;
}
sendResponse({ status: "error", message: "Invalid request" });
return true;
});
});
chrome.runtime.onMessage.addListener()
で、chrome.runtime.sendMessage()
によるイベントを監視しています。
この時、各イベントを判別するために、Content ScriptやPopupから渡されたaction
プロパティを使用しています。
microCMSからカテゴリー一覧を取得し、ポップアップに表示する実装
今回は、microCMS上のデータはPopupから直接取得します。entrypoints/popup/App.tsx
の実装を抜粋します。
async function fetchCategories() {
try {
const response = await client.getList({
endpoint: "categories",
});
const categories = response.contents.map((category: CategoryResponse) => {
return {
id: category.id,
name: category.name,
};
});
setCategories(categories);
} catch (error) {
console.error("Failed to fetch categories:", error);
}
}
microCMSでデータを取得しウェブサイトに表示する際の一般的な手法と同様で、APIを実行してデータを取得し、取得したデータをstateに格納しています。
拡張機能が複雑になってきたら、ロジックはすべてService Workerに集め、Service Worker経由でデータを取得するようにするのがよさそうです。
microCMSにデータを保存する実装
microCMSにデータを保存する処理も、Popupに直接実装します。
事前に定義したAPIスキーマに従って、データを保存する処理を実装します。
async function saveToMicroCMS() {
try {
const response = await client.create({
endpoint: "clips",
content: {
title: pageInfo.title,
url: pageInfo.url,
comment: comment,
category: selectedCategory,
},
});
console.log("Data saved to microCMS:", response);
alert("クリップしました!");
} catch (error) {
console.error("Failed to save data to microCMS:", error);
alert("クリップに失敗しました。");
}
}
最後に、Popupの表示部分を実装します。取得したタイトルやURLを表示できるほか、カテゴリーを選択したり、コメントを入力したりできるようにします。また、microCMSへのデータ保存を実行するためのボタンも配置します。
return (
<div className="popup-container">
<p className="page-info">
<span className="page-title">Title: {pageInfo.title}</span>
<span className="page-url">URL: {pageInfo.url}</span>
</p>
<div className="form-group">
<label htmlFor="category-select">カテゴリー:</label>
<select
id="category-select"
className="category-select"
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
>
<option value="">選択してください</option>
{categories.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
</div>
<textarea
className="comment-box"
placeholder="コメントを入力してください"
value={comment}
onChange={(e) => setComment(e.target.value)}
/>
<div className="button-container">
<button className="save-button" onClick={saveToMicroCMS}>
クリップ
</button>
</div>
</div>
);
設定ページの実装
microCMSのサブドメインやAPIキーなどを設定画面から行えるようにします。
まず、サブドメインやAPIキーを保存するストレージを storage.ts
として定義します。
import { storage } from "@wxt-dev/storage";
type Secrets = {
serviceDomain: string;
apiKey: string;
};
export const secrets = storage.defineItem<Secrets>("local:secrets", {
fallback: {
serviceDomain: "",
apiKey: "",
},
});
次に、entrypoints/popup
と同様のファイル構成で entrypoints/options
ディレクトリを作り、App.tsx
を編集して設定画面を作ります。
import { useState } from "react";
import { secrets } from "@/utils/storage";
import "./App.css";
function App() {
const [serviceDomain, setServiceDomain] = useState("");
const [apiKey, setApiKey] = useState("");
// secretsを保存する関数
async function handleSave() {
await secrets.setValue({
serviceDomain,
apiKey,
});
alert("設定が保存されました。");
}
...
先ほど定義したストレージをimportして利用しています。表示部分のコードは割愛していますが、handleSave()
をボタン押下で実行されるようにすればOKです。
以上でChromeからmicroCMSへ閲覧中のページ情報を保存できるようになりました🎉
microCMSからデータを取得し表示するフロント部分の実装
microCMSからデータを取得し表示するフロント部分は、一般的なブログの実装と同じイメージで進められるため、詳細は割愛します。
コードが気になる方は、サンプルコードをご確認ください。
https://github.com/ibulog/microcms-clip-chrome-extension/tree/main/web-app
細かい処理や設定については説明を省略していますが、以上で全体の実装は完了です。
開発した拡張機能をzipにして、拡張機能をインストールしましょう!npm run zip
おわりに
この記事では、microCMSにデータを登録するためのいち手段として、Chrome拡張機能をご紹介しました。Chrome拡張機能開発の流れはイメージできましたでしょうか?
サンプルではコメントを入力できるようにしましたが、生成AIを使って記事内容の要約を登録するようにしても面白そうですね。
APIを使って、いろいろな手段でmicroCMSにデータを登録してみてください!