スクリプト言語TypeScriptでWebAssembly――「AssemblyScript」を体験するいろんな言語で試す、WebAssembly入門(2)

第2回では、TypeScriptでWebAssemblyプログラムを開発できるAssemblyScriptを紹介します。差異こそありますがTypeScriptの構文を使ってコードを書けるので、フロントエンドに慣れた開発者にとってはWebAssemblyのための入りやすい選択肢と言えます。

» 2023年01月20日 05時00分 公開

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「いろんな言語で試す、WebAssembly入門」のインデックス

連載:いろんな言語で試す、WebAssembly入門

 本連載のサンプルコードをGitHubで公開しています。こちらからダウンロードしてみてください。


JavaScriptとWebAssembly

 連載第1回では、WebAssemblyの概要を紹介し、さまざまなプログラミング言語の選択肢に触れました。静的な型付け言語であるC++やRustは、WebAssemblyのための開発言語として最適なのですが、学習コストの点で問題があります。できれば、フロントエンド開発でなじみ深いJavaScriptなどの言語を用いて、WebAssemblyアプリケーションを開発したいと思う人も少なくないはずです。

 残念ながら、本稿作成時点において、JavaScriptのコードをWebAssemblyに直接コンパイルする手段は用意されていません。しかし、AltJS(JavaScriptを置き換える言語)であるTypeScriptには、WebAssembly開発のためのツールとしてAssemblyScriptがあります。このAssemblyScriptを用いることで、TypeScriptのコードをWebAssemblyにコンパイルできます。

 TypeScriptはその名の通り「型の付いたJavaScript」です。型におおらかなJavaScriptに対して、型の指定を積極的に行うTypeScriptは、WebAssemblyに適しているのです。近年では、ReactやVueといったJavaScriptフレームワークでも利用可能になり、数あるAltJSの代表格といえます。

 この場合、JavaScriptの利用は従来の用法(モジュールの読み込み、DOMの構築、操作など)の範囲内に限定し、高速な処理が要求される部分でTypeScriptによるWebAssemblyを導入するといった組み合わせが考えられます。

 JavaScriptの習得者にとって、TypeScriptの学習コストはC++やRustほど高くはないと思われるので、有効な選択肢になるでしょう。今回は、このAssemblyScriptを紹介していきます。なお、TypeScriptについては、@ITの記事を参考にしてください。

TypeScriptのTypeあれこれシリーズ

AssemblyScriptとは

 AssemblyScriptは、上記の通り、TypeScriptのコードをWebAssemblyのバイナリにコンパイルするためのツールです。ただし、TypeScriptの構文そのままでコンパイルできるわけではなく、WebAssemblyのためのサブセットという位置付けになります。そういう意味では、TypeScriptの構文を使ったWebAssemblyのための言語がAssemblyScriptとでしょう。

AssemblyScript

図 AssemblyScript

 TypeScriptとAssemblyScriptには、大まかに、以下のような違いがあります。

  • 数値型が細かく追加されている。整数のi32やi64、浮動小数点数のf64など
  • 参照型が細かく追加されている。内部参照のanyrefやArrayへのarrayrefなど
  • 数値の代入に制限が発生する。サイズの大きい型から小さい型への代入はできない。浮動小数点数から整数への代入ができないなど
  • undefinedやanyは使えない
  • イテレータ構文(for〜ofなど)が使えない
  • 例外処理(try〜catch)は使えない
  • instanceofは使えない
  • Union Typeは使えない
  • 基本型はnullableにできない

 このうち数値型の違い、イテレータ構文、例外処理については、後述するコード例で紹介します。

AssemblyScriptを使う準備

 AssemblyScriptを使うために、幾つかのソフトウェアを準備しましょう。エディタおよび実行環境としてVisual Studio Code(以下、VSCode)を、ビルドと実行の環境としてNode.js(バージョン16以上)をそれぞれインストールしておきます。

 VSCodeをインストールしたら、拡張機能「Open in Default Borwser」をインストールしておきます。インストール後には念のため、設定から「Open In Default Browser > Run: Open With Local Http Server」の「ローカルHTTPサーバーで開く」にチェックが入っていることを確認してください。これは、ローカルにあるHTMLファイルなどからWebAssemblyのバイナリファイルを読み込めるようにするための指定です。

