@ledsun blog

無味の味は佳境に入らざればすなわち知れず

JS::Object.awaitが返らないときがある?

JS::Object.await ふたたび - @ledsun blog で、JS::Object.awaitの使い方がわかりました。 そこでfetchを使ってrequire_relativeを実装します。

import { RubyScript } from "./RubyScript";
import { EvaluatedScriptStack } from "./EvaluatedScriptStack";

class RubyWasm {
  private _loadedPathes: Set<string> = new Set();
  private _stack: EvaluatedScriptStack;

  constructor(stack: EvaluatedScriptStack) {
    this._stack = stack;
  }

  async requireRelativeURL(relative_feature): Promise<boolean> {
    const filename = relative_feature.endsWith(".rb")
      ? relative_feature
      : `${relative_feature}.rb`;
    const url = new URL(filename, this._stack.currentURL);

    // Prevents multiple loading.
    if (this._loadedPathes.has(url.pathname)) {
      return false;
    }

    const response = await fetch(url);
    if (!response.ok) {
      return false;
    }

    const text = await response.text();
    await this._stack.eval(new RubyScript(url, text));

    return true;
  }
}

export function defineRubyWasm(stack: EvaluatedScriptStack): void {
  const loadedPathes: Set<string> = new Set();

  const global = window as any;
  global.rubyWasm = new RubyWasm(stack);
}

fetchして結果を待ってawait this._stack.eval(new RubyScript(url, text))します。 stackは次のようなクラスです。

import { RubyScript } from "./RubyScript";

// To achieve require_relative, we need to resolve relative paths in Ruby scripts.
// Remember the URL of the running Ruby script.
export class EvaluatedScriptStack {
  private _vm;
  // Stores the URL of the running Ruby script to get the relative path from within the Ruby script.
  private _stack: Array<RubyScript>;

  constructor(vm) {
    this._vm = vm;
    this._stack = [];
  }

  async eval(script: RubyScript): Promise<void> {
    this._stack.push(script);
    await this._vm.evalAsync(script.ScriptBody);
    this._stack.pop();
  }

  get currentURL(): URL {
    return this._stack.at(-1).URL;
  }
}

実行中のRubyスクリプトのURLを保持しつつawait this._vm.evalAsync(script.ScriptBody)します。 そして、requireRelativeURLrequrie_relativeを置換したrequire_relative_urlメソッドで呼び出します。

import { RubyVM } from "..";

export function define_require_relative_url(vm: RubyVM) {
  const script = `
  require "js"
  module Kernel
    def require_relative_url(relative_feature)
      JS.global[:rubyWasm].requireRelativeURL(relative_feature).await
    end
  end
  `;
  vm.eval(script);
}

これを次のようなHTMLを実行します。

<html>
  <script src="../../ruby-head-wasm-wasi/dist/browser.script.iife.js"></script>
  <script type="text/ruby">
    require_relative "a"
    puts "Hello, world!"
  </script>
</html>

aの読込に成功します。

a.rbを読み込んだあとにHello, world!が表示されます。 RubyJavaScriptのPromiseを待てています。 つぎに読み込むファイルを増やします。

<html>
  <script src="../../ruby-head-wasm-wasi/dist/browser.script.iife.js"></script>
  <script type="text/ruby">
    require_relative "a"
    require_relative "b"
    puts "Hello, world!"
  </script>
</html>

aとbの読込に成功します。

つぎに、a.rbを経由してb.rbを読み込んでみます。

require_relative "b"

p "a loaded!"

bの読込に成功します。が、そこで止まります。

a.rbを読み込んだあとにHello, world!が表示されてほしいです。 this._vm.evalAsync(script.ScriptBody)の中でthis._vm.evalAsync(script.ScriptBody)するのがよくないのでしょうか?