Skip to content

Commit 21828db

Browse files
committed
Add browser data-env support
1 parent ef0300a commit 21828db

7 files changed

Lines changed: 132 additions & 3 deletions

File tree

docs/cheat_sheet.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ The easiest way to run Ruby on browser is to use `browser.script.iife.js` script
4646
</html>
4747
```
4848

49+
Use `data-env` on the `browser.script.iife.js` script tag to pass environment variables when the Ruby VM starts:
50+
51+
```html
52+
<script
53+
src="https://cdn.jsdelivr.net/npm/@ruby/4.0-wasm-wasi@2.9.4/dist/browser.script.iife.js"
54+
data-env="RUBY_BOX=1 RUBY_FIBER_MACHINE_STACK_SIZE=1048576"
55+
></script>
56+
```
57+
4958
If you want to control Ruby VM from JavaScript, you can use `@ruby/wasm-wasi` package API:
5059

5160
```html

packages/npm-packages/ruby-wasm-wasi/example/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This is a simple example of how to use the `ruby-wasm-wasi` family packages
88
$ ruby -run -e httpd . -p 8000
99
$ # Open http://localhost:8000/hello.html
1010
$ # Open http://localhost:8000/lucky.html
11+
$ # Open http://localhost:8000/ruby-box.html
1112
$ # Open http://localhost:8000/script-src
1213
```
1314

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<html>
2+
<script
3+
src="https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@2.9.4/dist/browser.script.iife.js"
4+
data-env="RUBY_BOX=1"
5+
></script>
6+
<div id="result">
7+
<div id="enabled"></div>
8+
<div id="constant"></div>
9+
</div>
10+
<script type="text/ruby">
11+
require "js"
12+
13+
box = Ruby::Box.new
14+
box.eval <<~RUBY
15+
X = 123
16+
RUBY
17+
18+
document = JS.global[:document]
19+
document.getElementById("enabled")[:innerText] = "Ruby::Box.enabled?: #{Ruby::Box.enabled?}"
20+
document.getElementById("constant")[:innerText] = "box::X: #{box::X}"
21+
</script>
22+
</html>

packages/npm-packages/ruby-wasm-wasi/src/browser.script.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,18 @@ export const main = async (
88
pkg: { name: string; version: string },
99
options?: Parameters<typeof DefaultRubyVM>[1],
1010
) => {
11+
const scriptEnv = deriveEnv(document.currentScript);
1112
const response = fetch(
1213
`https://cdn.jsdelivr.net/npm/${pkg.name}@${pkg.version}/dist/ruby+stdlib.wasm`,
1314
);
1415
const module = await compileWebAssemblyModule(response);
15-
const { vm } = await DefaultRubyVM(module, options);
16+
const { vm } = await DefaultRubyVM(module, {
17+
...options,
18+
env: {
19+
...scriptEnv,
20+
...options?.env,
21+
},
22+
});
1623
await mainWithRubyVM(vm);
1724
};
1825

