Zennっぽい見た目にする
概要
このブログ、Markdownのハイライト周りをTheme由来で色々任せていたが、正直あまりコードの内容がよろしくない。見た目的にはzennとかQiitaのような見た目がいいと考えている。
そんな中、Zennはデフォルトで見た目をZennっぽくしてくれるパッケージを公開してくれている。
- zenn-editor/packages/zenn-markdown-html at canary · zenn-dev/zenn-editor
- zenn-editor/packages/zenn-content-css at canary · zenn-dev/zenn-editor
で、これをこのブログに仕込もうとして結構コケたので備忘。
結論どうしたか
記事本文をShadow DOMに入れてTailwindを回避しつつ、zenn-content-cssを適用することである程度それっぽい見た目になった。
コケたポイント
SSRでコケる
bun run devするとviteが走ってる訳だが、そこで無限にコケるようになった。
markdownToHtmlがviteで走らせるとなぜか(0, __vite_...) is not a functionで叱られる形。
ホットリロードは犠牲になるが、compose watchを導入して毎回ビルドすることで回避することにした。
develop:
watch:
- action: rebuild
path: src
- action: rebuild
path: astro.config.mjs
- action: rebuild
path: package.json
- action: rebuild
path: tailwind.config.mjs
Tailwindが邪魔
借りているテンプレート(larry-xue/astro-zen-blog)がデフォルトでTailwind Pluginを使用している都合、Markdownは@tailwind/typographyを使ったprose classでスタイリングされている。
これが嫌いなのでzenn-content-cssを入れたい訳だが、当然ながらTailwindが邪魔。
というわけで、ここでShadowDOMの出番となった。
ShadowDOM生成と補正
shadow DOMを生成して、htmlを食わせてスタイリングする。
後はstyleタグで良い感じに補正をかける。
<style>
/* ===== テーマ変数 ===== */
article.znc {
--znc-bg: #ffffff;
--znc-text: #1f2937;
--znc-border: #e5e7eb;
--znc-code-bg: #f6f8fa;
--znc-code-text: #24292e;
}
article.znc[data-theme="dark"] {
--znc-bg: #0f172a;
--znc-text: #e5e7eb;
--znc-border: #334155;
--znc-code-bg: #020617;
--znc-code-text: #e5e7eb;
}
/* ===== Zenn 補正 ===== */
article.znc {
background: var(--znc-bg);
color: var(--znc-text);
}
article.znc :not(pre) > code {
background: var(--znc-code-bg);
color: var(--znc-code-text);
}
.znc pre {
background: var(--znc-code-bg);
color: var(--znc-code-text);
}
.znc .msg-content {
color: #1f2937;
}
.znc .code-block-container {
position: relative;
}
.znc .code-block-filename {
background: var(--znc-code-bg);
color: var(--znc-code-text);
}
</style>
CSSの参照
zenn-content-cssはビルド時にcssを/publicにコピーして参照する方式に。
observerの設定
外から変更が入った時用にobserverを入れる。
const observer = new MutationObserver(() => {
const dark =
document.documentElement.classList.contains("dark") ||
document.documentElement.dataset.theme === "dark";
const article = shadow.querySelector("article.znc") as HTMLElement;
article.dataset.theme = dark ? "dark" : "light";
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class", "data-theme"],
});
全体コード
全体でこんな感じ。あとはこれを<zenn-content html={html}/>で呼ぶだけ。
htmlはzenn-markdown-htmlで生成する。(MarkdownContent.render()は使わない)
全体コード
---
const { html } = Astro.props;
---
<zenn-content data-html={html}></zenn-content>
<script>
class ZennContent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
const html = this.dataset.html;
const isDark =
document.documentElement.classList.contains("dark") ||
document.documentElement.dataset.theme === "dark";
shadow.innerHTML = `
<link rel="stylesheet" href="/zenn-content.css">
<style>
/* ===== テーマ変数 ===== */
article.znc {
--znc-bg: #ffffff;
--znc-text: #1f2937;
--znc-border: #e5e7eb;
--znc-code-bg: #f6f8fa;
--znc-code-text: #24292e;
}
article.znc[data-theme="dark"] {
--znc-bg: #0f172a;
--znc-text: #e5e7eb;
--znc-border: #334155;
--znc-code-bg: #020617;
--znc-code-text: #e5e7eb;
}
/* ===== Zenn 補正 ===== */
article.znc {
background: var(--znc-bg);
color: var(--znc-text);
}
article.znc :not(pre) > code {
background: var(--znc-code-bg);
color: var(--znc-code-text);
}
.znc pre {
background: var(--znc-code-bg);
color: var(--znc-code-text);
}
.znc .code-block-container {
position: relative;
}
.znc .code-block-filename {
background: var(--znc-code-bg);
color: var(--znc-code-text);
}
</style>
<article
class="znc"
data-theme="${isDark ? "dark" : "light"}"
>
${html}
</article>
`;
const observer = new MutationObserver(() => {
const dark =
document.documentElement.classList.contains("dark") ||
document.documentElement.dataset.theme === "dark";
const article = shadow.querySelector("article.znc") as HTMLElement;
article.dataset.theme = dark ? "dark" : "light";
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class", "data-theme"],
});
}
}
if (!customElements.get("zenn-content")) {
customElements.define("zenn-content", ZennContent);
}
</script>
ぼやき
ChatGPTに聞いたらShadowDOMがいいよー言われて簡単にコードまでお出しされちゃって、シャドウ DOM の使用 - Web API | MDNを読んだ。
Shadow DOMもそうだけど、全体的にMDN Docsに読めていない事項が多すぎてよろしくない。とはいえ、API全部文書読むのはキツいし、どうしたものか…