VuePressにRSSフィードを追加する
VuePressは便利なんですが製品ドキュメント向けに作られている感が強く、ブログとして使おうとすると、まだ貧弱な部分も目立ちます。今回書くRSS機能なども現在のところ用意されてはいません。(Issueで議論などはされているようなので、今後公式に実装される可能性は高いと思います。)
ここでは、公式実装が待ちきれずに作ってしまった、RSS機能を見ていきます。
要件
RSSフィード出力のために必要な機能は以下です。
- RSS仕様に従いXML出力
- 出力されたファイルを保存
- 出力するファイルパスの解決
詳細については、このサイトを生成しているリポジトリのpackage.json
を確認するのが早いですが、簡単に必要なものを挙げておくと以下のようなものになります。
- vuepress: ^0.8.4
- rss: ^1.2.2
なお、ファイル保存に必要なfs-extra
、パス解決に必要なpath
は、いずれもvuepress
が依存ライブラリとして利用しているため、依存定義の必要はありません。
実装概要
VuePressでは、通常のコンテンツはHTMLとして生成されてしまうため、テーマのカスタマイズ等では今回の要件を達成することができません。そこで今回は、VuePressに用意されているApp Level Enhancementsを利用します。
App Level Enhancementsは、.vuepress/enhanceApp.js
を作成することで利用でき、この拡張ポイントでは、以下4つのパラメーターを受け取ることができます。
- Vue
- options
- router
- siteData
このうち、siteData
には、サイト内の全てのコンテンツのパース後の内容が保持されているため、これを利用することで、RSSフィードに必要な情報の一式を取り出すことができます。
こちらも詳細については、このサイトで使用しているenhanceApp.js
を確認するのが早いですが、簡単にどんなことをしているのかを紹介してみます。
export default ({
Vue,
options,
router,
siteData
}) => {
try {
// `vuepress build` では参照可能、
// `vuepress dev` では参照エラーを起こすので、
// これを利用してDEVモード時は後続処理をスキップする
global
} catch (e) {
// DEVモード時は後続処理をスキップ
return
}
const path = require('path')
const fs = require('fs-extra')
const RSS = require('rss')
// RSSフィードの基本情報を設定
const feed = new RSS({
title: siteData.title,
description: siteData.description,
feed_url: 'https://www.yo1000.com/rss.xml',
site_url: 'https://www.yo1000.com/',
copyright: '2018-present yo1000',
language: 'ja',
})
// 日付の設定されていない投稿に設定するための日付初期値
const d = new Date()
d.setTime(0)
// 型チェック用の日付型
const dateType = typeof d
siteData.pages.filter(page => {
// このサイトではブログポストを `/posts` 配下に集約しているため、
// サイト内の全てのページの中から `/posts/` で始まる、
// なんらかの名前を持つページのみ抽出
return /^\/posts\/.+/.test(page.path)
}).map(page => {
// ページメタデータの日付が設定されていない場合は、
// 初期値を設定する
if (!page.frontmatter.date) {
page.frontmatter['date'] = d
return page
}
// ページメタデータの日付が設定されていても日付型でない場合は、
// 日付オブジェクトにして再設定
if (typeof page.frontmatter.date !== dateType) {
page.frontmatter.date = new Date(page.frontmatter.date)
}
return page
}).sort((a, b) => {
// 投稿日の降順にソート
const aTime = a.frontmatter.date.getTime()
const bTime = b.frontmatter.date.getTime()
if (aTime < bTime) return 1
if (aTime > bTime) return -1
const aTitle = a.frontmatter.title
const bTitle = b.frontmatter.title
if (aTitle && !bTitle) return -1
if (!aTitle && bTitle) return 1
if (aTitle < bTitle) return -1
if (aTitle > bTitle) return 1
const aPath = a.path
const bPath = b.path
if (aPath < bPath) return -1
if (aPath > bPath) return 1
return 0
}).map(page => {
// ページオブジェクトを、
// RSSフィードのアイテム形式へ変換
return {
title: page.title,
description: page.frontmatter.description,
url: `http://www.yo1000.com${page.path}`,
date: page.frontmatter.date,
}
}).forEach(page => {
// RSSフィードにアイテムを設定
feed.item(page)
})
// VuePressのビルド出力ディレクトリへ
// XML形式でRSSフィードを書き出し
fs.writeFileSync(
path.resolve('./docs/.vuepress/dist', 'rss.xml'),
feed.xml()
)
}
と、こんな具合でRSSフィードの作成ができました。