Home About Contact
WebAssembly , JavaScript , QuickJS

QuickJS を ブラウザ上で使う(その2)

以前に QuickJS を WebAssembly に変換して ブラウザ上で使う というポストを書いたのですが、 その後、もっと良いガイド Running self-hosted QuickJS in a browser があったので、こちらの方法でやり直します。

この方法であれば、 https://github.com/justjake/quickjs-emscripten を使うことで、emcc コマンドが不要。 自分で emscripten をインストールしなくてよいので助かる。

環境

$ node --version
v18.17.1
$ npm --version
10.8.3

Node のプロジェクトを作成 index.html で QuickJS オブジェクトを取得して使う

これは Running self-hosted QuickJS in a browser の手順通り実行しただけです。

$ mkdir qjs
$ cd qjs
$ npm install quickjs-emscripten webpack webpack-cli

あとは・・・全く同じ手順なので、省略します。

package.json に記載された各ライブラリのバージョン:

{
  "dependencies": {
    "quickjs-emscripten": "^0.31.0",
    "webpack": "^5.95.0",
    "webpack-cli": "^5.1.4"
  }
}

npx webpack すると dist/ 以下に ライブラリ生成されます。

ls -l -h するとこんな感じです:

 507K  0393610d054d74f0601f.wasm
  241  1.quickjs.js
  31K  226.quickjs.js
 2.1K  318.quickjs.js
  13K  672.quickjs.js
 6.1K  964.quickjs.js
 4.0K  quickjs.js

この Node.js プロジェクトのルートに index.html を作成します。

<html>
<head>
<script src="dist/quickjs.js"></script>
</head>
<body>
Hello, QuickJS!
<script>
getQuickJS().then((QuickJS)=>{
    const vm = QuickJS.newContext();

    const world = vm.newString("World");
    vm.setProp(vm.global, "name", world);
    world.dispose();

    const result = vm.evalCode(`"Hello, " + name + "!"`);

    if( result.error ){
        result.error.dispose();
    } else {
        console.log(vm.dump(result.value));
        result.value.dispose();
    }

    vm.dispose();
});
</script>
</body>
</html>

そして web server を起動 python3 -m http.server 8000 ブラウザで http://localhost:8000/ を開きます。

ブラウザのコンソールを見ると Hello, World! と表示されているはずです。

これでこの QuickJS のコンテキストに name という文字列の変数を用意することができたのはわかりました。

文字列ではなくオブジェクトを存在させる

こちらのサンプルを参考にしたらなんとかできました。 https://github.com/suchipi/js-sandbox-demo/blob/4f631c905968153f9b5c8351ef03036d26ad1b28/src/shortlived-vm.ts

const vm = QuickJS.newContext();

const myobj = vm.newObject();
const world = vm.newString("World");

vm.setProp(myobj, "name", world);
vm.setProp(vm.global, "myobj", myobj);

myobj.dispose();
world.dispose();

const result = vm.evalCode(`"Hello, " + myobj.name + "!"`);

実行すると ブラウザのコンソールに Hello, World! と表示されます。

関数を存在させる

ここ https://github.com/justjake/quickjs-emscripten/blob/main/doc/README.md の Exposing APIs のセクションにやり方が書いてあります。

const greetingF = vm.newFunction("greeting", (...args)=> {
    const nativeArgs = args.map(vm.dump)
    if( nativeArgs.length > 0 ){
        return vm.newString("Hello, " + nativeArgs[0] + "!");
    } else {
        return vm.newString("Hello!");
    }
});
vm.setProp(vm.global, "greeting", greetingF);
greetingF.dispose();

const result = vm.evalCode(`greeting("Pikachu")`);

実行すると ブラウザのコンソールに Hello, Pickachu! と表示されます。

まとめ

完成した index.html を載せます。

<html>
<head>
<script src="dist/quickjs.js"></script>
</head>
<body>
Hello, QuickJS!
<script>
getQuickJS().then((QuickJS)=>{
    const vm = QuickJS.newContext();

    /*
    const world = vm.newString("World");
    vm.setProp(vm.global, "name", world);
    world.dispose();

    const result = vm.evalCode(`"Hello, " + name + "!"`);
    */

    /*
    const myobj = vm.newObject();
    const world = vm.newString("World");

    vm.setProp(myobj, "name", world);
    vm.setProp(vm.global, "myobj", myobj);

    myobj.dispose();
    world.dispose();

    const result = vm.evalCode(`"Hello, " + myobj.name + "!"`);
    */

    const greetingF = vm.newFunction("greeting", (...args)=> {
        const nativeArgs = args.map(vm.dump)
        if( nativeArgs.length > 0 ){
            return vm.newString("Hello, " + nativeArgs[0] + "!");
        } else {
            return vm.newString("Hello!");
        }
    });
    vm.setProp(vm.global, "greeting", greetingF);
    greetingF.dispose();

    const result = vm.evalCode(`greeting("Pikachu")`);

    if( result.error ){
        result.error.dispose();
    } else {
        console.log(vm.dump(result.value));
        result.value.dispose();
    }
    vm.dispose();
});
</script>

</body>
</html>

以上です。