コンポーネントをVSCode上でプレビューできるvue-component-previewが最強だった件

# Abstract

本記事では、Vue.jsにおいてコンポーネントをプレビューしながらコードを書ける、vue-component-previewを紹介します。(図: プレビューしている様子)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483769/c113305e-a3d9-ade5-0c01-41ebb2e1324f.png)
こちらのプラグインは、本記事をサクッと読んでいただくとわかる通り、かなり"良い"です。
しかしながら、紹介/解説記事がQiitaには恐らくありませんでした。
一方でZennには最後に示した参考記事が一つありましたが、最も基本の機能しか紹介していません。
そこで本記事では、**基本機能から始まって公式の紹介でも紹介していない機能までを紹介していきます**。

:::note warn
VSCodeのお話です。
Vue.jsではscript setup記法 / TypeScriptを採用しています。
環境のセットアップ自体の詳しい解説は行いません。
:::

# Vue.jsのセットアップ

いつもの。
本記事ではyarnを用います。
その他でも基本的には大きな違いはないはずなので、yanr以外の方は適宜読み替えてください。

```shell
# npm派閥
npm create vite@latest
# yarn派閥
yarn create vite
# pnmp派閥
pnpm create vite
```

例のごとく、質問に答えていきます。
プロジェクト名はお好みで。本記事では「test-vue-component-preview」とでもしておきます。
もちろん、Vue.js, TypeScriptを選択。

必要なパッケージをインストールしていきます。

```shell
cd test-vue-component-preview
# vue等を一括インストール
yarn
```

## 拡張機能の導入

Vue and Nuxt Preview (from Volar) をインストールします。

https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.vscode-vue-preview

## プラグインの導入

プレビュー機能を実現するプラグインを導入します。

```shell
yarn add -D vite-plugin-vue-component-preview
```

次に、vite.config.tsに追記します。

```diff_typescript:vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
+ import Preview from "vite-plugin-vue-component-preview"


// https://vitejs.dev/config/
export default defineConfig({
- plugins: [vue()],
+ plugins: [Preview(), vue()],
})
```

続いて、tsconfig.jsonに追記します。

```diff_json:tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "strict": true,
    "jsx": "preserve",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["ESNext", "DOM"],
+   "types": ["vite-plugin-vue-component-preview/client"],
    "skipLibCheck": true,
    "noEmit": true
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
```

最後に、src/main.tsに追記します。

```diff_typescript:src/main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
+ import Previewer from "virtual:vue-component-preview"

- createApp(App).mount('#app')
+ const app = createApp(App)
+ app.use(Previewer)
+ app.mount("#app")
```

~~`virtual:vue-component-preview`のエラーが消えなかったりしますが、問題ありません。気にしないでください。~~
これで.vueファイルを開くと、左下に画面のプレビューが表示されます。
この後作成するボタンなどは、そのファイルを開くと、次の図のようにエクスプローラーの左下に表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483769/b3b7f056-c9c5-06d3-3463-c1e31b1c3ca2.png)
この**プレビューは色々な場所にドラッグアンドドロップで動かせる**ので、お好みの場所に動かしましょう。
おすすめはターミナルの右ですかね。↓こんな感じで、大きめのモニターだといい感じになると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483769/a20f0d65-9e86-7121-fb75-1469601c928a.png)


## Take Over Mode

一部のimport (vueファイルのimport) がエラーを出したりしますが、これは既定のTypeScriptの拡張機能がvueに対応していないからです。
Vue.js向けの拡張機能 Volar でその辺の機能も含めてサポートしているので、Volarをインストールしたら、拡張機能マーケットで @builtin と検索して、TypeScript and JavaScript Language Feature をワークスペースで無効化します。(↓コレ)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483769/edfed277-16d1-6c57-7f75-94959798de23.png)


# 例

百聞は一見に如かず。
ということで、簡単なコンポーネントでプレビューを見てみます。

## シンプルなボタン

シンプルなボタンを定義してみます。

```vue:src/component/MyButton.vue
<script setup lang="ts">

</script>

<template>
    <button>MyButton</button>
</template>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483769/dea1447d-b493-3c5f-d448-4c1e84e37599.png)

プレビューされました！
なお、viteで自動作成された`src/style.css`を`main.ts`で読み込んでいるため、そのスタイルが当たっています。

## Propsどうすんの

propsを使ったボタンを定義してみましょう。

```vue:src/component/MyButton.vue
<script setup lang="ts">
interface Props {
    title: string
}
const props = defineProps<Props>()
</script>

<template>
    <button>{{ title }}</button>
</template>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483769/e809124b-ed3a-88b9-8c55-06dddc65598a.png)

まぁ当たり前ですが、文字が何も表示されないボタンがプレビューされます。
プレビューする用に、なにかしらのpropsを渡したいところですね。
次のように、`<preview>`タグを追加してみましょう。

