634 文字
3 分
MDXでHTML,CSS,JSをブラウザで実行するAstroコンポーネントを作る
前回の記事では、CSSのコンテナクエリに関する記事を書きました。
その中で、MDXでHTML,CSS,JSをMDX内で書き、ブラウザでレンダリングする必要があったので、それを実現するコンポーネントを作成しました。
今回は、そのコンポーネント(TextToHTMLという名前にしています)について紹介します。
使用例
{/* 1. TextToHTMLコンポーネントを読み込む */}
import TextToHTML from 'src/components/markdown/TextToHTML.astro'
{/* 2. TextToHTMLコンポーネントに必要なデータを渡す */}
<TextToHTML
css=`
.box {
padding: 10px;
background-color: lightblue;
}
.text,
.js-result {color: #333;}
`
{/* 3. 他のコンポーネントに影響を与えないようにする都合上shadowDocumentという変数を用意して、アクセスできるようにしています */}
script=`
shadowDocument.querySelector(".js-result").innerText = shadowDocument.querySelector(".text").innerText + " World!"
`
>
<div class="box">
<p class="text">Hello</p>
<div class="js-result"></div>
</div>
</TextToHTML>
実際に上記のコードを実行すると、以下のようになります。
解説
以下のものがTextToHTMLコンポーネントのソースコードです。見ての通り、受け取った文字列のhtml, css, jsを愚直にレンダリングしているだけです。
工夫した点としては、CSS,JSが外部のHTMLに影響を与えないようにするためにShadow DOMを利用している点です。
idは、クライアント簡易的に生成しています。(絶対に衝突したくないのであれば、uuidを利用した方がいいです。)
少し気になる点としては、コンポーネントを利用する側から、documentで要素に楽にアクセスできない点です。
なので少し面倒ではありますが、shadowDocumentという変数を用意しており、それを指定することでShadow DOM内の要素にアクセスできるようにしています。
注意このコードを参考に実装する場合には、JS実行のためにFunctionを利用している点に注意が必要です。
JS部分の入力が外部からも可能である場合、Functionは任意のJavaScriptコードを実行するため、悪意のあるコードが実行されるリスクがあります。そのためこの実装はしてはいけません。
---
// slotで受け取る
type Props = {
css?: string;
script?: string;
};
const { css, script } = Astro.props;
const html = await Astro.slots.render("default");
const id = "astro-shadow-html-" + Math.random().toString(36).slice(2);
---
<div id={id} class="mb-5"></div>
<script define:vars={{ id, html, css, script }}>
const container = document.querySelector("#" + id);
const shadowRoot = container.attachShadow({ mode: "open" });
shadowRoot.innerHTML = html;
let sheet = new CSSStyleSheet();
sheet.replaceSync(css);
shadowRoot.adoptedStyleSheets.push(sheet);
const shadowDocument = shadowRoot;
new Function('shadowDocument', `'use strict'; ${script}`)(shadowDocument);
</script>
\てくのーと おすすめ書籍!/
OAuthという名前は知っているけれど、中身のフローは知らないという方におすすめです。
丁寧に書かれているので、理解がスムーズに進みます。 →感想詳細はこちら!