🤷♂️
MDsveX プラグイン + Tailwind CSS .prose で 期待する見た目にならない問題
2024-12-30 00:00
本ブログサイトを作成している中で、MDsveXで使用できるプラグインが期待通りの見た目にならない問題に遭遇していました。
MDsveX で使用できるプラグインといえば、 Remark
, Rehype
です。
これらのプラグインを、ある条件下で利用するとスタイルがぐちゃぐちゃになってしまいます。
その原因と対策について、紹介していきます。
どういう条件で問題が発生するの?
タイトルのとおりですが、Tailwind CSS で利用できる .prose
クラスと、デザインが関わるプラグインを掛け合わせたときに、デザイン崩れが発生します。
◉ 期待するデザイン
◉ 実際のデザイン
原因
tailwindcss-typography
の .prose
のCSSと、プラグインで用意(or 推奨)されているCSSが衝突する事によって、スタイル崩れが引き起こされています。
Tailwind CSS の .prose とは?
正式にお伝えすると、“Tailwind CSS の” というより、 tailwindcss-typography
の .prose
クラスの効果になります。
このクラスを付与した要素を良い感じの見た目にしてくれる優れモノです。
Markdown → HTML に変換したあと、細かいスタイルを設定することなく、 .prose
を付与するだけで整った見た目を実現してくれます。
MDsveX のプラグイン とは?
MDsveX は、mdsvex.config.js で以下のように指定し、 compile時にオプションとして読み込ませればプラグインが実行されます。
// mdsvex.config.js
import { defineMDSveXConfig as defineConfig } from "mdsvex";
import remarkLinkCard from "remark-link-card";
const config = defineConfig({
extensions: [".svelte.md", ".md", ".svx"],
remarkPlugins: [
// 読み込ませたいプラグインを指定
[remarkLinkCard, { shortenUrl: true }],
],
rehypePlugins: [],
});
export default config;
この設定をした状態で、更に公式で推奨されるCSSを書きます。
今更の紹介ですが、今回例として挙げているプラグインは RemarkLinkCard という、URLをリンクカードに変換してくれるものです。
公式で推奨されるCSSは以下に記載があります。
このスタイルを当てることによって、公式的に期待する見た目を実現してくれます。
この2つが合わさるとどうなるの?
例えばリンクカードだと <a>
や <img>
などのタグが必要になるわけですが、このタグに対して既に tailwindcss-typography
は CSS を当てています。
.prose :where(a):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
color: var(--tw-prose-links);
text-decoration: underline;
font-weight: 500;
}
.prose :where(img):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
margin-top: 2em;
margin-bottom: 2em;
}
そのため、「MDsveXプラグインに対するCSS + .prose
のCSS」が適用されてしまい、不要なマージンが加わってしまう等の問題が発生し、スタイル崩れに繋がります。
対策
tailwindcss-typography
の仕様になるのですが、もし .prose
以下の要素で用意されたCSSを当てたくない場合、その要素に対して .not-prose
というクラスを付与すればCSSを当てないようにすることが出来ます。
確かに、CSSもそのように書かれてますよね
/* :not(:where) を使って、 `.not-prose` を除外している */
.prose :where(img):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
margin-top: 2em;
margin-bottom: 2em;
}
まず、MDsveXのプラグインを独自にラップしたものを用意します。
そのラップの中では、生成される要素に対して not-prose
というクラスを付与するような処理を記述します。
RemarkLinkCard に対して、僕は以下のように記載しました。
import defaultRemarkLinkCard from "remark-link-card";
const remarkLinkCard = (options) => {
const basePlugin = defaultRemarkLinkCard(options);
return async (tree) => {
// 元々のプラグインの読み込み
const rlc = await basePlugin(tree);
for (let node of rlc.children) {
if (node.type !== "html" || !node.value) {
continue;
}
// 目当ての親要素を特定し、その要素に対して not-prose を付与する
node.value = node.value.replace("rlc-container", "rlc-container not-prose");
}
};
};
export default remarkLinkCard;
これにより、親要素に対して not-prose
が付与され、tailwindcss-typography
のCSSを当てることなく、純粋にプラグインに対するCSSのみを当てることに成功します。