Vueコンポーネント
VitePressではMarkdownファイルに直接<script>や<style>タグを書いて実行できます。あらかじめHTML, CSS, JavaScriptを組み合わせた部品を定義しておき,Markdownから呼び出すこともできます。この仕組みのことをVue Single-File Componentと呼びます。Vueコンポーネントを一度定義しておくと,複数のMarkdownファイルから共通の部品として使えます。
ディレクトリ構造とグローバルコンポーネント化
Vueコンポーネントは.vitepress/componentsに置きます。複数のVueコンポーネントを入れておけます。
.vitepress
├── config.mts
└── theme
├── components
│ ├── Component1.vue
│ ├── Component2.vue
│ └── Component3.vue
└── index.jsグローバルコンポーネントにするには.vitepress/theme/index.jsに以下のように書きます。このように書いておくと,どのMarkdownファイルからでも利用できます。
import DefaultTheme from 'vitepress/theme'
import Component1 from './components/Component1.vue'
import Component2 from './components/Component2.vue'
import Component3 from './components/Component3.vue'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('Component1', Component1)
app.component('Component2', Component2)
app.component('Component3', Component3)
}
}"hello, world"
まず,Markdownファイルに<hello /> と書くと hello, world に置き換えられるVueコンポーネントを作成してみます。
.vitepress/theme/components/Hello.vue を,以下の内容で作成します。
<template>
<p>hello, world</p>
</template>.vitepress/theme/index.ts を,以下の内容で作成します。
import DefaultTheme from 'vitepress/theme'
import Hello from './components/Hello.vue'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.component('Hello', Hello)
}
}そしてMarkdownファイルに以下のように書きます。
<hello />VitePressをリロードすると,この部分が以下のように出力されます。
hello, worldVueコンポーネントでは,<template> タグで囲われた部分は,VitePressドキュメントのDOMに挿入されるHTMLを記述します。この例では,以下のように記述しました。
<template>
<p>hello, world</p>
</template>これにより,Vueコンポーネントは,Markdownファイルの <hello /> の部分が <p>hello, world</p> と置き換える動作をします。
"hello, NAME"
デフォルト引数を設定しない場合
上記の例では,固定のHTMLに置き換えました。次に,hello, worldのworldの部分を引数で渡すVueコンポーネントを作成してみます。以後の例では,.vitepress/theme/index.ts はhello, worldのときと変わりませんので省略します。
.vitepress/theme/components/Hello.vue を以下に変更します。
<template>
<p>hello, world</p>
<p>hello, {{ name }}</p>
</template>
<script setup>
defineProps({
name: String
})
</script>Vueコンポーネントでは,<script setup> タグで囲われた部分がJavaScriptとして解釈・実行されます。ここで定義した変数は,<template> の中でmustachesを使い置き換えることができます。
defineProps() 関数は,VitePressから渡された値(props)を受け取るときの設定をします。ここでは name: String としていますが,これは name 属性が String 型であることをチェックしています。このチェック機構をバリデーションと呼びます。型が一致するか以外に,必須属性(required)であることを指定したり,自作のバリデータを設定したりできます。
Markdownファイルに以下のように書きます。
<hello name="John" />VitePressをリロードすると,この部分が以下のように出力されます。
hello, JohnMarkdownファイルではname="John" 属性をつけてこのVueコンポーネントを呼び出しています。この値である John が変数 name に代入されます。そして <template> の の部分が John で置き換えられ,<p>hello, John</p> がDOMに挿入されます。
では,name 属性を付けなければどうなるでしょうか。
<hello name="John" />
<hello />VitePressをリロードすると,この部分が以下のように出力されます。
hello, John
hello,name 属性がない場合はエラーにならず,空文字列として置き換えられます。意図せず不自然な表示になってしまうことを避けるため,属性の指定がない場合に採用される,いわゆるデフォルト引数を設定しておくことができます。
デフォルト引数(mustachesテンプレートで指定する)
mustachesでは,|| を用いてデフォルト引数を指定できます。例えば,以下のようにすると,name が未定義のときは 'world' が適用されます。
<template>
<p>hello, {{ name }}</p>
<p>hello, {{ name || 'world' }}</p>
</template>
<script setup>
defineProps({
name: String
})
</script>VitePressをリロードすると,先ほどの部分が以下のように出力されます。2行目では,デフォルト値である world が採用されています。
hello, John
hello, worldデフォルト引数(propで指定する)
defineProps では,辞書を使ってバリデーション指定できます。例えば,以下のようにすると,name が未定義のときは world が渡されます。
<template>
<p>hello, {{ name }}</p>
</template>
<script setup>
defineProps({
name: { type: String, default: 'world' }
})
</script><template>
<p>hello, {{ name || 'world' }}</p>
<p>hello, {{ name }}</p>
</template>
<script setup>
defineProps({
name: String
name: { type: String, default: 'world' }
})
</script>バリデーション
defineProps に指定できるバリデーション指定の詳細は props に書かれています。
| 名前 | 設定値 | 概要 |
|---|---|---|
type | StringNumberBooleanArrayObjectDateFunctionSymbolErrornull | 型が適合するかどうか調べます。複数の型のいずれかであればよい場合はリストを,どの型でもよい場合は null を指定します。 |
required | true/false | 省略されるとコンソールログに [Vue warn]: Missing required prop が記録されます。 |
default | デフォルト値 | 省略された場合に設定される値を指定します。 |
validator | ユーザーが定義した関数でバリデーションします。バリデーションに失敗するとコンソールログに [Vue warn]: Invalid prop: custom validator check failed for prop が記録されます。 |
validator を使うと,指定された値が想定しているものに合致するかどうか判定できます。例えば,align 属性は left, center, right のいずれかであってほしい場合は,次のように書きます。
validator: (value) => ['left', 'center', 'right'].includes(value)text-alignつきhello, NAME
ユーザー定義バリデータを使い,text-align スタイルを設定できる hello, NAME を作成してみます。
.vitepress/theme/components/Hello.vue を以下に変更します。
<template>
<p :style="{ textAlign: align || 'left' }">hello, {{ name || 'world' }}</p>
</template>
<script setup>
defineProps({
name: { type: String, default: 'world' },
align: {
type: String,
default: 'left',
validator: (value) => ['left', 'center', 'right'].includes(value)
}
})
</script><template>
<p>hello, {{ name }}</p>
<p :style="{ textAlign: align || 'left' }">hello, {{ name || 'world' }}</p>
</template>
<script setup>
defineProps({
name: { type: String, default: 'world' }
name: { type: String, default: 'world' },
align: {
type: String,
default: 'left',
validator: (value) => ['left', 'center', 'right'].includes(value)
}
})
</script>Markdownファイルに以下のように書きます。
<hello name="John" align="center" />
<hello name="Jane" align="right" />
<hello align="left" />すると,以下のように揃えられて表示されます。
hello, John
hello, Jane
hello, world
Boolean prop
propには Boolean 型を指定できます。Boolean は,そのキーが存在するかどうかを判定することができます。これを利用すると,例えば,Markdownファイルに以下のように書けます。
<hello name="John" center />
<hello name="Jane" right />
<hello left />これの意図するところは,一行目は中央揃え,二行目は右揃え,三行目は左揃えとするところ,align="center" ではなく center と書けるようにしたいということです。.vitepress/theme/components/Hello.vue を以下に変更します。
<template>
<span :style="{ textAlign: alignment }">hello, {{ name || 'world' }}</span>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
name: String,
left: Boolean,
right: Boolean,
center: Boolean
})
const alignment = computed(() => {
if (props.left) return 'left'
if (props.right) return 'right'
if (props.center) return 'center'
return 'left'
})
</script><template> 内の <span> タグに :style とあります。これは v-bind ディレクティブと呼ばれるものです。HTMLタグの属性部分にはmustaches記法が使えないため,変数置き換えをするために v-bind が利用されます。