🦞

unplugin-vue-markdownを使ったらデプロイ時エラーになった話

に公開

https://github.com/unplugin/unplugin-vue-markdown
個人サイトで、ブログなど更新が続いていく機能については記事をmarkdownで書きたい一方、その中身を書くのを楽にするためにVue.jsの機能を使いたい!という時に使えるのがこのunplugin-vue-markdownというライブラリです。
このライブラリを使ったアプリをVercelで公開した際にビルドが失敗したので、その原因と解決方法を残しておきます。

症状

Expected ">" but found "class" のようなエラーがビルド時に表示される
dev環境ではエラーはない

原因

<script setup lang="ts"> のlang="ts" TypeScriptを使用していること

解決方法

普通の<script setup> にするか、<script setup lang="js">にする

雑記

https://github.com/unplugin/unplugin-vue-markdown/issues/53
このissueに全て書いてありました。
Vue.jsがTypeScriptを本格的にサポートし始めたのは3.3 からであり、Vue.jsの長い歴史の中で言うとつい2,3年前ということになります。このissueも1,2年前のものであり、重大な部類の問題であるにも関わらずまだ解決していない[1]ことから、Vue.jsでTypeScriptを使うにあたっての問題が浮上したなあという形です。

おまけ:unplugin-vue-markdownの活用事例

Zenn上にunplugin-vue-markdownの記事が一件もなかったので、実際どう使っているのかも紹介します。
Umadepthというグローバル版ウマ娘の個人ガイドを開いていて、その中のガイド本編の部分に使っています。ゲーム用語でいうとサポカの画像を出す部分

サポカの型定義
supportCardType.ts
export type CardType = "speed" | "stamina" | "power" | "guts" | "wit" | "pals";
export type CardRarity = "ssr" | "sr" | "any";
const supportCardSpeedSSR = [サポカ名を列挙]
export type SupportCardSpeedSSRProps = {
  type: "speed";
  rarity: "ssr";
  name: SupportCardSpeedSSR;
  width?: number;
  description?: string;
};

export type SupportCardProps =
  | SupportCardSpeedSSRProps
  | SupportCardSpeedSRProps
  | SupportCardStaminaSSRProps
  | SupportCardStaminaSRProps
  | SupportCardPowerSSRProps
  | SupportCardPowerSRProps
  | SupportCardGutsSSRProps
  | SupportCardGutsSRProps
  | SupportCardWitSSRProps
  | SupportCardWitSRProps
  | SupportCardPalsProps
コンポーネント
SupportCard.vue
<script setup lang="ts">
import { ref } from 'vue';
import type { SupportCardProps } from '@/types/supportCardType';

const props = withDefaults(
  defineProps<SupportCardProps>(),
  {
    rarity: "ssr",
    width: 100
  }
);
const supportImagePath = `/images/supports/${props.type}/${props.rarity}/${props.name}.jpg`;
const fallbackImagePath = '/images/supports/Agemasen.jpg';
const currentImagePath = ref(supportImagePath);

const handleImageError = () => {
  currentImagePath.value = fallbackImagePath;
};

</script>
<template>
  <div :style="{ width: `${props.width}px` }">
    <figure class="image">
      <img :src="currentImagePath" :alt="props.name" @error="handleImageError" />
      <figcaption v-if="props.description">{{ props.description }}</figcaption>
    </figure>
  </div>
</template>

mdの一部
### Deck

#### Core
<CardRow> // CardRowは画像を配置するFlexBoxみたいなコンポーネント
  <SupportCard type="wit" rarity="ssr" name="Hoge Hoge" />
  <SupportCard type="wit" rarity="ssr" name="Hoge Hoge" />
  <SupportCard type="wit" rarity="ssr" name="Hoge Hoge" />
  <SupportCard type="speed" rarity="ssr" name="Hoge Hoge" />
  <SupportCard type="speed" rarity="ssr" name="Hoge Hoge" />
</CardRow>

Flexで表示したり、同じサイズの要素としてポンポン配置したい時にコンポーネントとして作った上でmd上に置けるのが便利です。

脚注
  1. 2025/09に最新リリースがあったり、Anthony Fuさんが関わっていることから死んでいるわけではないと思います ↩︎

Discussion