3. TextRenderer
WebAssembly 関数を呼び,返された文字列をそのまま表示するコンポーネント TextRenderer を作成しました。PPMRenderer が PPM 画像を <canvas> に描画するのに対し,TextRenderer はモンテカルロ積分の収束レポートなど,テキスト形式の計算結果を <pre> タグで表示するために使います。
登録方法は PPMRenderer と同様です。.vitepress/theme/index.js に追記します。
.vitepress
└── theme
├── components
│ ├── PPMRenderer.vue
│ └── TextRenderer.vue
└── index.jsimport 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
<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 を指定しているため,改行やスペースがそのまま保持されます。
<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 モジュールパス,そして省略可能な説明文です。
const props = defineProps({
title: { type: String, required: true },
wasmFunction: { type: String, required: true },
wasmModule: { type: String, required: true },
description: { type: String, default: '' },
})PPMRenderer の imageName に対応するのが title で,処理中メッセージ(Running ...)に使われます。description は PPMRenderer と異なり省略可能(デフォルト '')です。
テンプレートの呼び出し側(Markdown ファイル)では,以下のように記述します。
<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モジュールの読み込み」を参照してください。
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 にします。
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 = truePPMRenderer と異なり,canvas のサイズ調整や nextTick() は不要です。output.value に代入した時点で Vue のリアクティビティが <pre> の内容を更新します。