🧶

yarnで複数のslidevのスライドやテーマを一元管理する

2022/09/27に公開

概要

yarn workspaces の仕組みを利用して、複数の slidev プロジェクトを monorepo で効率的に管理する方法についての私の取り組みをまとめました。

slidev

slidev は markdown と vue でスライドを書くことができる高機能なフレームワークです。
ここでは slidev そのものについては詳しく触れないので、公式などを参照してください。

https://sli.dev/

サンプル

なお、この記事の内容のサンプルとして github でリポジトリを公開しています。
このまま fork してテンプレート的に使うこともできるのでよしなにご利用ください。
https://github.com/s10akir/slidev-monorepo-sample

目的

slidev のプロジェクトを一元管理したい

slidev は 1 スライドにつき 1 npm package として振る舞います。
現状では同一プロジェクト内で複数のスライドを管理する機能はないようです。

LT のために複数作った slidev のプロジェクトを分散させずに効率的に管理することが今回の目的です。

yarn workspaces

yarn workspaces は yarn に実装されている「Monorepo で複数の npm package を管理する」仕組みです。

https://classic.yarnpkg.com/lang/en/docs/workspaces/

これを用いるとリポジトリ内で依存パッケージを共通で管理してくれます。workspace root から各 workspace の package 空間で npm script を実行などもできるので、単純に同じディレクトリで複数の slidev プロジェクトを同居させて管理するよりも取り回しが良くなるメリットもあります。

また、同一リポジトリに存在する別 package を直接依存として参照できるため、slidev の自作テーマを効率よく管理・再利用する事が可能になります。

実装

環境

次の環境で確認していますが、基本的にはそこそこ最近の nodejs と yarn が使えれば問題ないと思います。

software version
OS macOS v12.6
node.js v16.17.0
yarn v1.22.19

root workspace の作成

まずは、複数の slidev project を workspace として管理するために、 root workspace を作成します。

yarn workspaces では、管理対象になる各 package を workspace 、それらを管理する親 package を root workspace と呼びます。

今回は root workspace の package 名を slides としました。

npm package の初期化

# ディレクトリの作成
$ mkdir slides
$ cd slides

# 最小限の設定でnpm packageを初期化する
# NOTE: 実際には必要に応じてpackage.jsonを設定してください
$ yarn init -y

# node_modules/とyarn.lockを生成するために一旦空インストールする
$ yarn

これで、以下の構成で slides package が初期化されます。

slides/
├── node_modules
├── package.json
└── yarn.lock

1 directory, 2 files

package の private 化

次に、yarn workspaces は private package でのみ利用できるため package.json に以下の設定を追記します。

 {
   "name": "slides",
   "version": "1.0.0",
   "main": "index.js",
-  "license": "MIT"
+  "license": "MIT",
+  "private": true
 }

yarn workspaces の有効化

最後に、workspace を管理するための設定を行います。
workspace は 1 ディレクトリごとに明示して指定する事もできますが、今回は workspace を管理するためのディレクトリを作成し、そのサブディレクトリをすべて管理対象にします。

# workspace管理用のディレクトリを作成
$ mkdir packages

作成したディレクトリを workspace の管理対象とするため package.json に以下の設定を追記します。

  {
    "name": "slides",
    "version": "1.0.0",
    "main": "index.js",
    "license": "MIT",
-   "private": true
+   "private": true,
+   "workspaces": [
+     "packages/*"
+   ]
  }

これで yarn workspaces を用いて複数の npm package を管理することができるようになりました。

workspace として slidev のプロジェクトを作成する

実際に slidev のプロジェクトを作成し、yarn wrokspaces で管理を行います。

slidev のプロジェクトの作成

先程作成した root workspace の管理対象となるように、 workspace として slidev のプロジェクトを作成します。

ここでは package 名は slide-01 としました。
Install and start it now? は no を選択してください。

# slides/packages/ 配下にslidevプロジェクトを配置したいのでカレントを変更する
$ cd packages

# slidevプロジェクトの作成
$ yarn create slidev
yarn create v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Installed "create-slidev@0.36.5" with binaries:
      - create-slidev