AssemblyScriptのインストール

 VSCodeとNode.jsを準備できたら、AssemblyScriptをインストールします。まずは、AssemblyScriptのためのプロジェクトフォルダを作成します。本連載のためのatmarkit_wasmフォルダを適当な場所(ドキュメントフォルダなど)に作成し、その中にさらにassemblyscriptフォルダを作成します。以降、このフォルダを起点に操作します。

% mkdir -p atmarkit_wasm/assemblyscript
% cd atmarkit_wasm/assemblyscript

 フォルダが準備できたら、AssemblyScriptをNode.jsのパッケージマネジャーであるnpmコマンドでインストールします。続けてnpm fundコマンドでインストールされたパッケージを確認し、npm installコマンドを実行して、依存関係にあるパッケージのインストールを済ませておきます。

% npm install -g assemblyscript		グローバルにインストール
added 3 packages, and audited 4 packages in 3s
…略…
found 0 vulnerabilities
% npm fund				確認
assemblyscript
% npm install				依存関係にあるパッケージのインストール
added 3 packages, and audited 4 packages in 2s
…略…

 なお、assemblyscriptパッケージ以外にbinaryen(WebAssemblyのためのツールチェイン)、long(64bit長整数のためのクラス)もインストールされます。これで、AssemblyScriptのインストールは完了です。

テンプレートの作成と実行

 AssemblyScriptには、テンプレートを作成する機能があります。このテンプレートを通じて、AssemblyScriptのファイルやツールについて見ていきましょう。

テンプレートを作成する

 テンプレートは、asinitコマンドで作成します。asinitコマンドを実行すると、途中で本当に作成するか尋ねられるので、「Y」を回答して先に進んでください。

% asinit .			カレントフォルダに作成
…略(実行すべきコマンドや、生成されるファイルの情報が表示される)…
Have a nice day!

 ずらっとメッセージが表示されますが、ほとんどは作成しようとしているフォルダ、作成したフォルダ、続けて実行すべきコマンドなどの情報なので、無視して構いません。主なフォルダとファイルの役割は以下の表1のとおりです。

フォルダ/ファイル 概要
assembly AssemblyScriptのソースコードを格納するフォルダ
assembly/index.ts AssemblyScriptプログラムのエントリポイント
build ビルドされたWebAssemblyファイルを格納するフォルダ
tests テストスクリプトを格納するフォルダ
index.html ビルドされたWebAssemblyプログラムを使うHTMLファイル
表1 asinitコマンドで作成される主なフォルダとファイル

ビルドする

 メッセージにあるnpm run asbuildコマンドでビルドを実行します。asbuildにオプションを与えない場合は、DebugターゲットとReleaseターゲットの双方に対してビルドが実行されます。それぞれのターゲットのオプションは、以下の実行例を参照してください。

% npm run asbuild			ビルドを実行(両ターゲット)
> asbuild
> npm run asbuild:debug && npm run asbuild:release
> asbuild:debug
> asc assembly/index.ts --target debug
> asbuild:release
> asc assembly/index.ts --target release

HTMLから表示させてみる

 上記の表1の通り、テンプレートには、WebAssemblyバイナリを読み込んで実行結果を表示させる、index.htmlファイルが含まれています。Releaseターゲットにてビルド後、VSCodeのエクスプローラーでindex.htmlを右クリックし、表示されるメニューから[ブラウザで開く]を選択してください。既定のWebブラウザで(この場合はGoogle Chrome)、以下のように結果が表示されます。

図 index.htmlファイルをWebブラウザで表示させたところ

 ここで、URL欄に注目してください。「http://localhost:52330/index.html」となっている通り(スキーマは省略されていたり、ポート番号は異なる場合があります)、サーバへのリクエストとなっています。連載第1回で触れたように、.wasmファイルの読み込みはSame Originである必要があるため、ローカルなファイルとしてindex.htmlファイルを開いた場合、.wasmファイルは読み込めません。このため、VSCodeの拡張機能「Open in Default Browser」により、ローカルHTTPサーバからレスポンスを返すようにしたのです。

【補足】Same Origin―同一生成元ポリシーとは

 Same Ogirinである必要があるとは、同一生成元ポリシーによる制約を意味します。簡単にいうと、リソースとそれの利用側のURLの一部(オリジン=スキーム+ホスト+ポート番号)が同一でない場合のアクセスに、Webブラウザが制限をかける仕組みです。上記の場合、オリジン「http://localhost:52330/」がindex.htmlと.wasmファイルで同一なので、Same Originということになります。

 結果は「3」とだけ表示されていますが、index.htmlファイル自身を見てみましょう。

