@ledsun blog

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

wandsをブラウザで実行する環境を少し便利にする

wandsをブラウザで実行する環境を作る - @ledsun blog の続きです。 前回 wands gem をブラウザで読み込む環境を作りました。 前回の書き方では、RubyスクリプトJavaScriptの中に書いてます。 エディタの支援が受けられず少し不便です。

ruby.wasmから「Rubyスクリプトをscriptタグから読み込む機能」を持ってきます。 https://github.com/ruby/ruby.wasm/blob/8be5074c626691d08ccc994a6f683246db51f3c3/packages/npm-packages/ruby-wasm-wasi/src/browser.script.ts#L41 にあります。 この機能は私が作ったので、どこにあるのかも知っています。 この関数をもとに「Rubyスクリプトをscriptタグから読み込むJavaScript」を作ります。

// Copy from https://github.com/ruby/ruby.wasm/blob/8be5074c626691d08ccc994a6f683246db51f3c3/packages/npm-packages/ruby-wasm-wasi/src/browser.script.ts#L41
const mainWithRubyVM = async (vm) => {
  vm.printVersion();

  globalThis.rubyVM = vm;

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", () =>
      runRubyScriptsInHtml(vm)
    );
  } else {
    runRubyScriptsInHtml(vm);
  }
};

const runRubyScriptsInHtml = async (vm) => {
  const tags = document.querySelectorAll('script[type="text/ruby"]');

  const promisingRubyScripts = Array.from(tags).map((tag) =>
    loadScriptAsync(tag)
  );

  for await (const script of promisingRubyScripts) {
    if (script) {
      const { scriptContent, evalStyle } = script;
      switch (evalStyle) {
        case "async":
          vm.evalAsync(scriptContent);
          break;
        case "sync":
          vm.eval(scriptContent);
          break;
      }
    }
  }
};

const deriveEvalStyle = (tag) => {
  const rawEvalStyle = tag.getAttribute("data-eval") || "sync";
  if (rawEvalStyle !== "async" && rawEvalStyle !== "sync") {
    console.warn(
      `data-eval attribute of script tag must be "async" or "sync". ${rawEvalStyle} is ignored and "sync" is used instead.`
    );
    return "sync";
  }
  return rawEvalStyle;
};

const loadScriptAsync = async (tag) => {
  const evalStyle = deriveEvalStyle(tag);

  if (tag.hasAttribute("src")) {
    const url = tag.getAttribute("src");
    const response = await fetch(url);

    if (response.ok) {
      return { scriptContent: await response.text(), evalStyle };
    }

    return null;
  }

  return { scriptContent: tag.innerHTML, evalStyle };
};

import { DefaultRubyVM } from "https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.7.1/dist/browser/+esm";
const response = await fetch("/dist/ruby.wasm");
const module = await WebAssembly.compileStreaming(response);
const { vm } = await DefaultRubyVM(module);

await mainWithRubyVM(vm);

実行したいRubyスクリプトもファイルにします。

require "wands"

それぞれを読み込むHTMLファイルです。

<html>

<body>
  <script type="module" src="dist/load_ruby_script.js"></script>
  <script type="text/ruby" data-eval="sync" src="dist/app.rb"></script>
</body>

</html>

これで前回と同じようにwandsを読み込んでエラーが起きるようになりました。

vm.js:739  Uncaught (in promise) J: <internal:/usr/local/lib/ruby/3.3.0/rubygems/core_ext/kernel_require.rb>:136:in `require': cannot load such file -- socket (LoadError)
<internal:/usr/local/lib/ruby/3.3.0/rubygems/core_ext/kernel_require.rb>:136:in `require'
/bundle/gems/wands-0.6.1/lib/wands/web_socket.rb:3:in `<top (required)>'
/bundle/gems/wands-0.6.1/lib/wands.rb:4:in `require_relative'
/bundle/gems/wands-0.6.1/lib/wands.rb:4:in `<top (required)>'
<internal:/usr/local/lib/ruby/3.3.0/rubygems/core_ext/kernel_require.rb>:136:in `require'
<internal:/usr/local/lib/ruby/3.3.0/rubygems/core_ext/kernel_require.rb>:136:in `require'
eval:1:in `<main>'
-e:in `eval'
    at j (https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.7.1/dist/browser/+esm:7:23748)
    at https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.7.1/dist/browser/+esm:7:24383
    at x (https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.7.1/dist/browser/+esm:7:23866)
    at S (https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.7.1/dist/browser/+esm:7:24319)
    at w.eval (https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.7.1/dist/browser/+esm:7:20663)
    at runRubyScriptsInHtml (http://localhost:8000/dist/load_ruby_script.js:31:14)

あとはwandsが動くように直して行きます。