@@ -24,12 +31,18 @@ export const componentMain = async (
2431
options: {
2532
instantiate: RubyComponentInstantiator;
2633
wasip2: any;
34+
env?: Record<string, string> | undefined;
2735
}
2836
) => {
37+
const scriptEnv = deriveEnv(document.currentScript);
2938
const componentUrl = `https://cdn.jsdelivr.net/npm/${pkg.name}@${pkg.version}/dist/component`;
3039
const fetchComponentFile = (relativePath: string) => fetch(`${componentUrl}/${relativePath}`);
3140
const { vm } = await RubyVM.instantiateComponent({
3241
...options,
42+
env: {
43+
...scriptEnv,
44+
...options.env,
45+
},
3346
getCoreModule: (relativePath: string) => {
3447
const response = fetchComponentFile(relativePath);
3548
return compileWebAssemblyModule(response);
@@ -90,6 +103,34 @@ const deriveEvalStyle = (tag: Element): "async" | "sync" => {
90103
return rawEvalStyle;
91104
};
92105

106+
const deriveEnv = (tag: Element | null): Record<string, string> => {
107+
const rawEnv = tag?.getAttribute("data-env");
108+
if (!rawEnv) {
109+
return {};
110+
}
111+
112+
const trimmedEnv = rawEnv.trim();
113+
if (!trimmedEnv) {
114+
return {};
115+
}
116+
117+
return trimmedEnv
118+
.split(/\s+/)
119+
.reduce<Record<string, string>>((env, entry) => {
120+
const delimiterIndex = entry.indexOf("=");
121+
if (delimiterIndex <= 0) {
122+
console.warn(
123+
`data-env entry must be in the KEY=value format. ${entry} is ignored.`,
124+
);
125+
return env;
126+
}
127+
128+
// Only the first "=" separates key and value so values can contain "=".
129+
env[entry.slice(0, delimiterIndex)] = entry.slice(delimiterIndex + 1);
130+
return env;
131+
}, {});
132+
};
133+
93134
const loadScriptAsync = async (
94135
tag: Element,
95136
): Promise<{ scriptContent: string; evalStyle: "async" | "sync" } | null> => {

packages/npm-packages/ruby-wasm-wasi/src/vm.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ export type RubyInitComponentOptions = {
5454
*/
5555
wasip2: any;
5656

57+
/**
58+
* Environment variables to pass to the Ruby VM.
59+
*/
60+
env?: Record<string, string> | undefined;
61+
5762
/**
5863
* The arguments to pass to the Ruby VM. Note that the first argument must be the Ruby program name.
5964
*
@@ -179,9 +184,18 @@ export class RubyVM {
179184
initComponent = async (jsRuntime) => {
180185
const { instantiate, getCoreModule, wasip2 } = options;
181186
const { cli, clocks, filesystem, io, random, sockets, http } = wasip2;
187+
const environment = options.env ? {
188+
...cli.environment,
189+
getEnvironment: () => Array.from(
190+
new Map([
191+
...cli.environment.getEnvironment(),
192+
...Object.entries(options.env ?? {}),
193+
]).entries(),
194+
),
195+
} : cli.environment;
182196
const importObject = {
183197
"ruby:js/js-runtime": jsRuntime,
184-
"wasi:cli/environment": cli.environment,
198+
"wasi:cli/environment": environment,
185199
"wasi:cli/exit": cli.exit,
186200
"wasi:cli/stderr": cli.stderr,
187201
"wasi:cli/stdin": cli.stdin,

packages/npm-packages/ruby-wasm-wasi/test-e2e/examples/examples.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ test("lucky.html is healthy", async ({ page }) => {
5555
expect(result).toMatch(/(Lucky|Unlucky)/);
5656
});
5757

58+
test("ruby-box.html is healthy", async ({ page }) => {
59+
await page.goto("/ruby-box.html");
60+
await waitForRubyVM(page);
61+
await expect(page.locator("#enabled")).toHaveText("Ruby::Box.enabled?: true");
62+
await expect(page.locator("#constant")).toHaveText("box::X: 123");
63+
});
64+
5865
test("script-src/index.html is healthy", async ({ page }) => {
5966
const messages: string[] = [];
6067
page.on("console", (msg) => messages.push(msg.text()));
@@ -80,7 +87,7 @@ if (process.env.RUBY_NPM_PACKAGE_ROOT) {
8087
await page.goto("/require_relative/index.html");
8188

8289
await waitForRubyVM(page);
83-
while (!messages.some((msg) => /Hello, world\!/.test(msg))) {
90+
while (!messages.some((msg) => /Hello, world\!/.test(msg))) {
8491
await page.waitForEvent("console");
8592
}
8693
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { test, expect } from "@playwright/test";
2+
3+
import {
4+
setupDebugLog,
5+
setupProxy,
6+
setupUncaughtExceptionRejection,
7+
resolveBinding,
8+
} from "../support";
9+
10+
if (!process.env.RUBY_NPM_PACKAGE_ROOT) {
11+
test.skip("skip", () => {});
12+
} else {
13+
test.beforeEach(async ({ context, page }) => {
14+
setupDebugLog(context);
15+
setupProxy(context);
16+
setupUncaughtExceptionRejection(page);
17+
});
18+
19+
test.describe("data-env", () => {
20+
test("passes environment variables to the Ruby VM", async ({ page }) => {
21+
const resolve = await resolveBinding(page, "checkResolved");
22+
await page.setContent(`
23+
<script
24+
src="https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@latest/dist/browser.script.iife.js"
25+
data-env="RUBY_WASM_TEST=ok RUBY_WASM_TEST_EQUALS=a=b"
26+
></script>
27+
<script type="text/ruby" data-eval="async">
28+
require "js"
29+
JS.global.checkResolved [ENV["RUBY_WASM_TEST"], ENV["RUBY_WASM_TEST_EQUALS"]].join(",")
30+
</script>
31+
`);
32+
expect(await resolve()).toBe("ok,a=b");
33+
});
34+
});
35+
}

0 commit comments

Comments
 (0)