Skip to content
Copied!
published on 2026-05-18

3. TextRenderer

WebAssembly 関数を呼び,返された文字列をそのまま表示するコンポーネント TextRenderer を作成しました。PPMRenderer が PPM 画像を <canvas> に描画するのに対し,TextRenderer はモンテカルロ積分の収束レポートなど,テキスト形式の計算結果を <pre> タグで表示するために使います。

登録方法は PPMRenderer と同様です。.vitepress/theme/index.js に追記します。

.vitepress
└── theme
    ├── components
    │   ├── PPMRenderer.vue
    │   └── TextRenderer.vue
    └── index.js
js
import DefaultTheme from 'vitepress/theme'
import PPMRenderer from './components/PPMRenderer.vue'
import TextRenderer from './components/TextRenderer.vue'

export default {
  extends: DefaultTheme,
  enhanceApp({ app }) {
    app.component('PPMRenderer', PPMRenderer)
    app.component('TextRenderer', TextRenderer)
  }
}

TextRenderer は,指定した wasm-module の関数 wasm-function を呼びます。この関数は文字列を返すことが期待されています。返り値を <pre> タグ内にそのまま表示します。

TextRenderer のソースコードを以下に示します。

TextRenderer.vue
vue
<template>
  <div class="wasm-text-renderer">
    <div v-if="!loaded && !error" class="loading">
      Running {{ title }}...
    </div>

    <div v-else-if="error" class="error">
      {{ error }}
    </div>

    <div v-else class="result">
      <p v-if="description" class="description">{{ description }}</p>
      <pre class="text-output">{{ output }}</pre>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { withBase } from 'vitepress'

const props = defineProps({
  title: { type: String, required: true },
  wasmFunction: { type: String, required: true },
  wasmModule: { type: String, required: true },
  description: { type: String, default: '' },
})

const loaded = ref(false)
const error = ref(null)
const output = ref('')

onMounted(async () => {
  try {
    const fullPath = withBase(props.wasmModule)
    const response = await fetch(fullPath)
    if (!response.ok) throw new Error(`HTTP ${response.status} fetching ${fullPath}`)
    const jsText = await response.text()
    const blob = new Blob([jsText], { type: 'text/javascript' })
    const blobUrl = URL.createObjectURL(blob)
    let wasm
    try {
      wasm = await import(/* @vite-ignore */ blobUrl)
    } finally {
      URL.revokeObjectURL(blobUrl)
    }

    const wasmBinaryUrl = new URL(fullPath.replace(/\.js$/, '_bg.wasm'), window.location.origin)
    await wasm.default(wasmBinaryUrl)

    const fn = wasm[props.wasmFunction]
    if (typeof fn !== 'function') {
      throw new Error(`WASM function '${props.wasmFunction}' is not available`)
    }

    output.value = String(fn())
    loaded.value = true
  } catch (err) {
    error.value = `Failed to run ${props.title}: ${err.message}`
    console.error('WASM text rendering error:', err)
  }
})
</script>

<style scoped>
.wasm-text-renderer {
  margin: 1.5rem 0;
  padding: 1rem;
  border: 1px solid var(--vp-c-border);
  border-radius: 8px;
  background: var(--vp-c-bg-soft);
}

.loading,
.error {
  padding: 1rem;
}

.error {
  background: var(--vp-c-danger-soft);
  color: var(--vp-c-danger-1);
  border-radius: 6px;
}

.description {
  margin-bottom: 0.75rem;
  color: var(--vp-c-text-2);
}

.text-output {
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
  overflow-x: auto;
  background: var(--vp-c-bg);
  border: 1px solid var(--vp-c-divider);
  border-radius: 6px;
  padding: 0.75rem;
}
</style>

一部分ずつ解説していきます。

テンプレート

まず,Vue テンプレートです。div.wasm-text-renderer ブロックに,処理中・エラー・結果の 3 状態を v-if / v-else-if / v-else で切り替えます。処理が完了するまでは Running ... を表示します。エラーが出た場合はその内容を表示します。

description は省略可能です。v-if="description" で値が存在するときのみ <p> を表示します。出力テキストは <pre class="text-output"> 内に表示されます。white-space: pre-wrap を指定しているため,改行やスペースがそのまま保持されます。

vue
<template>
  <div class="wasm-text-renderer">
    <div v-if="!loaded && !error" class="loading">
      Running {{ title }}...
    </div>

    <div v-else-if="error" class="error">
      {{ error }}
    </div>

    <div v-else class="result">
      <p v-if="description" class="description">{{ description }}</p>
      <pre class="text-output">{{ output }}</pre>
    </div>
  </div>
</template>

props

このテンプレートが受け取る情報は 4 つです。処理名,WASM 関数名,WASM モジュールパス,そして省略可能な説明文です。

js
const props = defineProps({
  title: { type: String, required: true },
  wasmFunction: { type: String, required: true },
  wasmModule: { type: String, required: true },
  description: { type: String, default: '' },
})

PPMRendererimageName に対応するのが title で,処理中メッセージ(Running ...)に使われます。descriptionPPMRenderer と異なり省略可能(デフォルト '')です。

テンプレートの呼び出し側(Markdown ファイル)では,以下のように記述します。

html
<ClientOnly>
  <TextRenderer
    title="Monte Carlo PI Report"
    wasm-module="/wasm/raytracing_demos/raytracing_demos.js"
    wasm-function="report_monte_carlo_pi"
    description="r302-monte-carlo の出力。収束チェックポイントと層化サンプリング比較を確認できます。"
  />
</ClientOnly>

マウント後コールバック

このコンポーネントがマウントされたときに呼ばれるコードです。

1. WASMモジュールの読み込み

PPMRenderer と同じ fetch + Blob URL パターンで WASM を読み込みます。詳細は前ページの「WASMモジュールの読み込み」を参照してください。

js
const fullPath = withBase(props.wasmModule)
const response = await fetch(fullPath)
const jsText = await response.text()
const blob = new Blob([jsText], { type: 'text/javascript' })
const blobUrl = URL.createObjectURL(blob)
let wasm
try {
  wasm = await import(/* @vite-ignore */ blobUrl)
} finally {
  URL.revokeObjectURL(blobUrl)
}
const wasmBinaryUrl = new URL(
  fullPath.replace(/\.js$/, '_bg.wasm'),
  window.location.origin
)
await wasm.default(wasmBinaryUrl)

2. WASM関数の呼び出しと表示

props で指定された関数名で関数オブジェクトを取り出し,typeof で関数であることを確認してから呼び出します。返り値を String() で文字列に変換して output.value に代入し,最後に loaded.value = true にします。

js
const fn = wasm[props.wasmFunction]
if (typeof fn !== 'function') {
  throw new Error(`WASM function '${props.wasmFunction}' is not available`)
}
output.value = String(fn())
loaded.value = true

PPMRenderer と異なり,canvas のサイズ調整や nextTick() は不要です。output.value に代入した時点で Vue のリアクティビティが <pre> の内容を更新します。