MarpでUnoCSSを使って爆速楽々スタイリング

2025/01/10に公開

結論

以下のようなengineを設定することで、Marp(Marp CLI)内でUnoCSSを使うことができます

https://github.com/eyemono-moe/marp-uno/blob/main/engine.mjs

Marp CLIに置けるEngineの設定方法はhttps://github.com/marp-team/marp-cli?tab=readme-ov-file#engineをご覧ください。
(Marp for VSCodeではカスタムEngineの設定が出来ません)

実際に作成したスライドのサンプル:

https://marp-uno.pages.dev/

リポジトリ:

https://github.com/eyemono-moe/marp-uno

Marpとは

https://marp.app

簡単に説明すると、以下のような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
```

以下の記事でも詳細に説明されていますのでぜひご覧ください。

https://zenn.dev/su8ru/articles/marp-cli-lt-slide

https://zenn.dev/topics/marp

今回はCLI版のMarpを使ったスライドの作成を前提に進めます。

Marpのつらいところ

筆者はかなり長くMarpを使っているのですが、毎回スタイリングに困っていました。

「2列にして左右に配置したい」「このページは3列」「長くて入りきらないのでちょっとだけ字を小さくしたい」...

このような"ちょっとした"スコープの狭いスタイリングをするのがなかなか面倒なのです。もちろん<style>要素やhtmlのインラインスタイルを書くこともできますが、記述量が多くて面倒です。私は以前サークル内の勉強会用に約60枚のスライドをMarpで作成したことがあるのですが、いくらMarkdownで楽に書けるとはいえ、ちょっとしたスタイリングに時間と集中力を削がれた記憶があります。(もちろんパワポで作るよりは楽でしたが)

見せてあげよう、Marp の真髄をで紹介されているように、ユーティリティクラスを用意するのは1つの手だと思います。実際に私もこの方針でスタイリングをしていました。

https://zenn.dev/ykicchan/articles/c30efd827337c3

しかし、ユーティリティクラスは人間が手動で網羅するものではないでしょう。ユーティリティクラスを書いたら自動でcssを生成してくれればいいのに......それってUnoCSSじゃん

UnoCSSとは

https://unocss.dev

UnoCSSは、高い柔軟性と拡張性を持つ、高速・軽量なAtomic CSS エンジンです。

...これだけ聞いても何者かわからないかもしれませんが、ざっくり言うと、使われているクラスから自動でcssを生成してくれるツールです。

例えばUnoCSSで以下のようなルールを設定すると、m-10というクラスが使われたときにmargin: 10pxというcssが自動で生成されます。

uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  rules: [
    [/^m-([.\d]+)$/, ([, num]) => ({ margin: `${num}px` })],
  ],
});

もちろんこれらのルールを全て我々が書く必要は無く、TailwindCSSやBootstrapなどの有名なフレームワークのユーティリティクラスと同じ書き方が使えるルールセットが公式に用意されています。

https://github.com/unocss/unocss/tree/main/packages/preset-wind

https://github.com/unocss/unocss/tree/main/packages/preset-uno

UnoCSSについては以下の記事で非常にわかりやすくまとめられているので、ぜひご覧ください。

https://zenn.dev/comm_vue_nuxt/articles/what_is_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を使うことができました。

https://github.com/eyemono-moe/marp-uno/blob/main/engine.mjs

renderメソッド

Marpのエンジンはrenderメソッドを持っており、このメソッドがMarkdownからhtmlへの変換処理を行います。
そこで、Marpクラスを継承したUnoMarpクラスを作成し、renderメソッドをオーバーライドしてUnoCSSの処理を追加しています。

engine.mjs
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 }のような記法で要素にクラスや属性を指定できるようになります。これを使用することで、クラス指定が簡単になりました。

input
# header {.c-red}
paragraph {.text-center}
output
<h1 class="c-red">header</h1>
<p class="text-center">paragraph</p>

markdown-it-bracketed-spans

markdown-it-attrsと併せて使用することで、[span]{.myClass}のような記法でspan要素にクラスを指定できるようになります。

input
paragraph with [a span]{.bg-zinc}
output
<p>paragraph with <span class="bg-zinc">a span</span></p>

**text**{.class}のような記法でもインラインの一部要素にクラスを指定できますが、余計に<strong>になってしまうのを避けるためにこちらのプラグインを使用しています。

markdown-it-container

:::で囲むことにより、ブロック要素にクラスを指定できるようになります。

input
::: myClass
paragraph
:::
output
<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";
  },
});

これにより、以下のような少し無茶なコンテナも作成できます。

input
::::grid grid-cols-2 gap-4 items-center
:::

# Left

On Your Left

:::
:::

# Right

Now I'm on the right side of the slide.

:::
::::

output
<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ベースがもたらす以下の利点が好き。
      • シンプルにスライドを管理できる
      • バージョン管理がわかりやすい
      • 最低限のポータビリティが保たれる
      • 慣れているエディタで編集できる
  • UnoCSSを使うだけならSlidevで良くない?
    • 確かにSlidevはめちゃくちゃ良い。標準でUnoCSSをサポートしているし機能も豊富でかっこいい。特にMonacoEditor埋め込めるやつとか個人的に好き。ただ、Marpのシンプルさは採用に値する利点だと思います。Marpの"薄さ"がもたらすポータビリティや応用範囲の広さは、Slidevにはない魅力だと考えています。

Discussion