この記事をおすすめしたい人
- v-htmlでコンポーネントを出力したい人
- WordPressのREST APIやmicroCMSでコンテンツ管理をしている人
- ヘッドレスCMSから取得した記事本文内のコンポーネントを展開したい人
- つまりオレ
何も知らない超初心者が脱WordPressしたくてNuxt.jsでサイト構築していくシリーズです。
タイトルは正確ではないですね。 最終的にv-htmlは使いませんでした。
今ちょっとmicroCMS(国産のヘッドレスCMS)でいろいろ実験をしていてですね。 今回はその中から
APIから取得した記事データ内のコンポーネントを展開する方法
をお話ししたいと思います。
WordPressの上でな!
※ このブログはまだWordPress製です
このページの目次
先にまとめ
だいぶ苦労したんですが、先に結果だけまとめます。
「v-runtime-template」モジュールをインストール
v-runtime-templateというモジュールを使うので、まずはインストール。
$ npm install v-runtime-template
nuxt.config.jsへの設定追加
何をやっているのかよく分かりませんが、nuxt.config.jsへ以下の設定を追加します。
export default {
build: {
extend(config, { isDev, isClient }) {
config.resolve.alias["vue"] = "vue/dist/vue.common"
}
},
}
node_modules以下に「vue/dist/vue.common」というファイルがあったので、Vueを定義しているファイルの参照先を変えてる…とか?
vueファイル内でモジュールをインポート
ページのvueファイル内の<script>でモジュールをimportして、componentsにVRuntimeTemplateを追加します。
<script>
import VRuntimeTemplate from 'v-runtime-template'
export default {
components: {
VRuntimeTemplate,
},
}
</script>
<template>内にコンポーネントを追加
ページのvueファイル内の<template>に<v-runtime-template>を追加します。
<template>
<v-runtime-template :template="content" />
</template>
引数「template」にasyncDataなどでAPIから取得した記事本文を渡します。
基本の流れはこれで終了です。 上記「content」にNuxt.jsのコンポーネントが含まれていた場合には、コンポーネントを出力して展開されます。
<nuxt-link>なんかはこれだけで動作するはず。
手順としてはこれで間違っていないようですが、実際にやってみると動かないケースがけっこうあると思うので、以下に注意点を書いておきます。
注意① templateに渡す文字列にはルートタグが必須
えー、この表現で合っているか…。 この↓部分です。
<template>
<v-runtime-template :template="content" />
</template>
templateに渡した値を<v-runtime-template>というコンポーネントとして出力するため、contentの中身を<div>なり<section>なりで外側を囲っていなくてはなりません。 普段Nuxt.jsでコンポーネントを作る時と同じ制限です。
ヘッドレスCMSに登録する際に外側を必ず<article>で囲うという縛りを自分に設けてもいいですが、忘れた際に事故を起こすので、この↓ようにNuxt.js側で処理している方が確実かも知れません。
<template>
<v-runtime-template :template="'<article>' + content + '</article>'" />
</template>
注意② 記事内に使われているコンポーネントは明示が必須
例えば<toc>というコンポーネントを記事内で使っているとします。
この場合、この↓ように使用しているコンポーネントを明示してやる必要があるようです。
<script>
import VRuntimeTemplate from 'v-runtime-template'
import toc from '@/components/toc'
export default {
components: {
VRuntimeTemplate,
toc,
},
}
</script>
コンポーネントが多いとけっこう手間です。
自動化する方法ないかな…。
注意③ _id.vueにnameプロパティが必須
僕は普段ページのvueファイルにnameプロパティは設定していないんですが、未設定だとこの↓ようなエラーがでました。
[Vue warn]: Invalid component name: "pages/blog/_id.vue". Component names should conform to valid custom element name in html5 specification.
これを解決するには_id.vueなり_slug.vueなりに名前を付けてやればいいようです。
export default {
name: 'blog-id',
}
注意④ コンポーネントにもnameプロパティが必須…?
ちょっとこれ曖昧です。 最初nameプロパティなしでもちゃんと動いていた気がしたんですが、最終的にはnameプロパティつけないとダメになりました。
上と同様にコンポーネントにも名前を指定してやります。
export default {
name: 'toc',
}
名前が必須なのは次に出てくる長い名前のやつだけかも。
注意⑤ 長い名前のコンポーネントはハイフンで接続…?
すいません、これもちょっと曖昧です。
というのも本実験、APIから取得した記事から動的に目次を自動生成するテストと同時に行ったため、どちらに必要な要素だったのか区別できていないからです。
やらずに動いた場合は無視してください。
で、ハイフンで接続というのはどういう意味かというと、実例では「Dds3SkillLink」のような名前のコンポーネントでエラーが発生しました。
[Vue warn]: Unknown custom element: <dds3skilllink> - did you register the component correctly?
For recursive components, make sure to provide the "name" option.
これまた名前を指定しろと書いてあるんで、この↓ように指定しましたが改善せず…。
export default {
name: 'Dds3SkillLink',
}
Nuxt.jsマニュアルでコンポーネントの説明みたけど分からず、Vue.jsの方を読んでいると<button-counter>のようにハイフンで接続していたのと、エラー内で<dds3skilllink>と全て小文字になっていたことから、なんとなく真似して「dds3-skill-link」という名前にしてみたら無事に動きました。
何がダメだったのか、何故動いたのかよく分かっていませんが、とりあえず動きました報告だけ。
このDds3SkillLinkというコンポーネントはかなりの数のページで使用していたため、名前変更となると大事だったんですが、実際には限定的な修正で済みました。
変えた部分は、
- Dds3SkillLink.vue(コンポーネント)のnameに「dds3-skill-link」を設定した
- microCMSに登録した記事内の<Dds3SkillLink>を<dds3-skill-link>に変更した
変えなかった部分は、
- Dds3SkillLinkのファイル名は変更なし
- 既存のページの<Dds3SkillLink>はそのまま
- _id.vue内でimportした際の変数名やファイル名は変更なし
これで既存のページでもちゃんとコンポーネントが動作して、APIを出力しているページでも動作しました。
う~ん、よく分からん。
注意点⑥ コンポーネントは閉じタグが必須…?
これも曖昧ですが、こちらはたぶん目次用の内容だと思います。
APIからのコンポーネント展開に興味ある人は目次の自動生成にも興味あるんじゃないかと思うので、合わせて書いておきます。
閉じタグというのは</Dds3SkillLink>みたいなタグのことで、これ↓ではダメで、
<Dds3SkillLink />
これ↓ならOKでした。
<Dds3SkillLink></Dds3SkillLink>
目次用に使ったcheerioというモジュールの仕様だと思います。
まとめ
以上です。
ヘッドレスCMS利用でのJAMStack構成を作る場合、API内のコンポーネント展開って必須とも呼べる要素だと思うので、同じことで困っている人は参考にしてもらえたら嬉しいです。
もしかすると漏れがあるかもしれませんが、実際に動いている環境があるので、この通りにやったけど動かなかった!みたいな場合は手助けできるかもしれません。 Twitter始めたのでTwitterにでもご連絡ください。
次回は目次の自動生成のお話をしようかと思います。
以上、WordPressからお届けしました!