目次
- この記事の目的
- babel, webpackの導入
- JavaScriptのソースコードを用意する
- サンプルを動かすHTMLを準備する
- wasmに変換するソースコードを準備する
- Utilsクラスでwasm読み込みを実装する
- 動作確認用のHTMLを準備する
- まとめ
1. この記事の目的
この記事ではbabael, webpackと一緒にwasmを使う方法を紹介します。
babel, webpackを使って、wasmを参照するUtilsクラスを実装します。
babel, webpackの説明は他にもたくさん情報があるので最低限にします、ご了承ください。
この記事のソースコードをGitHubに上げました。 github.com
2. babel, webpackの導入
npm initしてからyarnでbabel, webpackを導入します。
yarn add --dev webpack babel-core babel-loader babel-preset-es2015 babel-preset-es2016
scriptsの項目は自分で追記して、結局package.jsonは下記のようになりました。
{ "name": "wasm-babel-webpack", ... "scripts": { "webpack": "webpack" }, ... "devDependencies": { "babel-core": "^6.24.1", "babel-loader": "^7.0.0", "babel-preset-es2015": "^6.24.1", "babel-preset-es2016": "^6.24.1", "webpack": "^2.5.1", "webpack-dev-server": "^2.4.5" } }
webpackの設定(webpack.config.js)は下記のようにしました。babelの設定もここに書きます。
module.exports = { context: __dirname + '/src', entry: { 'entry': './entry' }, output: { path: __dirname + '/dist', filename: 'bundle.js', library: 'sample' }, module: { loaders: [ { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader", query:{ presets: ['es2015', 'es2016'] } } ] } };
この設定ファイルについて少し説明します。
src/entry.jsをエントリーポイントにしてファイルの依存性を解決していきます。
複数のファイルを繋げる直前に、それぞれのファイルにbabelで変換をかけるイメージです。
最終的にes5記法に変換されて1つのファイルにまとまった成果物がdist/bundle.jsに保存されます。
3. JavaScriptのソースコードを用意する
srcディレクトリに2つのソースコードを用意します。
まずはwasmと繋げるUtilsクラスをWasmUtils.jsに実装します。
wasmとの接続は後回しにして、最低限の構成で用意します。
export default class WasmUtils { static initialize() { console.log('initialize'); } }
次にentry.jsは、webpack.config.jsにもある通りエントリーポイントです。
今回はクラスをWasmUtilsしか用意しないため、あまり意味がない様に感じるかもしれませんが、WasmUtilsをimportしてその名前でexportしています。
export { default as WasmUtils } from './WasmUtils';
この状態でnpm run webpackを実行することで成果物がdist/bundle.jsに保存されます。
4. サンプルを動かすHTMLを準備する
先ほどできたdist/bundle.jsの動作を確認するHTMLを準備します。
WasmUtilsのinitialize関数を呼び出してみます。
webpack.config.jsにmodule.exports.output.library: “sample"と記述があります。
なのでHTMLからはsample.WasmUtils.initialize()とすることで呼出せます。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title>wasm babel webpack</title> </head> <body> <script src="../dist/bundle.js"></script> <script> sample.WasmUtils.initialize(); </script> </body> </html>
http-serverを実行してブラウザで確認すると、コンソールにinitializeと出力されることが確認できました。
5. wasmに変換するソースコードを準備する
今回も2つの整数の和を計算するため、wasm/add.cppを下記の通り保存します。
extern "C" { int add(int a, int b) { return a + b; } }
次にadd.cppからwasmファイルを生成するシェルスクリプト(wasm/cpp2wasm.sh)を書きます。
ここで、あとで他のファイルから参照できるように、自動生成されるjsファイルの末尾にexport default Module;を追加します。
自動生成されたjsファイルについては少しこちらで説明しています。
emcc add.cpp \ -s WASM=1 \ -s "MODULARIZE=1" \ -s "EXPORTED_FUNCTIONS=['_add']" \ -s "DEMANGLE_SUPPORT=1" \ -o add.js && \ echo 'export default Module;' >> add.js
cpp2wasm.shをDockerコンテナ内で実行するシェルスクリプト(wasm/compile.sh)を準備します。
docker run --rm -t -v $(pwd):/src gifnksm/emscripten-incoming sh cpp2wasm.sh
この状態で、wasmディレクトリでcompile.shを実行するとadd.js, add.wasmが生成されます。
add.jsの最終行にexport default Module;の記述があることを確認してください。
6. Utilsクラスでwasm読み込みを実装する
ここからはいつも通り、add.js, add.wasmの読み込みを行います。
add.jsの読み込みは、これまではHTMLのscriptタグで行っていましたが、今回はwebpackのimportで行います。
add.wasmの読み込みはこれまで通りfetchで行います。
wasmの読み込みが完了するまでは、このUtilsクラスは使えないので、読み込み完了フラグで制御します。
実際に実装したWasmUtilsクラスが下記の通りになります。
import Module from '../wasm/add'; export default class WasmUtils { static initialize() { this.isInitialized = false; fetch('../wasm/add.wasm') .then(response => response.arrayBuffer()) .then(buffer => new Uint8Array(buffer)) .then(binary => { let moduleArgs = { wasmBinary: binary, onRuntimeInitialized: function() { WasmUtils.readFunctions(); } }; this.module = Module(moduleArgs); }); } static readFunctions() { this.functions = {}; this.functions.add = this.module.cwrap('add', 'number', ['number', 'number']); this.isInitialized = true; } static add(a, b) { if (!(this.isInitialized && this.functions && this.functions.add)) { return null; } return this.functions.add(a, b); } }
7. 動作確認用のHTMLを準備する
WasmUtils.add()の動作を確認するHTMLを下記の通り準備します。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title>wasm babel webpack</title> </head> <body> <script src="../dist/bundle.js"></script> <script> sample.WasmUtils.initialize(); console.log('0: 2 + 3 = ' + sample.WasmUtils.add(2, 3)); setTimeout(function () { console.log('1: 2 + 3 = ' + sample.WasmUtils.add(2, 3)); }, 1000); </script> </body> </html>
WasmUtils.add(2, 3)を2回読んでいて、2回目は1秒間遅延させて呼び出しています。
私の環境ではwasmの読み込みに約200msかかっています。
1回目の呼び出し時は、まだwasmの読み込みが完了していないためnullをかえします。
2回目は5がコンソールに出力されました。
8. まとめ
今回はbabel, webpackと一緒にwasmを使う方法を紹介しました。
babel, webpackの説明を最小限にしたり、あちこち端折ったものの、かなり長い記事になってしまいました。
なにか不足している部分がありましたら、GitHubのソースコードをご覧いただくか、コメント頂けると幸いです。