Plasmo CSUIのライフサイクル
PlasmoのCSUI (Content Script User Interface)は、コンテンツスクリプト内でReact、Vue、またはSvelteコンポーネントのマウントとアンマウントを行うライフサイクルをオーケストレートします。UIライブラリ/フレームワークごとにマウントAPIがわずかに異なりますが、最上位のライフサイクルはほぼ同じです。
Anchor
を取得するRoot Container
を作成するか、見つけるRenderer
を使用してRoot Container
にコンポーネントをレンダリングする
用語
用語 | 説明 |
---|---|
Anchor | CSUIにコンポーネントのマウント方法と場所を指示します。 |
Anchor-getter | CSUIにアンカーの探し方を指示します。 |
Overlay | 最上位(最大のz-index)のオーバーレイ要素にコンポーネントをマウントします。 |
Inline | ターゲット要素の隣に、ウェブページのDOMにコンポーネントをマウントします。 |
Root Container | CSUIによって作成された、コンポーネントを分離するためのShadowDOM要素です。 |
Renderer | 最上位のライフサイクルランナー(すべてを実行します) |
Anchor
Plasmo CSUIアンカーは、次の型で定義されます。
export type PlasmoCSUIAnchor = {
type: "overlay" | "inline"
element: Element
}
デフォルトでは、CSUIライフサイクルはdocument.body
要素を使用してオーバーレイアンカーを作成します。
{
type: "overlay",
element: document.body
}
アンカーゲッター関数が定義されてエクスポートされている場合、CSUIライフサイクルは代わりに返された要素と関連するアンカータイプを使用します。アンカーゲッター関数は非同期にすることができるため、Plasmoがコンポーネントをマウントするタイミングを制御することもできます。たとえば、特定の要素がページに表示されるのを待ってからコンポーネントをマウントできます。
アンカーは、アンカープロップを介してCSUIに渡されます。次のようにアクセスできます。
import type { PlasmoCSUIProps } from "plasmo"
const AnchorTypePrinter: FC<PlasmoCSUIProps> = ({ anchor }) => {
return <span>{anchor.type}</span>
}
export default AnchorTypePrinter
Overlay
オーバーレイアンカーは、CSUIごとに単一のRoot Container
要素に一括でマウントされるCSUI Overlay Container
を生成します。Overlay Container
は、各アンカーの要素に対して絶対的に配置され、z-indexが最大化されます。次に、エクスポートされたCSUI Component
が各Overlay Container
にマウントされます。
単一のオーバーレイアンカーを指定するには、getOverlayAnchor
関数をエクスポートします。
import type { PlasmoGetOverlayAnchor } from "plasmo"
export const getOverlayAnchor: PlasmoGetOverlayAnchor = async () =>
document.querySelector("#pricing")
オーバーレイアンカーのリストを指定するには、getOverlayAnchorList
関数をエクスポートします。
import type { PlasmoGetOverlayAnchorList } from "plasmo"
export const getOverlayAnchorList: PlasmoGetOverlayAnchorList = async () =>
document.querySelectorAll("a")
getOverlayAnchorList
は現時点では動的なケースに対応していません。たとえば、最初のレンダリング後に新しいアンカーがウェブページに追加された場合、CSUIライフサイクルはそれを検出できません。この機能の改善のためのPRは大歓迎です!
位置の更新
デフォルトのOverlay Container
は、ウィンドウのスクロールイベントをリッスンして、アンカー要素と位置を合わせます。watchOverlayAnchor
関数をエクスポートすることで、Overlay Container
が絶対位置を更新する方法をカスタマイズできます。次の例では、8472ミリ秒ごとに位置を更新します。
import type { PlasmoWatchOverlayAnchor } from "plasmo"
export const watchOverlayAnchor: PlasmoWatchOverlayAnchor = (
updatePosition
) => {
const interval = setInterval(() => {
updatePosition()
}, 8472)
// アンマウント時に間隔をクリアする
return () => {
clearInterval(interval)
}
}
例については、with-content-scripts-ui/contents/plasmo-overlay-watch.tsx (opens in a new tab)を参照してください。
Inline
インラインアンカーは、CSUI Component
をウェブページに直接埋め込みます。各アンカーは、ターゲット要素の隣に付加されたRoot Container
を生成します。各Root Container
内で、エクスポートされたCSUI Component
をマウントするために使用されるInline Container
が作成されます。
単一のインラインアンカーを指定するには、getInlineAnchor
関数をエクスポートします。
import type { PlasmoGetInlineAnchor } from "plasmo"
export const getInlineAnchor: PlasmoGetInlineAnchor = async () =>
document.querySelector("#pricing")
挿入位置を指定した単一のインラインアンカーを指定するには:
import type { PlasmoGetInlineAnchor } from "plasmo"
export const getInlineAnchor: PlasmoGetInlineAnchor = async () => ({
element: document.querySelector("#pricing"),
insertPosition: "afterend"
})
インラインアンカーのリストを指定するには、getInlineAnchorList
関数をエクスポートします。
import type { PlasmoGetInlineAnchorList } from "plasmo"
export const getInlineAnchorList: PlasmoGetInlineAnchorList = async () =>
document.querySelectorAll("a")
挿入位置を指定したインラインアンカーのリストを指定するには:
import type { PlasmoGetInlineAnchorList } from "plasmo"
export const getInlineAnchorList: PlasmoGetInlineAnchorList = async () => {
const anchors = document.querySelectorAll("a")
return Array.from(anchors).map((element) => ({
element,
insertPosition: "afterend"
}))
}
例については、with-content-scripts-ui/contents/plasmo-inline.tsx (opens in a new tab)を参照してください。
Root Container
Root Container
は、CSUI Component
がマウントされる場所です。組み込みのRoot Container
は、plasmo-csui
カスタムタグを持つShadowDOM要素です。これにより、ウェブページのスタイルの影響を受けることなく、Root Container
とそのエクスポートされたコンポーネントをスタイル設定できます。
カスタムDOMマウント
Root Container
は、ウェブページのDOMツリーに挿入されるshadowHost
を作成します。デフォルトでは、Plasmoはインラインアンカーの要素の後にshadowHost
を挿入し、オーバーレイアンカーの場合はdocument.body
の前に挿入します。この動作をカスタマイズするには、mountShadowHost
関数をエクスポートします。
import type { PlasmoMountShadowHost } from "plasmo"
export const mountShadowHost: PlasmoMountShadowHost = ({
shadowHost,
anchor,
mountState
}) => {
anchor.element.appendChild(shadowHost)
mountState.observer.disconnect() // OPTIONAL DEMO: 必要に応じてオブザーバーを停止する
}
クローズドシャドウルート
デフォルトでは、シャドウルートは「オープン」になっており、誰でも(開発者と拡張機能のユーザー)がShadowDOMの階層を検査できます。この動作をオーバーライドするには、createShadowRoot
関数をエクスポートします。
import type { PlasmoCreateShadowRoot } from "plasmo"
export const createShadowRoot: PlasmoCreateShadowRoot = (shadowHost) =>
shadowHost.attachShadow({ mode: "closed" })
カスタムスタイル
組み込みのShadowDOMは、HTML style要素 (opens in a new tab)を返すgetStyle
関数をエクスポートすることで、拡張機能開発者がコンポーネントを安全にスタイル設定するための便利なメカニズムを提供します。
CSUIのスタイル設定に関する詳細なガイダンスについては、Plasmo CSUIのスタイル設定をお読みください。
カスタムルートコンテナ
ニーズに合わせてPlasmoのShadow DOMコンテナの実装を完全に置き換えたい場合があります。たとえば、新しいDOM要素を作成する代わりに、ウェブページ内の要素自体を利用したい場合があります。そのためには、getRootContainer
関数をエクスポートします。
import type { PlasmoGetRootContainer } from "plasmo"
export const getRootContainer = () => document.getElementById("itero")
これを行う理由のいくつかを次に示します。
- 拡張機能はホストウェブページのスタイルを吸収する (opens in a new tab)必要があります。
- 拡張機能は、Shadow DOMを使用する代わりに、ウェブページにコンポーネントを直接マウントする必要があります。
- 拡張機能は、代わりにiframe (opens in a new tab)を使用する必要があります。
getRootContainer
関数をエクスポートする場合、getStyle
やgetShadowHostId
など、組み込みのShadowDOMを拡張する関数はすべて無視されます。必要に応じて、カスタムgetRootContainer
ロジック内でこれらの関数を呼び出してください!
例については、with-content-scripts-ui (opens in a new tab)を参照してください。
Renderer
Renderer
は、各Root Container
の存在を検出し、各アンカーの要素とそのRoot Container
間のリンクを追跡するために、ウェブサイトのDOMを観察する役割を担っています。安定したRoot Container
が決定されると、Renderer
はAnchor
のタイプに応じてInline Container
またはOverlay Container
を使用して、エクスポートされたCSUI Component
をRoot Container
にマウントします。
Root Containerの削除の検出と最適化
ウェブページがDOM構造を変更すると、Root Container
が削除される場合があります。たとえば、受信トレイアイテムでいっぱいのメールクライアントと、各アイテムの横にインラインで挿入されたCSUIがあるとします。アイテムが削除されると、ルートコンテナも削除されます。
Root Container
の削除を検出するために、CSUI Renderer
は、マウントされた各コンテナのルートとwindow.document
オブジェクトを比較します。このチェックは、getShadowHostId
関数をエクスポートすることでO(1)に最適化できます。
import type { PlasmoGetShadowHostId } from "plasmo"
export const getShadowHostId: PlasmoGetShadowHostId = () => `adonais`
この関数は、検出された各アンカーのIDをカスタマイズすることもできます。
import type { PlasmoGetShadowHostId } from "plasmo"
export const getShadowHostId: PlasmoGetShadowHostId = ({ element }) =>
element.getAttribute("data-custom-id") + `-pollax-iv`
カスタムレンダラー
開発者は、デフォルトのレンダラーをオーバーライドするためにrender
関数をエクスポートできます。この機能が必要になる場合があります。
- カスタム
Inline container
またはOverlay container
を提供する - マウントロジックをカスタマイズする
- カスタム
MutationObserver
を提供する
たとえば、既存の要素をカスタムコンテナとして使用するには、次のようにします。
import type { PlasmoRender } from "plasmo"
import { CustomContainer } from "~components/custom-container"
const EngageOverlay = () => <span>ENGAGE</span>
// この関数は、デフォルトの`createRootContainer`をオーバーライドします
export const getRootContainer = () =>
new Promise((resolve) => {
const checkInterval = setInterval(() => {
const rootContainer = document.getElementById("itero")
if (rootContainer) {
clearInterval(checkInterval)
resolve(rootContainer)
}
}, 137)
})
export const render: PlasmoRender = async ({
anchor, // 観察されたアンカー、またはdocument.body。
createRootContainer // これはデフォルトのルートコンテナを作成します
}) => {
const rootContainer = await createRootContainer()
const root = createRoot(rootContainer) // 任意のルート
root.render(
<CustomContainer>
<EngageOverlay />
</CustomContainer>
)
}
カスタムコンテナを動的に作成する方法:
import type { PlasmoRender } from "plasmo"
import { CustomContainer } from "~components/custom-container"
const EngageOverlay = () => <span>ENGAGE</span>
// この関数は、デフォルトの`createRootContainer`をオーバーライドします
export const getRootContainer = ({ anchor, mountState }) =>
new Promise((resolve) => {
const checkInterval = setInterval(() => {
let { element, insertPosition } = anchor
if (element) {
const rootContainer = document.createElement("div")
mountState.hostSet.add(rootContainer)
mountState.hostMap.set(rootContainer, anchor)
element.insertAdjacentElement(insertPosition, rootContainer)
clearInterval(checkInterval)
resolve(rootContainer)
}
}, 137)
})
export const render: PlasmoRender = async ({
anchor, // 観察されたアンカー、またはdocument.body。
createRootContainer // これはデフォルトのルートコンテナを作成します
}) => {
const rootContainer = await createRootContainer(anchor)
const root = createRoot(rootContainer) // 任意のルート
root.render(
<CustomContainer>
<EngageOverlay />
</CustomContainer>
)
}
組み込みのInline Container
またはOverlay Container
を利用するには、次のようにします。
import type { PlasmoRender } from "plasmo"
const AnchorOverlay = ({ anchor }) => <span>{anchor.innerText}</span>
export const render: PlasmoRender = async (
{
anchor, // 観察されたアンカー、またはdocument.body。
createRootContainer // これはデフォルトのルートコンテナを作成します
},
_,
OverlayCSUIContainer
) => {
const rootContainer = await createRootContainer()
const root = createRoot(rootContainer) // 任意のルート
root.render(
// デフォルトのコンテナをマウントするには、アンカーを渡す必要があります。ここではデフォルトのアンカーを渡します
<OverlayCSUIContainer anchor={anchor}>
<AnchorOverlay anchor={anchor} />
</OverlayCSUIContainer>
)
}
MutationObserver
をカスタマイズする必要がある場合は、アンカーゲッター関数をエクスポートしないでください。それ以外の場合は、組み込みのMutationObserver
が引き続き生成されます。