<!DOCTYPE html>
<html lang="en">
<head>
<script type="module">
import { add } from "./build/release.js";	(1)
document.body.innerText = add(1, 2);		(2)
</script>
</head>
<body></body>
</html>
index.html

 重要なのは2行です。(1)は、index.tsファイルのビルドによって生成されるヘルパーであるbuild/release.jsのインポート、そして(2)は、そこでエクスポートされている関数addの呼び出しとDOMの書き換えです。この関数addこそがTypeScriptで記述されているのですが、これについては後述するコード例の節で取り上げます。

 このようにAssemblyScriptでは、TypeScriptファイルから生成される各種のファイルを用いることで、HTMLおよびJavaScriptからも簡単に利用できます。

テストを実行する

 npm testコマンドでコードをテストすることもできます。テストで実行する内容は、testsフォルダにあるスクリプトに記述されています。

% npm test				テストを実行
> test
> node tests
(node:48691) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
ok

 警告(Fetch APIの実装は実験的)が表示されますが、ここでは無視して構いません。最後に「ok」と表示されていれば、テストは成功です。

ビルドによって生成されるファイル

 ビルドによって生成されるファイルにも簡単に触れておきます。npm run buildコマンドでは、DebugターゲットとReleaseターゲットそれぞれに以下のファイルが作成されます。拡張子から分かる通り、それぞれWebAssemblyのバイナリファイル、そのマップファイル、WAT(WAST)ファイル、そしてWebAssemblyのバイナリファイルを読み込むJavaScriptファイルとなります(WASTファイルについては連載第1回を参照してください)。

  • ./build/debug.wasm(./build/release.wasm)
  • ./build/debug.wasm.map(./build/release.wasm.map)
  • ./build/debug.wat(./build/release.wat)
  • ./build/debug.js(./build/release.js)

ascコマンドとwasm-optコマンド、wasm2jsコマンド

 先ほど、asinitコマンドでAssemblyScriptのテンプレートを作成しました。AssemblyScriptのインストールによって利用可能になるコマンドには、他にascコマンドとwasm-optコマンド、wasm2jsコマンドがあります。これらの機能は以下の通りです(個別の利用については割愛します)。

  • ascコマンド……AssemblyScriptファイル(.ts)をコンパイルする
  • wasm-optコマンド……WebAssemblyバイナリ(.wasm)を最適化する
  • wasm2jsコマンド……WebAssemblyバイナリ(.wasm)をCommonJSモジュールに変換する

AssemblyScriptのコード例

 ここからは、エントリポイントのファイルであるindex.tsを手始めに、AssemblyScriptのコード例を幾つか、TypeScriptとの差異を絡めて紹介していきます。

まずはindex.tsを見てみる

 assemblyフォルダにあるindex.tsがエントリポイントとなるファイルです。他にTypeScriptのファイルを作っていく場合、このindex.tsファイルにimport文を記述していくことになります。以下は、index.tsファイルの内容です。

export function add(a: i32, b: i32): i32 {
  return a + b;
}
assembly/index.ts

 先ほどHTMLから呼び出していた関数addの定義で、たった3行からなるスクリプトです。関数addは、32bit符号付き整数(i32型)の引数を2個受け取り、加算した結果を同じく32bit符号付き整数(i32型)で返します。本稿冒頭でも触れたように、i32はAssemblyScript固有のデータ型で、TypeScriptにはないデータ型です。

データ型は細かく指定する

 このように、AssemblyScriptではTypeScriptにおいてnumberやbigintといった型で指定していたところを、i32のようなサイズの明確なデータ型で指定する必要があります。これは、コンパイル時に明確に型を決定する必要があるためです。AssemblyScriptには、主に以下の表2に示すデータ型があります。対応するWebAssemblyとAssemblyScriptのデータ型も並記してあります。

