VuePress プラグイン開発に挑戦 (ローカル編)

> この記事は、 [North Detail Advent Calendar 2019](https://qiita.com/advent-calendar/2019/northdetail) の8日目の記事です。

> 2020/04/02 追記
> 本記事は、NorthDetail ブログにも投稿しています。
> https://www.northdetail.co.jp/blog/448/

## 前置き

昨年に [VuePress](https://v1.vuepress.vuejs.org/) を知ってから、ちょっとしたドキュメントをまとめたサイトを作るのによく使っています。

Markdown でファイルをちゃちゃっと書けば、すぐに良い感じのサイトに仕立て上げられるので、すごく便利ですね。

ただ、コンテンツ全体を出すのにサイドバーにメニューを作っていかなきゃいけないんですが、それって結構面倒だなぁ、と思うんですよ。

「じゃぁ、プラグイン作っちゃえばよくね?」
と、思ったので挑戦してみました。

今回は NPM のパッケージではなく、ローカルに配置するパターンです。
また、作成した Markdown ファイルをアルファベット順に表示するだけ、です。
(これだけでも、シンプルなサイトなら十分楽になるので。)

## 準備

まず、 VuePress のプロジェクトを作りましょう。

```
$ mkdir sample-project
$ cd sample-project
$ yarn add -D vuepress
$ mkdir docs
```

できたら、順次下記ファイルを作成していきます。

ビルドなどを手軽にできるように、 `package.json` に `"scripts"` 追加。

``` package.json
{
  "devDependencies": {
    "vuepress": "^1.2.0"
  },
  "scripts": {
    "docs:dev": "vuepress dev docs",
    "docs:build": "vuepress build docs"
  }
}
```

サンプル表示用に、いくつか Markdown ファイルを作成。

``` docs/README.md
# 誰かの冒険

プラグイン作成のための冒険。

## 第1章: 旅立ち

[旅立ちます。](/01-departure)

## 第2章: 帰還

[ただいま。](/02-return)
```

``` docs/01-departure.md
# 第1章: 旅立ち

旅立ちます。

## 1.1: お城

王「ひのきの棒を与えよう。」

```

``` docs/02-return.md
# 第2章: 帰還

ただいま。

## 2.1: お城

王「死んでしまうとは何ごとだ。」
```

この状態で、開発サーバを立ち上げて画面を確認します。

```
$ yarn docs:dev
```

http://localhost:8080/ をブラウザで表示してみましょう。

![20191206_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10784/654d6340-73cb-44d9-729e-9081ca381cd4.png)

今の所サイドバーの設定はしていないので、何も表示されていませんね。

## プラグイン作成へ

### その前にサイドバーの設定をやる場合は

こんな感じで書きますね。

``` docs/.vuepress/config.js
module.exports = {
  themeConfig: {
    sidebar: [
      '/',
      '/01-departure',
      '/02-return',
    ]
  }
}
```

こうやると、当然ブラウザでサイドバーにメニューが表示されますね。

![20191206_002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10784/0a2da3c6-55df-d00e-c548-5d8a14b3ed7a.png)

表示確認できたら、 `sidebar` は不要なので消してください。

### プラグインを読み込む設定

では、プラグインを作っていきましょう。
今回は、ローカルにあるプラグイン用のJavaScriptを読み込みます。

[Using a Plugin | VuePress](https://v1.vuepress.vuejs.org/plugin/using-a-plugin.html#use-plugins-from-a-dependency)

``` docs/.vuepress/config.js
module.exports = {
  plugins: [
    require('./auto-sidebar')
  ]
}
```

`docs/.vuepress/auto-sidebar` のディレクトリの中に必要なファイルを作成していきます。

### プラグインを書く

そもそも、サイドバーを変更するにはどうすれば良いのか。

実は情報をパッとは見つけられなくて、結構試行錯誤をしました。。。

その中で、 [*App Level Enhancements*](https://v1.vuepress.vuejs.org/guide/basic-config.html#app-level-enhancements) の `siteData` を操作すれば良さそうなことがわかったので、こちらを色々といじっていきます。

作成したページの情報は `siteData.pages` に入っており、これを使ってメニューに相当する配列を作成します。
作成した配列は `siteData.themeConfig.sidebar` に格納すればよさそうです。

では、実際にサイドバーにメニューを表示するプラグインを書いてみましょう。

色々な設定内容(API)があるので、詳しくはオフィシャルのドキュメントを確認してみてください。
[Writing a Plugin | VuePress](https://v1.vuepress.vuejs.org/plugin/writing-a-plugin.html)

今回はプラグイン内で *App Level Enhancements* を利用できるような設定を行ないます。
これは `enhanceAppFiles` というのを設定すれば良いです。

``` docs/.vuepress/auto-sidebar/index.js
const path = require('path')

module.exports = (option, context) => ({
  enhanceAppFiles: path.resolve(__dirname, 'enhanceAppFile.js')
})
```

この設定で、実際の処理は同じディレクトリの `enhanceAppFile.js` でやるよ、というような意味になります。
では、実際の処理を見てみましょう。

``` docs/.vuepress/auto-sidebar/enhanceAppFile.js
export default ({ Vue, options, router, siteData }) => {
  const sidebar = []

  // regularPath を使うと、ページトップの `#` で宣言した内容をタイトルとして使ってくれる。
  for (const page of siteData.pages) {
    sidebar.push(page.regularPath)
  }

  // regularPath を昇順にソート
  sidebar.sort((page1, page2) => {
    return page1.localeCompare(page2)
  })

  siteData.themeConfig.sidebar = sidebar
}
```

### 確認

開発サーバ(`yarn docs:dev`)を起動していたら一旦終了して、再度立ち上げてください。
そして、再度 http://localhost:8080/ をブラウザで表示してみましょう。

設定を書いた時と同じように表示されましたね!

ここで、 Markdown 内のタイトルを変更したり、ファイルを追加したりすると、リアルタイムに変更・追加されることも確認できると思います。

例えば次のような Markdown を追加すると、

``` docs/03-legend.md
# 終章: そして伝説へ

お星さまになります。
```

すぐにブラウザに反映されます。

![20191206_003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10784/9bf6d2fd-75f5-4b51-4a6f-39737e6becd4.png)

これで目的達成できました!

作成した分は、下記リポジトリに置いています。
https://github.com/tacck/vuepress-plugin-sidebar

## 今後

もう少し構成の凝ったサイトでも導入できるように、もうちょっと色々とやってみたいですね。

* オプション対応
    * 固定のメニューを追加するなど [^1]
* グルーピング対応
* マルチ言語対応
* NPM パッケージ化 [^1]

徐々にチャレンジしていきます。

## FAQ

Q. プラグインを修正しても反映されない？
A. ローカルサーバを起動しなおしてみよう。うまいやり方あったら教えて。

Q. どこに設定要素があるかわからん。
A. 自分もわからん。 `console.log()` で頑張ったので、うまいやり方あったら教えて。。

Q. 順番をもうちょっとコントロールしたい。
A. ファイル名で頑張る想定。ソートしている部分を好きにすれば好きにできるので、プラグインを拡張してね。

[^1]: やってみました。 https://www.npmjs.com/package/vuepress-plugin-sidebar