MarpでUnoCSSを使って爆速楽々スタイリング
結論
以下のようなengineを設定することで、Marp(Marp CLI)内でUnoCSSを使うことができます。
Marp CLIに置けるEngineの設定方法はhttps://github.com/marp-team/marp-cli?tab=readme-ov-file#engineをご覧ください。
(Marp for VSCodeではカスタムEngineの設定が出来ません)
実際に作成したスライドのサンプル:
リポジトリ:
Marpとは
簡単に説明すると、以下のようなMarkdownからスライドを作成できるJavaScript製のツールです。
# **Marp**
Markdown Presentation Ecosystem
https://marp.app/
---
# How to write slides
Split pages by horizontal ruler (`---`). It's very simple! :satisfied:
```markdown
# Slide 1
foobar
---
# Slide 2
foobar
```
以下の記事でも詳細に説明されていますのでぜひご覧ください。
今回はCLI版のMarpを使ったスライドの作成を前提に進めます。
Marpのつらいところ
筆者はかなり長くMarpを使っているのですが、毎回スタイリングに困っていました。
「2列にして左右に配置したい」「このページは3列」「長くて入りきらないのでちょっとだけ字を小さくしたい」...
このような"ちょっとした"スコープの狭いスタイリングをするのがなかなか面倒なのです。もちろん<style>
要素やhtmlのインラインスタイルを書くこともできますが、記述量が多くて面倒です。私は以前サークル内の勉強会用に約60枚のスライドをMarpで作成したことがあるのですが、いくらMarkdownで楽に書けるとはいえ、ちょっとしたスタイリングに時間と集中力を削がれた記憶があります。(もちろんパワポで作るよりは楽でしたが)
見せてあげよう、Marp の真髄をで紹介されているように、ユーティリティクラスを用意するのは1つの手だと思います。実際に私もこの方針でスタイリングをしていました。
しかし、ユーティリティクラスは人間が手動で網羅するものではないでしょう。ユーティリティクラスを書いたら自動でcssを生成してくれればいいのに......それってUnoCSSじゃん
UnoCSSとは
UnoCSSは、高い柔軟性と拡張性を持つ、高速・軽量なAtomic CSS エンジンです。
...これだけ聞いても何者かわからないかもしれませんが、ざっくり言うと、使われているクラスから自動でcssを生成してくれるツールです。
例えばUnoCSSで以下のようなルールを設定すると、m-10
というクラスが使われたときにmargin: 10px
というcssが自動で生成されます。
import { defineConfig } from "unocss";
export default defineConfig({
rules: [
[/^m-([.\d]+)$/, ([, num]) => ({ margin: `${num}px` })],
],
});
もちろんこれらのルールを全て我々が書く必要は無く、TailwindCSSやBootstrapなどの有名なフレームワークのユーティリティクラスと同じ書き方が使えるルールセットが公式に用意されています。
UnoCSSについては以下の記事で非常にわかりやすくまとめられているので、ぜひご覧ください。
UnoCSSはTailwindCSSの(ほぼ)スーパーセットとなっているため、筆者はもうずっとUnoCSSを使っています。
MarpでUnoCSSを使う
さて、ここまで来たらMarpでUnoCSSを使ってスタイリングしたいですよね。
Marp CLIではMarkdownからスライドへの変換エンジンにデフォルトでMarp Coreのエンジンが使用されています。
https://github.com/marp-team/marp-cli?tab=readme-ov-file#engineで紹介されているように、このエンジン部分は設定ファイルを作成することで自由に指定できます。独自のエンジンを作成し、MarpによるHTMLへの変換処理後にUnoCSSを噛ませてあげれば良さそうです。
結論としては以下のようなエンジンを設定することで、Marp(Marp CLI)内でUnoCSSを使うことができました。
renderメソッド
Marpのエンジンはrender
メソッドを持っており、このメソッドがMarkdownからhtmlへの変換処理を行います。
そこで、Marpクラスを継承したUnoMarpクラスを作成し、render
メソッドをオーバーライドしてUnoCSSの処理を追加しています。
import { Marp } from "@marp-team/marp-core";
import { createGenerator } from "@unocss/core";
import unoConfig from "./uno.config.mjs";
const unoGenerator = await createGenerator(unoConfig);
class UnoMarp extends Marp {
async render(markdown, env) {
const res = super.render(markdown, env);
const { css: unoCss } = await unoGenerator.generate(res.html, {
minify: true,
});
return {
...res,
css: unoCss + res.css,
};
}
}
UnoCSSによるcss生成は、@unocss/core
を使って行います。createGenerator
関数にUnoCSSの設定ファイルを渡してcss生成器を作成し、generate
メソッドでhtmlからcssを生成しています。
あとは、Marpのrender
メソッドで生成されたhtmlとUnoCSSで生成されたcssを結合して返せば、MarpのスライドにUnoCSSが適用されるようになります🎉
Markdown-itプラグインでクラス指定を簡単に
Marpではmarkdown-itのプラグインを利用できます。
html要素を使った<div class="...">
のようなクラス指定も可能ですが、せっかくなのでMarkdown側でクラス指定を簡単にできるようにするため、いくつかプラグインを使用しています。
markdown-it-attrs
{ .class1 .class2 key=value }
のような記法で要素にクラスや属性を指定できるようになります。これを使用することで、クラス指定が簡単になりました。
# header {.c-red}
paragraph {.text-center}
<h1 class="c-red">header</h1>
<p class="text-center">paragraph</p>
markdown-it-bracketed-spans
markdown-it-attrsと併せて使用することで、[span]{.myClass}
のような記法でspan要素にクラスを指定できるようになります。
paragraph with [a span]{.bg-zinc}
<p>paragraph with <span class="bg-zinc">a span</span></p>
**text**{.class}
のような記法でもインラインの一部要素にクラスを指定できますが、余計に<strong>
になってしまうのを避けるためにこちらのプラグインを使用しています。
markdown-it-container
:::
で囲むことにより、ブロック要素にクラスを指定できるようになります。
::: myClass
paragraph
:::
<div class="myClass">
<p>paragraph</p>
</div>
一部スライドのみgridでレイアウトしたい場合などに非常に便利です。
デフォルトの設定のままだと、::: message
のように決まった名前とクラスが付いたコンテナしか使えないのですが、以下のように
-
validate
で任意の名前を許可 -
render
で付与された名前を全てクラスに指定
するように設定を変更しています。
marp.use(markdownItContainer, "", {
// allow empty name
validate: () => true,
render: (tokens, idx) => {
const _class = tokens[idx].info.trim();
if (tokens[idx].nesting === 1) {
// opening tag
return `<div ${_class ? `class="${_class}"` : ""}>\n`;
}
// closing tag
return "</div>\n";
},
});
これにより、以下のような少し無茶なコンテナも作成できます。
::::grid grid-cols-2 gap-4 items-center
:::
# Left
On Your Left
:::
:::
# Right
Now I'm on the right side of the slide.
:::
::::
<div class="grid grid-cols-2 gap-4 items-center">
<div>
<h1 id="left">Left</h1>
<p>On Your Left</p>
</div>
<div>
<h1 id="right">Right</h1>
<p>Now I'm on the right side of the slide.</p>
</div>
</div>
おわりに
数十行の設定だけでUnoCSSが使えるようになり、自分でもかなり驚いています。特にIcons presetを使って簡単にアイコンを挿入できるのが好きです。
皆さんもUnoCSSで爆速楽々スタイリングを楽しんでみてください!
予想されるコメント
- もうパワポでいいじゃん
- 確かにスタイリングを頑張るならもうWYSIWYGエディタで作れよと思われるかもしれません。ですが、Marpのつらいところでも触れたように、今回のアプローチは、 スコープが狭く,登場頻度の少ないスタイリングを,手軽に行うこと を目的にしています。
基本的にはUnoCSSに頼らずに書き、本当に必要な時だけUnoCSSでスタイリングするという使い方が理想です。アニメーションや複雑なレイアウトを行う場合は、やはりパワポやGoogleスライドの方が適していると思います。 - (UnoCSSとか関係なく)そもそもMarkdownベースがもたらす以下の利点が好き。
- シンプルにスライドを管理できる
- バージョン管理がわかりやすい
- 最低限のポータビリティが保たれる
- 慣れているエディタで編集できる
- 確かにスタイリングを頑張るならもうWYSIWYGエディタで作れよと思われるかもしれません。ですが、Marpのつらいところでも触れたように、今回のアプローチは、 スコープが狭く,登場頻度の少ないスタイリングを,手軽に行うこと を目的にしています。
- UnoCSSを使うだけならSlidevで良くない?
- 確かにSlidevはめちゃくちゃ良い。標準でUnoCSSをサポートしているし機能も豊富でかっこいい。特にMonacoEditor埋め込めるやつとか個人的に好き。ただ、Marpのシンプルさは採用に値する利点だと思います。Marpの"薄さ"がもたらすポータビリティや応用範囲の広さは、Slidevにはない魅力だと考えています。
Discussion