QuickJS を emscripten で WebAssembly にして、ブラウザ上で動かすのを試した。その覚え書きです。 動機としては、 JavaScript を使った web 上で動く playground をつくりたいと考えているから。 直接 eval するのは怖いので、QuickJS を使う。
参考にしたページ
OS は Ubunut 22.04 を使用。 emcc コマンド等は apt 経由ではインストールしないで、こちらの本家 emscripten の Download and install の通りでsetupしました。
$ emcc --version
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.41 (71634e036d20209a5d81c2b2171e145b44de1e12)
Copyright (C) 2014 the Emscripten authors (see AUTHORS.txt)
This is free and open source software under the MIT license.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
wasm-jsevalをそのまま動かしたかったのですが、なぜか手元環境ではうまくいかなかった。 そこで、そちらを参考にしつつ、 あれこれしたらなんとなく動くようになったので、その記録を残します。
以下は、emscripten のインストールができている前提です。
適当なディレクトリ(このプロジェクト用)を作成して、QuickJS のソースを持ってきます。
$ git clone https://github.com/bellard/quickjs.git
次に必要なファイルを作成します。
$ touch my-quickjs-eval.c
$ touch build.sh
$ touch index.html
wasm に変換する eval 関数を用意した my-quickjs-eval.c の内容は次の通りです。
#include "emscripten.h"
#include <string.h>
#include "quickjs.h"
EMSCRIPTEN_KEEPALIVE
const char* eval(const char* str) {
JSRuntime* runtime = JS_NewRuntime();
JSContext* ctx = JS_NewContext(runtime);
JSValue result = JS_Eval(ctx, str, strlen(str), "<evalScript>", JS_EVAL_TYPE_GLOBAL);
if (JS_IsException(result)) {
JSValue realException = JS_GetException(ctx);
return JS_ToCString(ctx, realException);
}
JSValue json = JS_JSONStringify(ctx, result, JS_UNDEFINED, JS_UNDEFINED);
JS_FreeValue(ctx, result);
return JS_ToCString(ctx, json);
}
emcc コマンドを使って Wasm (my-quickjs-eval.js) を生成するための build.sh スクリプト。
#!/bin/bash
emcc \
-o my-quickjs-eval.js -O3\
-s WASM=1\
-s SINGLE_FILE\
-s EXPORTED_RUNTIME_METHODS='["cwrap"]'\
-I ./quickjs\
./my-quickjs-eval.c ./quickjs/quickjs.c ./quickjs/cutils.c ./quickjs/libregexp.c ./quickjs/libbf.c ./quickjs/libunicode.c\
-DCONFIG_VERSION="\"1.0.0\""
もし SINGLE_FILE オプションを指定すると wasm ファイルは my-quickjs-eval.js に含まれる形になります。 このオプションをはずせば my-quickjs-eval.wasm ファイルが生成できます。
それでは実行して my-quickjs-eval.js を生成。
$ sh build.sh
my-quickjs-eval.js が生成されました。
それでは、my-quickjs-eval.js を使うための index.html を準備します。
<html>
<body>
<h1>Open your console</h1>
<script src="./my-quickjs-eval.js"></script>
<script>
const myCode = `
const add = (a,b)=> { return a+b }
const result = {
total: add(1,2)
}
result
`
Module.onRuntimeInitialized = () => {
const rawEval = Module.cwrap('eval', 'string', ['string'])
const result = rawEval(myCode)
console.log(result)
console.log(JSON.parse(result).total)
}
</script>
</body>
</html>
この段階でのプロジェクトディレクトリを確認します。
.
├── build.sh
├── index.html
├── my-quickjs-eval.c
├── my-quickjs-eval.js
└── quickjs/
それでは web サーバを起動して、WebAssembly 対応のブラウザで index.html にアクセスしてみましょう。
$ python3 -m http.server 8080 --directory .
QuickJSで eval した結果は、console に出力されるので、そちらを確認します。
rawEval した結果は JSON文字列なので、必要なら JSON.parse() して値を取得します。
Node.js でも実行できることがわかった。 ブラウザ上でデバッグするのは負担が大きいので助かる。
$ node --version
v12.22.9
index.js
const myCode = `
const add = (a,b)=> { return a+b }
const result = {
total: add(1,2)
}
result
`
const myQuickjsEval = require('./my-quickjs-eval.js')
//console.log( myQuickjsEval )
myQuickjsEval.onRuntimeInitialized = ()=>{
const rawEval = myQuickjsEval.cwrap('eval', 'string', ['string'])
console.log( rawEval(myCode) )
}
以上です。