```diff_vue:src/component/MyButton.vue
<script setup lang="ts">
interface Props {
    title: string
}
const props = defineProps<Props>()
</script>

<template>
    <button>{{ title }}</button>
</template>

+ <preview lang="md">
+ <slot title="Hello" />
+ </preview>
```

すると、**なんとpropsを反映したプレビューになります**。
つまり**slotタグにコンポーネントが代入される**っぽいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483769/8215e2dd-0bf1-49ce-eb87-355eb974f755.png)
※もしかしたら、即座に反映されないかもしれません。プレビューのタブを閉じて開くなどを試すと、うまく行くかもしれません。

しかもこれ、`lang="md"`と書いてある通りマークダウンなので、軽いドキュメント書けます。
更に更に、`v-for`とかが使えるので、次のように異なるpropsを描画とかも可能です。

```vue:src/component/MyButton.vue
<script setup lang="ts">
interface Props {
    title: string
}
const props = defineProps<Props>()
</script>

<template>
    <button>{{ title }}</button>
</template>

<preview lang="md">
# MyButton Preview

<script setup lang="ts">
const titles = [
    "こんにちは",
    "Hello"
]
</script>

<template v-for="title in titles">
    <slot :title="title" />
</template>
</preview>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483769/ce23735d-1323-bc6d-a4db-050df3c889cb.png)

もうこの時点でかなり強いです！！

## previewにはCSSを追加できるっぽい。

プラグインのgithubを見ると、`style`タグを追加していました。
**色々試してみたところ、トップレベルに`template`が必ず必要というわけではないことが分かりました**。
次のように、CSSを当てて`v-for`してみる。

```vue:src/component/MyButton.vue
<!-- さっきと同じ -->
<preview lang="md">
# MyButton Preview

<script setup lang="ts">
const titles = [
    "こんにちは",
    "Hello"
]
</script>

<div class="button-container">
    <template v-for="title in titles" key="title">
        <slot :title="title" />
    </template>
</div>

<style>
body {
    background-color: grey;
}
.button-container {
    display: flex;
    justify-content: space-around;
}
</style>
</preview>
```

すると、**CSSが反映されてしまう**。マジかよ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483769/cb8efb1a-9f87-899a-466a-c953120c0c94.png)

## 他のコンポーネントをimportすることもできる

src/component/FlexContainer.vueを次のように書いてみる。(実用性はおいておきます。)

```vue:src/component/FlexContainer.vue
<script setup lang="ts">
</script>

<template>
    <div class="flex-container">
        <slot />
    </div>
</template>

<style scoped>
.flex-container {
    display: flex;
    justify-content: space-around;
}
</style>
```

MyButton.vueのpreviewの部分を次のように変更してみる。

```diff_vue:src/component/MyButton.vue
<preview lang="md">
# MyButton Preview

<script setup lang="ts">
+ import FlexContainer from "./FlexContainer.vue";
const titles = [
    "こんにちは",
    "Hello"
]
</script>

+ <FlexContainer>
- <div class="button-container">
    <template v-for="title in titles" key="title">
        <slot :title="title" />
    </template>
+ </FlexContainer>
- </div>

- <style>
- body {
-     background-color: grey;
- }
- .button-container {
-     display: flex;
-     justify-content: space-around;
- }
- </style>
</preview>
```

**想定通りのプレビューが出てきてしまった**。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483769/12cf84c5-1980-fb4c-4e4a-dde496163543.png)



# Tips

## VSCode上だとちっせえよ！

**プレビューはブラウザでも開けます**。
右下に表示されている、青い「Previe Port: 3333」をクリックして、Open in browserを選択します。
http://localhost:3333では、App.vueのプレビューを表示するようです。
`src/components/MyButton.vue`をプレビューしたい場合、
http://localhost:3333/__preview/src/components/MyButton.vue
のように開く必要があります。(2023/02/07時点)

## コンポーネントをimportする話って公式にも書いてなくない？

紹介した最後の例のように、コンポーネントをimportできる話は、実はpreviewのプラグインのgithubにも公式の説明にも載っていません。
この仕様は、プラグインの内部の処理で使われているmarkdonw-itのプラグインの処理に由来していると思われます。

https://github.com/mdit-vue/vite-plugin-vue-markdown

恐らく、このvite-plugin-vue-markdownのドキュメントに書いてあることは大体できるはず。
ただまぁ、補完とかは現状提供されていないので、いずれにしよ大規模なドキュメントとかには向いていないでしょう。~~Storybookさん「呼んだ？」~~
個人開発とか、小規模なプロジェクトでは有用かと思います。

## previewの中のlang="ts"は必要？

A. 多分不要。
無意識に叩いていたけれど、公式の説明でもlang属性は省略されていました。
必須なのは`setup`属性だけだと思われます。

# 参考

↓ 知ったきっかけ ↓

https://zenn.dev/oreo2990/articles/52441ae2e558b5

↓ 公式の紹介 ↓

https://github.com/johnsoncodehk/volar/discussions/1511

↓ プレビューのプラグインのgithub ↓

https://github.com/johnsoncodehk/vite-plugin-vue-component-preview