VuePress v2 + Vite のプラグイン・テーマまわりの変更に対応したので記録する
個人ブログを VuePress v2 + Vite で立ち上げた記事を以前書きました。
その際に npm パッケージとして公開していた VuePress Plugin usePages のリポジトリに Issue が届きました。
調べてみると、どうやら VuePress 2.0.0-beta.40 (2022-04-25) にていくつかのブレイクチェンジがあり、プラグイン・テーマの取り扱いが変更されたようです。
備忘録をかね、新たなプラグインの設定方法および今回の対応手順について書いていきたいと思います。
バージョン 2.0.0-beta.40 以降のプラグイン・テーマ
設定ファイルの記述例
import { defineUserConfig } from 'vuepress'
import { viteBundler } from 'vuepress'
import { defaultTheme } from 'vuepress'
import { searchPlugin } from '@vuepress/plugin-search'
import { googleAnalyticsPlugin } from '@vuepress/plugin-google-analytics'
export default defineUserConfig({
bundler: viteBundler(),
theme: defaultTheme(),
plugins: [
searchPlugin(),
googleAnalyticsPlugin({
id: 'G-YOUR_ID',
}),
],
})
プラグイン
従来まで config.ts
の plugins
に、文字列でプラグイン名(もしくはプラグインファイルのパス)を記述していました。
今後は上記のようにプラグインをインポートして記述します。
プラグイン固有のオプションは googleAnalyticsPlugin()
のようにカッコ内に記述します。
テーマ
テーマも同様です。
インポートしたテーマを、直接 theme
プロパティに設定します。
(プロパティが変更になりました)
そのほかいろいろ
デフォルトのバンドラーが Vite になったことに伴い、これまで vuepress-vite
で利用していたものはすべて vuepress
に置き換え可能です。
バンドラーは bundler
で設定するようになったことに加え defineUserConfig()
に設定していた型引数は不要になりました。
TypeScript を意識することもなく、どのようなプロパティでどう設定すればよいかが分かりやすいですね。
PluginObject と ThemeObject
プラグインやテーマは、それぞれ PluginObject と ThemeObject を返す関数として記述します。
ThemeObject の作成例
たとえば defaultTheme
を継承した childDefaultTheme
を作成する場合は、次のように作成可能です。
import { defaultTheme } from 'vuepress'
import type { Theme, ThemeObject } from '@vuepress/core'
import { path } from '@vuepress/utils'
export type ChildDefaultThemeOptions = {}
export const childDefaultTheme = (options?: ChildDefaultThemeOptions): Theme => (app): ThemeObject => ({
name: 'vuepress-theme-default-child',
extends: defaultTheme(),
layouts: {
// レイアウトファイル docs/.vuepress/theme/layouts/Layout.vue を指定
Layout: path.resolve(__dirname, 'layouts/Layout.vue'),
// ロゴ docs/.vuepress/public/images/logo.png を指定
logo: '/images/logo.png',
},
})
export default childDefaultTheme
PluginObject の作成例
今回変更したプラグインは、次のようになりました。
import type { Page, PluginObject } from '@vuepress/core'
export interface UsePagesPluginOptions {
startsWith?: string
filter?: (page: Page) => boolean
sort?: (a: Page, b: Page) => number
limit?: number | false
file?: string
}
export const usePagesPlugin = (options?: UsePagesPluginOptions): PluginObject => {
const name = 'vuepress-plugin-use-pages'
const multiple = true
const onPrepared: PluginObject['onPrepared'] = (app) => {
const defaultSort = (a: Page, b: Page) => {
if (!a.data.frontmatter.date || !b.data.frontmatter.date) {
return 0
}
return (new Date(b.data.frontmatter.date).getTime()) - (new Date(a.data.frontmatter.date).getTime())
}
const {
startsWith = '/articles/',
filter,
sort = defaultSort,
limit = false,
file = 'pages.js',
}: UsePagesPluginOptions = options || {}
const docs = app.pages.filter(p => p.data.path.startsWith(startsWith))
const filtered = filter ? docs.filter(filter) : docs
filtered.sort(sort) // Sorted in place
const limited = limit ? filtered.slice(0, limit) : filtered
const pageData = limited.map(p => p.data)
const content = `export const usePages = () => ${JSON.stringify(pageData)}`
app.writeTemp(file, content)
}
return {
name,
multiple,
onPrepared,
}
}
コンポーネントをアタッチするだけのプラグインは、たとえば次のようになります。
import type { PluginObject } from '@vuepress/core'
import { path } from '@vuepress/utils'
export const examplePlugin = (options): PluginObject => {
return {
name: 'vuepress-plugin-example',
clientConfigFile: path.resolve(__dirname, `../client/clientConfig.mjs`),
}
}
クライアント側で必要なファイルを用意します。
import { defineClientConfig } from '@vuepress/client'
import MyNiceComponent from './components/MyNiceComponent.vue'
export default defineClientConfig({
enhance({ app }) {
app.component('MyNiceComponent', MyNiceComponent)
},
})
beta.44 以降 clientAppEnhanceFiles
と clientAppRootComponentFiles
と clientAppSetupFiles
hooks は clientConfigFile
に変更されました。
ビルドステップとランタイム
VuePress は、静的サイトジェネレーターによるCMSです。
Vite によるビルド時に html を書き出し、実行時(ランタイム)はSPAとして振る舞います。
プラグインは、ビルドステップで実行されるものと、ランタイムで実行されるものがあり、後者は Vue.js の機能を利用するものと考えればわかりやすいでしょう。
VitePress
VuePress 2 と同様 Vite + Vue.js 3 で構築された VitePress と比較されることがあります。
VitePress は VuePress よりもミニマルな CMS です。 Vue.js の公式ドキュメントも VitePress で構築されています。
プラグインの考え方もシンプルで、ビルドを伴う拡張は Vite 自体のプラグインとして、ランタイムなどそれ以外の拡張は Vue.js のプラグインとして拡張するようです。
両者の特徴を踏まえて、選択していきたいですね。
Nuxt 3 に対応した Nuxt Content v2 も登場しました。
それではよき Vue.js ライフを!
おまけ: NPM に更新版を公開する
プラグインのディレクトリ内は、別のリポジトリにしています。
機能の修正ができ、ビルドで書き出した JavaScript による動作が確認できたら、リモートリポジトリにあげておき、その後 np
により公開します。
今回も npx np --no-tests
を使い package.json
内のバージョン変更および GitHub へのプッシュと NPM への更新のすべてをいっきに行ってもらいました。便利。
最後に GitHub のリリースノートの作成ページが立ち上がるので、リリースノートを更新し完了です。
Discussion