AssemblyScript WebAssembly TypeScript 概要
i32 i32 number 32bit符号付き整数
u32 i32 number 32bit符号なし整数
i64 i64 bigint 64bit符号付き整数
u64 i64 bigint 64bit符号なし整数
isize i32またはi64 numberまたはbigint 32bit符号付き整数(WASM32)または64bit符号付き整数(WASM64)
usize i32またはi64 numberまたはbigint 32bit符号なし整数(WASM32)または64bit符号なし整数(WASM64)
f32 f32 number 32bit浮動小数点数
f64 f64 number 64bit浮動小数点数
bool i32 boolean 真偽値
void void 戻り値のないことを明示
anyref anyref 参照
表2 AssemblyScriptの主なデータ型

 このように、数値型は整数と浮動小数点型に大きく分けられ、さらにデータ長や符号の有無に応じて細かく分けられています。また、真偽値を表すbool型や、参照を表すanyref型がAssemblyScript独自の型として設けられています。参照には、Object型に相当するanyrefの他に、Array型への参照であるarrayref型、string型への参照であるstringref型なども設けられています。

 使用できない型として、undefinedとanyがあります。これらはいずれも、コードの曖昧さに基づくので、WebAssemblyには不適当であると見なされているのです。

 AssemblyScriptのデータ型の詳細は、以下を参照してください。

Types | The AssemblyScript Book

 なお、TypeScriptの型であるnumberとbooleanがエイリアスとして使えます。それぞれ、f64とi32に対応します。ただし、特にnumberがf64型に相当することには要注意です。例えば以下のコードは、number由来でコンパイルエラーとなります。

export function sub(a: i32, b: i32): i32 {
  const result: number = a - b;
  return result;	// f64からi32に変換できないエラー
}
assembly/index.ts
ERROR AS200: Conversion from type 'f64' to 'i32' requires an explicit cast.

 numberがf64型になるので、関数の戻り値の型であるi32にはキャストが必要となるのです。このような問題を避けるため、numberとbooleanは利用こそできますが、非推奨の扱いです。できるだけ、i32のような数値型やbool型を使うようにしてください。

 以下は、number型を使わないか、演算結果を明示的にキャストするよう改めた例です。

const result: i32 = a - b;		// numberを使わずi32を用いる
return i32(result);			// 明示的にキャスト
assembly/index.ts

イテレータは普通のfor文にする

 AssemblyScriptでは、利用頻度の高いfor〜of、for〜inのようなイテレータ構文が使えません。例えば以下のようなコードは、イテレータが実装されていないとしてコンパイルエラーになります。

export function calcSum(): i32 {
  const ary: i32[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  let sum: i32 = 0;
  // イテレータは使えない
  for (let value of ary) {
      sum += value;
  }
  return sum;
}
assembly/index.ts
ERROR AS100: Not implemented: Iterators

 コンパイルエラーを解消するには、オーソドックスなfor文で記述し直す必要があります。これは、for〜of文も同じです。

// オーソドックスなfor文を使う必要あり
for (let i: i32 = 0; i < ary.length; i++) {
  sum += ary[i];
}
assembly/index.ts

例外処理は条件判定に置き換える

 AssemblyScriptでは、try〜catch構文のような例外処理が実装されていません。例えば、以下のコードは0による除算を例外としてキャッチしようとしますが、例外処理が実装されていないというコンパイルエラーとなります。

export function divide(a: i32, b: i32): i32 {
  let result: i32 = 0;
  try {
    result = a / b;
  } catch(e) {
    result = 0;
  }
  return result;
}
assembly/index.ts
ERROR AS100: Not implemented: Exceptions

 コンパイルエラーを解消するには、if文などで事前に条件判定するように記述し直す必要があります。tryブロックの中で発生する可能性のある例外を、事前にチェックするようにします。

// 条件判定する記述に変更する必要あり
if (b == 0) {
  result = 0;
} else {
  result = a / b;
}
assembly/index.ts

まとめ

 今回は、TypeScriptでWebAssemblyプログラムを開発できるAssemblyScriptを紹介しました。差異こそありますが、TypeScriptの構文を使ってコードを書けて、利用側ではテンプレートで生成されたファイルを取り込むだけというシンプルさを理解いただけたのではないかと思います。

 次回は、C#と.NETでWebAssemblyプログラムを開発できるBlazor WebAssemblyを紹介します。

筆者紹介

WINGSプロジェクト

有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティー(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手掛ける。2021年10月時点での登録メンバーは55人で、現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。

サーバーサイド技術の学び舎 - WINGS(https://wings.msn.to/
RSS(https://wings.msn.to/contents/rss.php
Twitter: @yyamada(https://twitter.com/yyamada
Facebook(https://www.facebook.com/WINGSProject


Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。