Skip to content

Commit 76b8bd1

Browse files
committed
WebR - Expreimental
1 parent 82433df commit 76b8bd1

File tree

12 files changed

+149
-8
lines changed

12 files changed

+149
-8
lines changed

docs/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ We also explicitly use that "_piece of software_" as the interpreter name it ref
3535
* [micropython](https://micropython.org/) is the name of the interpreter that runs a small subset of the *Python* standard library and is optimized to run in constrained environments such as *Mobile* phones, or even *Desktop*, thanks to its tiny size and an extremely fast bootstrap
3636
* [ruby-wasm-wasi](https://github.com/ruby/ruby.wasm) is the name of the (currently *experimental*) interpreter that adds *Ruby* to the list of programming languages currently supported
3737
* [wasmoon](https://github.com/ceifa/wasmoon) is the name of the interpreter that runs *Lua* on the browser and that, among the previous two interpreters, is fully compatible with all core features
38+
* [webr](https://docs.r-wasm.org/webr/latest/) is the name of the (currently *experimental*) interpreter that adds *R* to the list of programming languages currently supported
3839

3940
`<script>` tags specify which *interpreter* to use via the `type` attribute. This is typically the full name of the interpreter:
4041

@@ -56,6 +57,10 @@ We also explicitly use that "_piece of software_" as the interpreter name it ref
5657
<script type="wasmoon">
5758
print(_VERSION)
5859
</script>
60+
61+
<script type="webr">
62+
print(R.version.string)
63+
</script>
5964
```
6065

6166
ℹ️ - Please note we decided on purpose to not use the generic programming language name instead of its interpreter project name to avoid being too exclusive for alternative projects that would like to target that very same Programming Language (i.e. note *pyodide* & *micropython* not using *python* indeed as interpreter name).
@@ -772,10 +777,14 @@ Please note that if a worker is created explicitly, there won't be any element,
772777
| micropython | • | • | • | • | • | • |
773778
| ruby-wasm-wasi | • | • | • | ! | | |
774779
| wasmoon | • | • | • | ! | • | |
780+
| webr | r | • | re | | | |
775781
776782
* **run** allows code to run synchronously and optionally return value
777783
* **runAsync** allows code to run asynchronously and optionally return value
778784
* **runEvent** allows events to be invoked and receive the `event` object
779785
* **registerJSModule** allows `from polyscript import Xworker` or registration of arbitrary modules for *custom types*. It currently fallback to globally defined reference (the module name) whenever it's not possible to register a module (i.e. `polyscriptXWorker` in Lua or `$polyscript.XWorker` in Ruby).
780786
* **writeFile** it's used to save *fetch* config files into virtual FS (usually the one provided by Emscripten). It is then possible to import those files as module within the evaluated code.
781787
* **transform** allows `xworker.sync` related invokes to pass as argument internal objects without issues, simplifying as example the dance needed with *pyodide* and the `ffi.PyProxy` interface, automatically using `.toJs()` for better DX.
788+
789+
* issue **r**: the runtime exposes the `run` utility but this is *not synchronous*
790+
* issue **re**: the event or its listener somehow run but it's not possible to `stopPropagation()` or do other regular *event* operations even on the main thread

docs/index.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/index.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

esm/interpreter/webr.js

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { create } from 'gc-hook';
2+
import { dedent } from '../utils.js';
3+
import { fetchFiles, fetchJSModules, fetchPaths } from './_utils.js';
4+
import { io, stdio } from './_io.js';
5+
6+
const type = 'webr';
7+
const r = new WeakMap();
8+
9+
// REQUIRES INTEGRATION TEST
10+
/* c8 ignore start */
11+
const run = async (interpreter, code) => {
12+
const { shelter, destroy, io } = r.get(interpreter);
13+
const { output, result } = await shelter.captureR(dedent(code));
14+
for (const { type, data } of output) io[type](data);
15+
// this is a double proxy but it's OK as the consumer
16+
// of the result here needs to invoke explicitly a conversion
17+
// or trust the `(await p.toJs()).values` returns what's expected.
18+
return create(result, destroy, { token: false });
19+
};
20+
21+
export default {
22+
type,
23+
experimental: true,
24+
module: (version = '0.3.2') =>
25+
`https://cdn.jsdelivr.net/npm/webr@${version}/dist/webr.mjs`,
26+
async engine(module, config) {
27+
const { get } = stdio();
28+
const interpreter = new module.WebR();
29+
await get(interpreter.init().then(() => interpreter));
30+
const shelter = await new interpreter.Shelter();
31+
r.set(interpreter, {
32+
module,
33+
shelter,
34+
destroy: shelter.destroy.bind(shelter),
35+
io: io.get(interpreter),
36+
});
37+
if (config.files) await fetchFiles(this, interpreter, config.files);
38+
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
39+
if (config.js_modules) await fetchJSModules(config.js_modules);
40+
return interpreter;
41+
},
42+
// Fallback to globally defined module fields (i.e. $xworker)
43+
registerJSModule(_, name) {
44+
console.warn(`Experimental interpreter: module ${name} is not supported (yet)`);
45+
// TODO: as complex JS objects / modules are not allowed
46+
// it's not clear how we can bind anything or import a module
47+
// in a context that doesn't understand methods from JS
48+
// https://docs.r-wasm.org/webr/latest/convert-js-to-r.html#constructing-r-objects-from-javascript-objects
49+
},
50+
run,
51+
runAsync: run,
52+
async runEvent(interpreter, code, event) {
53+
// TODO: WebR cannot convert exoteric objects or any literal
54+
// to an easy to reason about data/frame ... that convertion
55+
// is reserved for the future:
56+
// https://docs.r-wasm.org/webr/latest/convert-js-to-r.html#constructing-r-objects-from-javascript-objects
57+
await interpreter.evalRVoid(`${code}(event)`, {
58+
env: { event: { type: [ event.type ] } }
59+
});
60+
},
61+
transform: (_, value) => {
62+
console.log('transforming', value);
63+
return value;
64+
},
65+
writeFile: () => {
66+
// MAYBE ???
67+
},
68+
};
69+
/* c8 ignore stop */

esm/interpreters.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,6 @@ import micropython from './interpreter/micropython.js';
6262
import pyodide from './interpreter/pyodide.js';
6363
import ruby_wasm_wasi from './interpreter/ruby-wasm-wasi.js';
6464
import wasmoon from './interpreter/wasmoon.js';
65-
for (const interpreter of [micropython, pyodide, ruby_wasm_wasi, wasmoon])
65+
import webr from './interpreter/webr.js';
66+
for (const interpreter of [micropython, pyodide, ruby_wasm_wasi, wasmoon, webr])
6667
register(interpreter);

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@
2323
"size:module": "echo module is $(cat dist/index.js | brotli | wc -c) bytes once compressed",
2424
"size:worker": "echo worker is $(cat esm/worker/xworker.js | brotli | wc -c) bytes once compressed",
2525
"ts": "rm -rf types && tsc -p .",
26-
"update:interpreters": "npm run version:pyodide && npm run version:wasmoon && npm run version:micropython && npm run version:ruby-wasm-wasi && node rollup/update_versions.cjs && npm run build && npm run test",
26+
"update:interpreters": "npm run version:pyodide && npm run version:wasmoon && npm run version:webr && npm run version:micropython && npm run version:ruby-wasm-wasi && node rollup/update_versions.cjs && npm run build && npm run test",
2727
"version:micropython": "npm view @micropython/micropython-webassembly-pyscript version>versions/micropython",
2828
"version:pyodide": "npm view pyodide version>versions/pyodide",
2929
"version:ruby-wasm-wasi": "git ls-remote --tags --refs --sort='v:refname' https://github.com/ruby/ruby.wasm.git | grep 'tags/[[:digit:]]\\.' | tail -n1 | sed 's/.*\\///'>versions/ruby-wasm-wasi",
30-
"version:wasmoon": "npm view wasmoon version>versions/wasmoon"
30+
"version:wasmoon": "npm view wasmoon version>versions/wasmoon",
31+
"version:webr": "npm view webr version>versions/webr"
3132
},
3233
"keywords": [
3334
"polyscript",
@@ -89,6 +90,6 @@
8990
"to-json-callback": "^0.1.1"
9091
},
9192
"worker": {
92-
"blob": "sha256-x9FnCN9Oz1l5i3eTcOPg2IrqbdU7VfskuTHKznW4/L0="
93+
"blob": "sha256-LfRMRHNR4OuZRpfqszG9crCkgf5MX6IPDaoCdHjnzls="
9394
}
9495
}

test/integration.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>polyscript integration tests</title>
77
</head>
8-
<body><ul><li><strong>micropython</strong><ul><li><a href="/test/integration/interpreter/micropython/bootstrap.html">bootstrap</a></li><li><a href="/test/integration/interpreter/micropython/config-json.html">config-json</a></li><li><a href="/test/integration/interpreter/micropython/config-object.html">config-object</a></li><li><a href="/test/integration/interpreter/micropython/current-script.html">current-script</a></li><li><a href="/test/integration/interpreter/micropython/custom-hooks.html">custom-hooks</a></li><li><a href="/test/integration/interpreter/micropython/fetch.html">fetch</a></li><li><a href="/test/integration/interpreter/micropython/interpreter-local.html">interpreter-local</a></li><li><a href="/test/integration/interpreter/micropython/mip.html">mip</a></li><li><a href="/test/integration/interpreter/micropython/no-type.html">no-type</a></li><li><a href="/test/integration/interpreter/micropython/ready-done.html">ready-done</a></li><li><a href="/test/integration/interpreter/micropython/worker-attribute.html">worker-attribute</a></li><li><a href="/test/integration/interpreter/micropython/worker-bad.html">worker-bad</a></li><li><a href="/test/integration/interpreter/micropython/worker-empty-attribute.html">worker-empty-attribute</a></li><li><a href="/test/integration/interpreter/micropython/worker-error.html">worker-error</a></li><li><a href="/test/integration/interpreter/micropython/worker-lua.html">worker-lua</a></li><li><a href="/test/integration/interpreter/micropython/worker-tag.html">worker-tag</a></li><li><a href="/test/integration/interpreter/micropython/worker-window.html">worker-window</a></li><li><a href="/test/integration/interpreter/micropython/worker.html">worker</a></li></ul><li><strong>pyodide</strong><ul><li><a href="/test/integration/interpreter/pyodide/bootstrap.html">bootstrap</a></li><li><a href="/test/integration/interpreter/pyodide/button.html">button</a></li><li><a href="/test/integration/interpreter/pyodide/config-json.html">config-json</a></li><li><a href="/test/integration/interpreter/pyodide/fetch.html">fetch</a></li><li><a href="/test/integration/interpreter/pyodide/sync.html">sync</a></li><li><a href="/test/integration/interpreter/pyodide/worker-error.html">worker-error</a></li><li><a href="/test/integration/interpreter/pyodide/worker-transform.html">worker-transform</a></li><li><a href="/test/integration/interpreter/pyodide/worker.html">worker</a></li></ul><li><strong>ruby-wasm-wasi</strong><ul><li><a href="/test/integration/interpreter/ruby-wasm-wasi/bootstrap.html">bootstrap</a></li></ul><li><strong>wasmoon</strong><ul><li><a href="/test/integration/interpreter/wasmoon/bootstrap.html">bootstrap</a></li><li><a href="/test/integration/interpreter/wasmoon/worker.html">worker</a></li></ul></ul></body>
8+
<body><ul><li><strong>micropython</strong><ul><li><a href="/test/integration/interpreter/micropython/bootstrap.html">bootstrap</a></li><li><a href="/test/integration/interpreter/micropython/config-json.html">config-json</a></li><li><a href="/test/integration/interpreter/micropython/config-object.html">config-object</a></li><li><a href="/test/integration/interpreter/micropython/current-script.html">current-script</a></li><li><a href="/test/integration/interpreter/micropython/custom-hooks.html">custom-hooks</a></li><li><a href="/test/integration/interpreter/micropython/fetch.html">fetch</a></li><li><a href="/test/integration/interpreter/micropython/interpreter-local.html">interpreter-local</a></li><li><a href="/test/integration/interpreter/micropython/mip.html">mip</a></li><li><a href="/test/integration/interpreter/micropython/no-type.html">no-type</a></li><li><a href="/test/integration/interpreter/micropython/ready-done.html">ready-done</a></li><li><a href="/test/integration/interpreter/micropython/worker-attribute.html">worker-attribute</a></li><li><a href="/test/integration/interpreter/micropython/worker-bad.html">worker-bad</a></li><li><a href="/test/integration/interpreter/micropython/worker-empty-attribute.html">worker-empty-attribute</a></li><li><a href="/test/integration/interpreter/micropython/worker-error.html">worker-error</a></li><li><a href="/test/integration/interpreter/micropython/worker-lua.html">worker-lua</a></li><li><a href="/test/integration/interpreter/micropython/worker-tag.html">worker-tag</a></li><li><a href="/test/integration/interpreter/micropython/worker-window.html">worker-window</a></li><li><a href="/test/integration/interpreter/micropython/worker.html">worker</a></li></ul><li><strong>pyodide</strong><ul><li><a href="/test/integration/interpreter/pyodide/bootstrap.html">bootstrap</a></li><li><a href="/test/integration/interpreter/pyodide/button.html">button</a></li><li><a href="/test/integration/interpreter/pyodide/config-json.html">config-json</a></li><li><a href="/test/integration/interpreter/pyodide/fetch.html">fetch</a></li><li><a href="/test/integration/interpreter/pyodide/sync.html">sync</a></li><li><a href="/test/integration/interpreter/pyodide/worker-error.html">worker-error</a></li><li><a href="/test/integration/interpreter/pyodide/worker-transform.html">worker-transform</a></li><li><a href="/test/integration/interpreter/pyodide/worker.html">worker</a></li></ul><li><strong>ruby-wasm-wasi</strong><ul><li><a href="/test/integration/interpreter/ruby-wasm-wasi/bootstrap.html">bootstrap</a></li></ul><li><strong>wasmoon</strong><ul><li><a href="/test/integration/interpreter/wasmoon/bootstrap.html">bootstrap</a></li><li><a href="/test/integration/interpreter/wasmoon/worker.html">worker</a></li></ul><li><strong>webr</strong><ul><li><a href="/test/integration/interpreter/webr/just-click.html">just-click</a></li></ul></ul></body>
99
</html>

test/integration/_shared.js

+12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ exports.shared = {
99
await expect(result.trim()).toBe('OK');
1010
},
1111

12+
justClick: ({ expect }, baseURL) => async ({ page }) => {
13+
// Test that a config passed as object works out of the box.
14+
const logs = [];
15+
page.on('console', msg => logs.push(msg.text()));
16+
await page.goto(`${baseURL}/just-click.html`);
17+
await page.waitForSelector('html.ready');
18+
await page.getByRole('button').click();
19+
// this is ugly ... reaction time really slow on listeners (100 is safe)
20+
await new Promise($ => setTimeout($, 100));
21+
await expect(/\bOK\b/.test(logs.at(-1))).toBe(true);
22+
},
23+
1224
worker: ({ expect }, url) => async ({ page }) => {
1325
const logs = [];
1426
page.on('console', msg => logs.push(msg.text()));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<script type="module">
7+
import { init } from '../utils.js';
8+
init('webr');
9+
</script>
10+
</head>
11+
<body>
12+
<script type="webr">
13+
show_OK <- function(event) {
14+
if (event["type"] == "click")
15+
print("OK")
16+
}
17+
</script>
18+
<button webr-click="show_OK"></button>
19+
</body>
20+
</html>

test/integration/webr.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
3+
const { shared } = require('./_shared.js');
4+
5+
module.exports = (playwright, baseURL) => {
6+
const { test } = playwright;
7+
8+
test('WebR just click', shared.justClick(playwright, baseURL));
9+
};

test/webr.html

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
6+
<title>WebR</title>
7+
<link rel="stylesheet" href="style.css">
8+
<script type="module" src="/dist/index.js"></script>
9+
</head>
10+
<body>
11+
<script type="webr">
12+
print_version <- function(event) {
13+
if (event["type"] == "click")
14+
print(R.version.string)
15+
}
16+
</script>
17+
<button webr-click="print_version">webr version</button>
18+
</body>
19+
</html>

versions/webr

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.3.2

0 commit comments

Comments
 (0)