[#######################] 23/23
  ●■▲
  Slidev Creator  v0.36.5

✔ Project name: … slide-01
  Scaffolding project in slide-01 ...
  Done.

✔ Install and start it now? … no

  start it later by:

  cd slide-01
  yarn
  yarn dev

  ● ■ ▲

✨  Done in 14.77s.

この時点で次のような構成になっています。

slides/
├── node_modules
├── package.json
├── packages
│   └── slide-01
│       ├── README.md
│       ├── components
│       │   └── Counter.vue
│       ├── netlify.toml
│       ├── package.json
│       ├── slides.md
│       └── vercel.json
└── yarn.lock

4 directories, 8 files

slidev プロジェクトにバージョンを明記する

yarn workspaces で管理する workspace には、npm package としての version が設定されている必要があります。
公開を目的としたバージョン管理ではないので、任意のバージョンを指定してください。
slide-01/package.json に以下の設定を追記します。

 {
   "name": "slide-01",
   "private": true,
+  "version": "1.0.0",
   "scripts": {
     "build": "slidev build",
     "dev": "slidev --open",
     "export": "slidev export"
   },
   "dependencies": {
     "@slidev/cli": "^0.36.5",
     "@slidev/theme-default": "*",
     "@slidev/theme-seriph": "*"
   }
 }

これで yarn workspaces の機能を用いて workspace slide-01 を管理する事ができるようになりました。

yarn install での依存解決

では、この状態で slide-01 package での依存解決を行ってみます。
slidev の cli やデフォルトテーマと、その依存関係が解決されるはずです。

# slide-01へカレントを変更
$ cd packages/slide-01

# yarnで依存解決
$ yarn
yarn install v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
warning @slidev/theme-seriph@0.21.3: The engine "slidev" appears to be invalid.
warning @slidev/theme-default@0.21.2: The engine "slidev" appears to be invalid.
warning theme-vitesse@0.1.14: The engine "vscode" appears to be invalid.
[3/4] 🔗  Linking dependencies...
warning "workspace-aggregator-f14d10f8-3260-4467-92d3-c74265e5ea04 > slide-01 > @slidev/cli > postcss-nested@5.0.6" has unmet peer dependency "postcss@^8.2.14".
warning "workspace-aggregator-f14d10f8-3260-4467-92d3-c74265e5ea04 > slide-01 > @slidev/cli > @slidev/client > @vueuse/motion@2.0.0-beta.22" has unmet peer dependency "@nuxt/kit@^3.0.0-rc.9".
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 15.09s.

依存 package が解決されました。
しかし、 packages/slide-01 ディレクトリには yarn.lock が生成されていません。また、node_modules ディレクトリが空になっています。

実はこの依存関係は root workspace に対して解決されており、バージョンロックも root workspace の yarn.lock に記録されています。

(正確には workspace 下の node_modules は空ではなく.binは存在していますが、この中に配置される実行ファイルも root workspace のそれへのシンボリックリンクになっています)

$ tree slides/node_modules/ -L 2 | head
slides/node_modules/
├── @ampproject
│   └── remapping
├── @antfu
│   ├── install-pkg
│   └── utils
├── @babel
│   └── parser
├── @braintree
│   └── sanitize-url
$ head slides/yarn.lock
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


"@ampproject/remapping@^2.2.0":
  version "2.2.0"
  resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
  integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==
  dependencies:
    "@jridgewell/gen-mapping" "^0.1.0"

これが yarn workspaces の依存解決の仕組みです。
なお、「複数 package から同一 package のバージョン違いの依存が発生した場合」には、各 workspace 配下の node_modules に依存が解決されます。

workspace の実行

では、作成した workspace slide-01 を実行してみます。
まずは通常通りプロジェクトのディレクトリからコマンドを実行します。

# slide-01へカレントを変更
$ packages/slide-01

# slidevの開発サーバを起動
$ yarn dev
arn dev
yarn run v1.22.19
$ slidev --open


  ●■▲
  Slidev  v0.36.5

  theme   @slidev/theme-seriph
  entry   /Users/s10akir/slides/packages/slide-01/slides.md

  public slide show   > http://localhost:3030/
  presenter mode      > http://localhost:3030/presenter/
  remote control      > pass --remote to enable

  shortcuts           > restart | open | edit

通常通り slidev を起動して、ブラウザからスライドが表示できることを確認してください。
次に、一旦この実行を Ctrl-C で終了して workspace root から実行してみます。

# root workspaceへカレントを変更(実際にはどこからでも実行可能)
$ cd ../../

# yarn workspacesを通してslide-01のslidev開発サーバを起動
$ yarn workspace slide-01 dev
yarn workspace v1.22.19
yarn run v1.22.19
$ slidev --open


  ●■▲
  Slidev  v0.36.5

  theme   @slidev/theme-seriph
  entry   /Users/s10akir/slides/packages/slide-01/slides.md

  public slide show   > http://localhost:3030/
  presenter mode      > http://localhost:3030/presenter/
  remote control      > pass --remote to enable

  shortcuts           > restart | open | edit

このように、yarn workspace コマンドを通して root workspace から workspace 単位での npm script を実行することができました。
なお、どちらの方法で実行しても特に差はないので好みで使い分ければ良いと思います。

複数 workspace を管理する

ここまでで yarn workspace の使い方を説明しましたが、これだけでは特に yarn workspace を使うメリットは享受できません。

なので、実際の運用を考慮して複数の workspace で複数の slidev プロジェクトを管理してみます。
先の slide-01 と同じ手順で slidev のプロジェクトを作成し、package.json に version 定義を追加します。

ここでは package 名を slide-02 としました。

# packagesディレクトリへカレントを変更
$ cd packages

# slidevプロジェクトの作成
# Project name => slide-02
# Install and start it now? => no
$ yarn create slidev

# package.jsonに "version" 追加
$ vim slide-02/package.json

# 依存解決
$ cd slide-02/
$ yarn
yarn install v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
warning @slidev/theme-default@0.21.2: The engine "slidev" appears to be invalid.
warning @slidev/theme-seriph@0.21.3: The engine "slidev" appears to be invalid.
warning theme-vitesse@0.1.14: The engine "vscode" appears to be invalid.
[3/4] 🔗  Linking dependencies...
warning "workspace-aggregator-994cd56e-f3bd-4a4f-b5b5-846f5368fed6 > slide-01 > @slidev/cli > postcss-nested@5.0.6" has unmet peer dependency "postcss@^8.2.14".
warning "workspace-aggregator-994cd56e-f3bd-4a4f-b5b5-846f5368fed6 > slide-01 > @slidev/cli > @slidev/client > @vueuse/motion@2.0.0-beta.22" has unmet peer dependency "@nuxt/kit@^3.0.0-rc.9".
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 0.49s.

ここで、yarn の依存解決が一瞬で終わることに注目してください。
先程作成した slidev プロジェクトと、今作成した slidev プロジェクトは同じ package に依存しています。つまり、slide-02 にとって必要な依存はすでに root workspace に解決済みであり、再度 package を取得してくる必要がありません。

これが yarn workspaces で複数 slidev プロジェクトを管理するときの強みになります。

同じリポジトリの workspace の参照

slidev では、カスタムテーマも 1 つの npm package として管理します。

このリポジトリ内でテーマ package も同じく workspace で管理し、他プロジェクトから参照する事ができればスライドを作る際に使いまわしが効き便利です。
ということで実際に試してみます。

slidev-theme プロジェクトの作成

slidev プロジェクトと同様にpackages配下に slidev-theme プロジェクトを作成します。

ここでは、package 名を slidev-theme-one としました。
slidev-theme プロジェクトは version 定義がすでにされているため、特に package.json を変更する必要はありません。

# packagesディレクトリへカレントを変更
$ cd packages

# slidev-themeのプロジェクトを作成
$ yarn create slidev-theme
yarn create v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Installed "create-slidev-theme@0.36.5" with binaries:
      - create-slidev-theme
[#########################] 25/25
  ●■▲
  Slidev Theme Creator  v0.36.5

✔ Theme name: … slidev-theme-one
  Scaffolding Slidev theme in slidev-theme-one ...
  Done.


  start it by:

  cd slidev-theme-one
  yarn
  yarn dev

  ● ■ ▲

✨  Done in 3.74s.

# 依存の解決
$ cd slidev-theme-one
$ yarn
yarn install v1.22.19
warning package.json: No license field
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
warning slidev-theme-one@0.0.0: The engine "slidev" appears to be invalid.
warning @slidev/theme-default@0.21.2: The engine "slidev" appears to be invalid.
warning @slidev/theme-seriph@0.21.3: The engine "slidev" appears to be invalid.
warning theme-vitesse@0.1.14: The engine "vscode" appears to be invalid.
warning theme-vitesse@0.5.2: The engine "vscode" appears to be invalid.
[3/4] 🔗  Linking dependencies...
warning "workspace-aggregator-e0aa2505-c0ed-426c-bfeb-dd93ecca18c9 > slide-01 > @slidev/cli > postcss-nested@5.0.6" has unmet peer dependency "postcss@^8.2.14".
warning "workspace-aggregator-e0aa2505-c0ed-426c-bfeb-dd93ecca18c9 > slide-01 > @slidev/cli > @slidev/client > @vueuse/motion@2.0.0-beta.22" has unmet peer dependency "@nuxt/kit@^3.0.0-rc.9".
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 2.59s.

slidev プロジェクトから slidev-theme プロジェクトを参照する

では、先程作成した slidev-theme プロジェクトを利用してみます。

packages/slide-01/package.json を次のように変更します。

 {
   "name": "slide-01",
  "private": true,
   "version": "1.0.0",
   "scripts": {
     "build": "slidev build",
     "dev": "slidev --open",
     "export": "slidev export"
   },
   "dependencies": {
     "@slidev/cli": "^0.36.5",
     "@slidev/theme-default": "*",
-    "@slidev/theme-seriph": "*"
+    "@slidev/theme-seriph": "*",
+    "slidev-theme-one": "*"
   }
 }

依存関係に先程作成した slidev-theme-one を追加しました。
この状態で yarn の依存解決を行います。

# slide-01へカレントを変更
$ cd packages/slide-01

# 依存解決
$ yarn
yarn install v1.22.19
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
warning slidev-theme-one@0.0.0: The engine "slidev" appears to be invalid.
warning @slidev/theme-default@0.21.2: The engine "slidev" appears to be invalid.
warning @slidev/theme-seriph@0.21.3: The engine "slidev" appears to be invalid.
warning theme-vitesse@0.5.2: The engine "vscode" appears to be invalid.
warning theme-vitesse@0.1.14: The engine "vscode" appears to be invalid.
[3/4] 🔗  Linking dependencies...
warning "workspace-aggregator-c3fed39a-75d5-49cf-821b-d37616167902 > slide-01 > @slidev/cli > postcss-nested@5.0.6" has unmet peer dependency "postcss@^8.2.14".
warning "workspace-aggregator-c3fed39a-75d5-49cf-821b-d37616167902 > slide-01 > @slidev/cli > @slidev/client > @vueuse/motion@2.0.0-beta.22" has unmet peer dependency "@nuxt/kit@^3.0.0-rc.9".
[4/4] 🔨  Building fresh packages...

依存解決に成功しました。
では、実際にこのテーマが利用できるか確認してみます。

packages/slide-01/slide.md を次のように変更します。
frontmatter の中身をすべて削除して、 theme: one を記述します。

 ---
-# try also 'default' to start simple
-theme: seriph
+theme: one
-# random image from a curated Unsplash collection by Anthony
-# like them? see https://unsplash.com/collections/94734566/slidev
-background: https://source.unsplash.com/collection/94734566/1920x1080
-# apply any windi css classes to the current slide
-class: 'text-center'
-# https://sli.dev/custom/highlighters.html
-highlighter: shiki
-# show line numbers in code blocks
-lineNumbers: false
-# some information about the slides, markdown enabled
-info: |
-  ## Slidev Starter Template
-  Presentation slides for developers.
-
-  Learn more at [Sli.dev](https://sli.dev)
-# persist drawings in exports and build
-drawings:
-  persist: false
-# use UnoCSS
-css: unocss
 ---

 # Welcome to Slidev

これで slide-01 から slide-theme-one を参照する設定ができました。
slide-01 を起動してみます。

$ yarn workspace slide-01 dev

テーマがデフォルトから変わりました。正しく参照できているようです。

次に、slide-01 のサーバは起動したまま slidev-theme-one に変更を加えてみます。

packages/slidev-theme-one/styles/layout.css を次のように変更してください。
わかりやすく表紙の背景色を変更しています。

 .slidev-layout.cover,
 .slidev-layout.intro {
+
+  background: var(--slidev-theme-primary);
+
   @apply h-full grid;

   h1 {

すると、ブラウザで表示している slide-01 にも変更後のスタイルが適用されました。

これで、同一リポジトリ内の slidev-theme プロジェクトを再利用することができるようになりました。
当然 slide-02 からも同じように利用することができますし、slidev-theme プロジェクトが増えても同じように参照ができます。

まとめ

以上の通り、yarn workspaces の機能を活用することで slidev のスライドやテーマを一元管理することができるようになりました。これで 1 リポジトリなので、このまま github で管理なども可能です。

個人的には、作りかけのテーマや完全に自分用のテーマを作った場合にあまり npm publish したくないので、こういった形で再利用性高く管理できるのがうれしいポイントです。
個人テーマについては github で管理して git 経由での npm package 解決という手段もありますが、仮にテーマを private リポジトリにしたくなったさいにトークンの管理などが面倒になるので、プロジェクト内のスコープに閉じ込められるのはメリットかなと思います。

ちなみに、workspace はネストはできませんが複数のディレクトリを切ることができるので、
root workspace の package.json を

{
  "name": "slides",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "private": true,
  "workspaces": ["slides/*", "theme/*"]
}

のようにして、

slides/
├── node_modules
├── package.json
├── slides
│   ├── slide-01
│   └── slide-02
├── theme
│   └── slidev-theme-one
└── yarn.lock

こんなディレクトリ構成でスライドとテーマを分けて管理することも可能です。
この場合でも package 名だけで相互に参照することができます。

Discussion