Skip to content

ブラウザでワークブックを開く ​

ユーザーが .xlsx をアップロードし、アプリがブラウザ内で再計算し、値または変更済みワークブックを返すパターンです。ファイルをサーバーに送らずに完結します。

ブラウザホスティングの要件

既定の @libraz/formulon WASM 入口は単一スレッドで動作し、オリジン間分離を必要としません。@libraz/formulon/threads 入口を使う場合は COOP / COEP ヘッダーが必要です。バンドラ設定 と トラブルシュート を参照してください。

用語: ArrayBuffer / Uint8Array

ブラウザでは、ファイルのバイト列を ArrayBuffer で受け取り、Uint8Array として読み出せます。File.arrayBuffer() の戻り値を new Uint8Array(buffer) で包み、loadBytes() に渡します。読み込みが終わるまで、そのバッファを別のワーカーへ転送しないでください。

流れ ​

01ユーザーが .xlsx を選択ファイルはブラウザの外へ出ない02ArrayBuffer を読み込むUint8Array に包んで渡す03Module.Workbook.loadBytes使う前に wb.isValid() を確認04入力値を変更入力セルへ書き込む05recalc()読む前に status.ok を確認06値を読む / 保存セルの値、または save() のバイト列01ユーザーが .xlsx を選択ファイルはブラウザの外へ出ない02ArrayBuffer を読み込むUint8Array に包んで渡す03Module.Workbook.loadBytes使う前に wb.isValid() を確認04入力値を変更入力セルへ書き込む05recalc()読む前に status.ok を確認06値を読む / 保存セルの値、または save() のバイト列
ファイルはブラウザ内で再計算されるため、サーバーへアップロードされません。

最小実装 ​

ts
import createFormulon from '@libraz/formulon'

const Module = await createFormulon()
const check = (status: { ok: boolean; message: string; context: string }) => {
  if (!status.ok) throw new Error(`${status.message}: ${status.context}`)
}

export async function recalcUpload(file: File) {
  const bytes = new Uint8Array(await file.arrayBuffer())
  const wb = Module.Workbook.loadBytes(bytes)

  try {
    if (!wb.isValid()) {
      throw new Error(Module.lastErrorMessage())
    }

    check(wb.recalc())
    const cell = wb.getValue(0, 0, 0)
    if (!cell.status.ok) {
      throw new Error(cell.status.message)
    }
    const saved = wb.save()

    if (!saved.status.ok || saved.bytes === null) {
      throw new Error(saved.status.message)
    }

    return { cell, bytes: saved.bytes }
  } finally {
    wb.delete()
  }
}

try / finally の形が重要です。ヘルパーやアプリケーションが再計算中に例外を送出しても、wb.delete() が WASM ヒープを解放します。

下のパネルでは、この関数にファイル選択と編集用のグリッドを組み合わせています。ワークブックを選ぶと File.arrayBuffer() で読み取ったバイト列を loadBytes() に渡し、再計算後の結果を埋め込みの formulon-cell グリッドに表示して、保存したバイト列をダウンロードできます。ワークブックとして読めないファイルは isValid() で止まり、lastErrorMessage() の内容が表示されます。後述のエラー表の 1 行目にあたる挙動です。

xlsx を読み込んで再計算する

ファイルはどこにもアップロードされません。読み込み・再計算・書き出しはすべてこのページの WASM エンジンが行い、バイト列がブラウザの外に出ることはありません。

本物の Formulon エンジン (WASM) がこのページ内で動作します。データは送信されません。

メインスレッドの外でエンジンを動かす ​

大規模ワークブックは再計算中に UI を止めがちです。専用ワーカーにエンジンを切り出します。

ts
// worker.ts
import createFormulon from '@libraz/formulon'

const Module = await createFormulon()

self.onmessage = async (event) => {
  const bytes = new Uint8Array(event.data)
  const wb = Module.Workbook.loadBytes(bytes)
  try {
    if (!wb.isValid()) {
      throw new Error(Module.lastErrorMessage())
    }
    const recalculated = wb.recalc()
    if (!recalculated.ok) throw new Error(recalculated.message)
    const saved = wb.save()
    if (!saved.status.ok || saved.bytes === null) {
      self.postMessage({ ok: false, message: saved.status.message })
      return
    }
    self.postMessage({ ok: true, bytes: saved.bytes }, [saved.bytes.buffer])
  } catch (e) {
    self.postMessage({ ok: false, message: (e as Error).message })
  } finally {
    wb.delete()
  }
}
ts
// main.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' })
worker.onmessage = (event) => updateUi(event.data)

const buffer = await file.arrayBuffer()
worker.postMessage(buffer, [buffer])

転送リストに載せたバッファは、コピーされずに受信側へ移ります。送信側のバッファは切り離され、以後は使えません。バッファは 1 回だけ読み込み、同じ参照を送信データと転送リストの両方に渡してください。file.arrayBuffer() をもう一度呼ぶと別のバッファができ、転送リストに載っていない方はコピーされます。

01ArrayBuffer を読み込むメインスレッドで 1 回だけ02ワーカーへ転送postMessage(buffer,[buffer])03再計算ワーカーがバッファを所有04結果を転送し返すpostMessage(bytes,[bytes.buffer])05結果を受け取るメインスレッドが出力を所有01ArrayBuffer を読み込むメインスレッドで 1 回だけ02ワーカーへ転送postMessage(buffer, [buffer])03再計算ワーカーがバッファを所有04結果を転送し返すpostMessage(bytes, [bytes.buffer])05結果を受け取るメインスレッドが出力を所有
転送したバッファはコピーされず移動するため、送り側では以降使えません。

エラーの分岐 ​

失敗検出箇所対応
.xlsx として不正wb.isValid() === false、lastErrorMessage()「対応していない Excel ファイルです」と表示
セルの Excel エラーcell.status.ok === true かつ cell.value.kind === ValueKind.ErrorModule.errorDisplayName(cell.value.errorCode) で #DIV/0! などの表示文字列に変換して表示。アップロード自体は成功扱い
保存失敗saved.status.ok === falseメッセージを出し、元のバイト列を保持
簡易エンジンが有効(formulon-cell の WorkbookHandle を preferStub: true で使った場合)isUsingStub() が true計算機能が無効である旨をユーザーに通知

UX チェックリスト ​

  • 読み込み前にファイルタイプ / サイズをクライアント側で検証する
  • 未対応関数の失敗はアップロード失敗ではなく互換性問題として見せる
  • 再計算後の出力を永続化できるまで、元の File / ArrayBuffer を保持する(保存に失敗してもアップロードを失わないため)
  • UI の応答性が必要ならワーカーで計算する
  • @libraz/formulon/threads を読み込む場合は COOP / COEP ヘッダーを配信し、SharedArrayBuffer が使えないブラウザへの対応を用意する

適合性の確認 ​

データをブラウザ内に留めたい、プライバシーやオフライン挙動を重視する場面に適しています。大規模ワークブックを扱う場合やサーバー側でまとめて計算したい場合は Native Node / Python のサーバー側経路を検討してください。

次に読むもの ​