mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
[next] Improve the whole events story (#1584)
This commit is contained in:
committed by
GitHub
parent
c6b5ce7f55
commit
0b0e03456c
@@ -455,7 +455,8 @@ In few words, while every *interpreter* is literally passed along to unlock its
|
||||
| io | `wrap.io` | Allow to lazily define different `stdout` or `stderr` via the running *interpreter*. This `io` field can be lazily defined and restored back for any element currently running the code. |
|
||||
| config | `wrap.config` | It is the resolved *JSON* config and it is an own clone per each element running the code, usable also as "_state_" reference for the specific element, as changing it at run time will never affect any other element. |
|
||||
| run | `wrap.run(code)` | It abstracts away the need to know the exact method name used to run code synchronously, whenever the *interpreter* allows such operation, facilitating future migrations from an interpreter to another. |
|
||||
| runAsync | `wrap.run(code)` | It abstracts away the need to know the exact method name used to run code asynchronously, whenever the *interpreter* allows such operation, facilitating future migrations from an interpreter to another. |
|
||||
| runAsync | `wrap.runAsync(code)` | It abstracts away the need to know the exact method name used to run code asynchronously, whenever the *interpreter* allows such operation, facilitating future migrations from an interpreter to another. |
|
||||
| runEvent | `wrap.runEvent(code, event)` | It abstracts away the need to know how an *interpreter* retrieves paths to execute an event handler. |
|
||||
|
||||
This is the `wrap` mentioned with most hooks and initializers previously described, and we're more than happy to learn if we are not passing along some extra helper.
|
||||
|
||||
|
||||
@@ -119,6 +119,7 @@ export const handleCustomType = (node) => {
|
||||
config: structuredClone(configs.get(name)),
|
||||
run: module.run.bind(module, interpreter),
|
||||
runAsync: module.runAsync.bind(module, interpreter),
|
||||
runEvent: module.runEvent.bind(module, interpreter),
|
||||
};
|
||||
|
||||
resolve(resolved);
|
||||
|
||||
@@ -2,23 +2,23 @@ import { clean, writeFile as writeFileUtil } from "./_utils.js";
|
||||
|
||||
// REQUIRES INTEGRATION TEST
|
||||
/* c8 ignore start */
|
||||
export const registerJSModule = (interpreter, name, value) => {
|
||||
interpreter.registerJsModule(name, value);
|
||||
};
|
||||
|
||||
export const run = (interpreter, code) => interpreter.runPython(clean(code));
|
||||
|
||||
export const runAsync = (interpreter, code) =>
|
||||
interpreter.runPythonAsync(clean(code));
|
||||
|
||||
export const getGlobal = (interpreter, name) => interpreter.globals.get(name);
|
||||
|
||||
export const setGlobal = (interpreter, name, value) => {
|
||||
interpreter.globals.set(name, value);
|
||||
};
|
||||
|
||||
export const deleteGlobal = (interpreter, name) => {
|
||||
interpreter.globals.delete(name);
|
||||
};
|
||||
|
||||
export const registerJSModule = (interpreter, name, value) => {
|
||||
interpreter.registerJsModule(name, value);
|
||||
export const runEvent = async (interpreter, code, event) => {
|
||||
// allows method(event) as well as namespace.method(event)
|
||||
// it does not allow fancy brackets names for now
|
||||
const [name, ...keys] = code.split(".");
|
||||
let target = interpreter.globals.get(name);
|
||||
let context;
|
||||
for (const key of keys) [context, target] = [target, target[key]];
|
||||
target.call(context, event);
|
||||
};
|
||||
|
||||
export const writeFile = ({ FS }, path, buffer) =>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "@ungap/with-resolvers";
|
||||
|
||||
import { getBuffer } from "../fetch-utils.js";
|
||||
import { absoluteURL, entries } from "../utils.js";
|
||||
import { absoluteURL } from "../utils.js";
|
||||
|
||||
/**
|
||||
* Trim code only if it's a single line that prettier or other tools might have modified.
|
||||
@@ -131,12 +131,4 @@ export const fetchPaths = (module, interpreter, config_fetch) =>
|
||||
.then((buffer) => module.writeFile(interpreter, path, buffer)),
|
||||
),
|
||||
);
|
||||
|
||||
// this is a fallback for interpreters unable to register JS modules
|
||||
// all defined keys will end up as globally available references
|
||||
// REQUIRES INTEGRATION TEST
|
||||
/* c8 ignore start */
|
||||
export function registerJSModule(interpreter, _, value) {
|
||||
for (const [k, v] of entries(value)) this.setGlobal(interpreter, k, v);
|
||||
}
|
||||
/* c8 ignore stop */
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { fetchPaths, stdio } from "./_utils.js";
|
||||
import {
|
||||
run,
|
||||
getGlobal,
|
||||
setGlobal,
|
||||
deleteGlobal,
|
||||
registerJSModule,
|
||||
run,
|
||||
runAsync,
|
||||
runEvent,
|
||||
writeFile,
|
||||
} from "./_python.js";
|
||||
|
||||
@@ -23,16 +22,10 @@ export default {
|
||||
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
|
||||
return interpreter;
|
||||
},
|
||||
getGlobal,
|
||||
setGlobal,
|
||||
deleteGlobal,
|
||||
registerJSModule,
|
||||
run,
|
||||
// TODO: MicroPython doesn't have a Pyodide like top-level await,
|
||||
// this method should still not throw errors once invoked
|
||||
async runAsync(...args) {
|
||||
return this.run(...args);
|
||||
},
|
||||
runAsync,
|
||||
runEvent,
|
||||
writeFile,
|
||||
};
|
||||
/* c8 ignore stop */
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { fetchPaths, stdio } from "./_utils.js";
|
||||
import {
|
||||
registerJSModule,
|
||||
run,
|
||||
runAsync,
|
||||
getGlobal,
|
||||
setGlobal,
|
||||
deleteGlobal,
|
||||
registerJSModule,
|
||||
runEvent,
|
||||
writeFile,
|
||||
} from "./_python.js";
|
||||
|
||||
@@ -32,12 +30,10 @@ export default {
|
||||
}
|
||||
return interpreter;
|
||||
},
|
||||
getGlobal,
|
||||
setGlobal,
|
||||
deleteGlobal,
|
||||
registerJSModule,
|
||||
run,
|
||||
runAsync,
|
||||
runEvent,
|
||||
writeFile,
|
||||
};
|
||||
/* c8 ignore stop */
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { clean, fetchPaths, registerJSModule } from "./_utils.js";
|
||||
import { clean, fetchPaths } from "./_utils.js";
|
||||
import { entries } from "../utils.js";
|
||||
|
||||
const type = "ruby-wasm-wasi";
|
||||
const jsType = type.replace(/\W+/g, "_");
|
||||
|
||||
// MISSING:
|
||||
// * there is no VFS apparently or I couldn't reach any
|
||||
@@ -23,31 +25,35 @@ export default {
|
||||
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
|
||||
return interpreter;
|
||||
},
|
||||
registerJSModule,
|
||||
getGlobal(interpreter, name) {
|
||||
try {
|
||||
return this.run(interpreter, name);
|
||||
} catch (_) {
|
||||
const method = this.run(interpreter, `method(:${name})`);
|
||||
return (...args) =>
|
||||
method.call(
|
||||
name,
|
||||
...args.map((value) => interpreter.wrap(value)),
|
||||
);
|
||||
// Fallback to globally defined module fields (i.e. $xworker)
|
||||
registerJSModule(interpreter, _, value) {
|
||||
const code = ['require "js"'];
|
||||
for (const [k, v] of entries(value)) {
|
||||
const id = `__module_${jsType}_${k}`;
|
||||
globalThis[id] = v;
|
||||
code.push(`$${k}=JS.global[:${id}]`);
|
||||
}
|
||||
},
|
||||
setGlobal(interpreter, name, value) {
|
||||
const id = `__pyscript_ruby_wasm_wasi_${name}`;
|
||||
globalThis[id] = value;
|
||||
this.run(interpreter, `require "js";$${name}=JS::eval("return ${id}")`);
|
||||
},
|
||||
deleteGlobal(interpreter, name) {
|
||||
const id = `__pyscript_ruby_wasm_wasi_${name}`;
|
||||
this.run(interpreter, `$${name}=nil`);
|
||||
delete globalThis[id];
|
||||
this.run(interpreter, code.join(";"));
|
||||
},
|
||||
run: (interpreter, code) => interpreter.eval(clean(code)),
|
||||
runAsync: (interpreter, code) => interpreter.evalAsync(clean(code)),
|
||||
runEvent(interpreter, code, event) {
|
||||
// patch common xworker.onmessage/onerror cases
|
||||
if (/^xworker\.(on\w+)$/.test(code)) {
|
||||
const { $1: name } = RegExp;
|
||||
const id = `__module_${jsType}_event`;
|
||||
globalThis[id] = event;
|
||||
this.run(
|
||||
interpreter,
|
||||
`require "js";$xworker.call("${name}",JS.global[:${id}])`,
|
||||
);
|
||||
delete globalThis[id];
|
||||
} else {
|
||||
// Experimental: allows only events by fully qualified method name
|
||||
const method = this.run(interpreter, `method(:${code})`);
|
||||
method.call(code, interpreter.wrap(event));
|
||||
}
|
||||
},
|
||||
writeFile: () => {
|
||||
throw new Error(`writeFile is not supported in ${type}`);
|
||||
},
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import {
|
||||
clean,
|
||||
fetchPaths,
|
||||
stdio,
|
||||
registerJSModule,
|
||||
writeFileShim,
|
||||
} from "./_utils.js";
|
||||
import { clean, fetchPaths, stdio, writeFileShim } from "./_utils.js";
|
||||
|
||||
import { entries } from "../utils.js";
|
||||
|
||||
const type = "wasmoon";
|
||||
|
||||
@@ -27,16 +23,21 @@ export default {
|
||||
if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
|
||||
return interpreter;
|
||||
},
|
||||
registerJSModule,
|
||||
getGlobal: (interpreter, name) => interpreter.global.get(name),
|
||||
setGlobal(interpreter, name, value) {
|
||||
interpreter.global.set(name, value);
|
||||
},
|
||||
deleteGlobal(interpreter, name) {
|
||||
interpreter.global.set(name, void 0);
|
||||
// Fallback to globally defined module fields
|
||||
registerJSModule: (interpreter, _, value) => {
|
||||
for (const [k, v] of entries(value)) interpreter.global.set(k, v);
|
||||
},
|
||||
run: (interpreter, code) => interpreter.doStringSync(clean(code)),
|
||||
runAsync: (interpreter, code) => interpreter.doString(clean(code)),
|
||||
runEvent: (interpreter, code, event) => {
|
||||
// allows method(event) as well as namespace.method(event)
|
||||
// it does not allow fancy brackets names for now
|
||||
const [name, ...keys] = code.split(".");
|
||||
let target = interpreter.global.get(name);
|
||||
let context;
|
||||
for (const key of keys) [context, target] = [target, target[key]];
|
||||
target.call(context, event);
|
||||
},
|
||||
writeFile: (
|
||||
{
|
||||
cmodule: {
|
||||
|
||||
@@ -41,8 +41,7 @@ export const listener = async (event) => {
|
||||
el.getAttribute(`${name}-env`) || name,
|
||||
);
|
||||
const handler = registry.get(name);
|
||||
const callback = handler.getGlobal(interpreter, value);
|
||||
callback(event);
|
||||
handler.runEvent(interpreter, value, event);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -24,15 +24,14 @@ try {
|
||||
);
|
||||
}
|
||||
|
||||
let interpreter, run, interpreterEvent;
|
||||
let interpreter, runEvent;
|
||||
const add = (type, fn) => {
|
||||
addEventListener(
|
||||
type,
|
||||
fn ||
|
||||
(async (event) => {
|
||||
await interpreter;
|
||||
interpreterEvent = event;
|
||||
run(`xworker.on${type}(xworker.event);`, xworker);
|
||||
runEvent(`xworker.on${type}`, event);
|
||||
}),
|
||||
!!fn && { once: true },
|
||||
);
|
||||
@@ -52,17 +51,6 @@ const xworker = {
|
||||
onmessage() {},
|
||||
onmessageerror() {},
|
||||
postMessage: postMessage.bind(self),
|
||||
// this getter exists so that arbitrarily access to xworker.event
|
||||
// would always fail once an event has been dispatched, as that's not
|
||||
// meant to be accessed in the wild, respecting the one-off event nature of JS.
|
||||
// because xworker is a unique well defined globally shared reference,
|
||||
// there's also no need to bother setGlobal and deleteGlobal every single time.
|
||||
get event() {
|
||||
const event = interpreterEvent;
|
||||
if (!event) throw new Error("Unauthorized event access");
|
||||
interpreterEvent = void 0;
|
||||
return event;
|
||||
},
|
||||
};
|
||||
|
||||
add("message", ({ data: { options, code, hooks } }) => {
|
||||
@@ -99,10 +87,10 @@ add("message", ({ data: { options, code, hooks } }) => {
|
||||
}
|
||||
// set the `xworker` global reference once
|
||||
details.registerJSModule(interpreter, "xworker", { xworker });
|
||||
// simplify run calls after possible patches
|
||||
run = details[name].bind(details, interpreter);
|
||||
// execute the content of the worker file
|
||||
run(code);
|
||||
// simplify runEvent calls
|
||||
runEvent = details.runEvent.bind(details, interpreter);
|
||||
// run either sync or async code in the worker
|
||||
await details[name](interpreter, code);
|
||||
return interpreter;
|
||||
})();
|
||||
add("error");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.8",
|
||||
"description": "PyScript Next core",
|
||||
"main": "./cjs/index.js",
|
||||
"types": "./types/index.d.ts",
|
||||
@@ -66,6 +66,6 @@
|
||||
"coincident": "^0.8.3"
|
||||
},
|
||||
"worker": {
|
||||
"blob": "sha256-BqPm4/IdGDQduhprGUnwdf5iumpMmkkGNsPrPZXl+mU="
|
||||
"blob": "sha256-JuwqC8WVlEqybo1Q4UB76PQ260TCqm7b5sSEj1/Tuc8="
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -14,10 +14,16 @@
|
||||
import sys
|
||||
print(event.type)
|
||||
print(sys.version)
|
||||
|
||||
class Printer:
|
||||
def version(self, event):
|
||||
print_version(event)
|
||||
|
||||
printer = Printer()
|
||||
</script>
|
||||
<button
|
||||
pyodide-pointerdown="print_version"
|
||||
pyodide-click="print_version"
|
||||
pyodide-click="printer.version"
|
||||
>
|
||||
pyodide version
|
||||
</button>
|
||||
@@ -28,9 +34,10 @@
|
||||
print(event.type)
|
||||
print(sys.version)
|
||||
</script>
|
||||
<!-- ⚠️ MicroPython bug: it fails the printer.version case -->
|
||||
<button
|
||||
micropython-pointerdown="print_version"
|
||||
micropython-click="print_version"
|
||||
micropython-click="printer.version"
|
||||
>
|
||||
micropython version
|
||||
</button>
|
||||
|
||||
@@ -64,5 +64,17 @@
|
||||
w.postMessage('MicroPython: Hello Lua 👋')
|
||||
w.onmessage = handle_message
|
||||
</script>
|
||||
|
||||
<!-- XWorker - MicroPython to Ruby -->
|
||||
<script type="micropython">
|
||||
from xworker import XWorker
|
||||
|
||||
def handle_message(event):
|
||||
print(event.data)
|
||||
|
||||
w = XWorker('./worker.rb', type='ruby-wasm-wasi')
|
||||
w.postMessage('MicroPython: Hello Ruby 👋')
|
||||
w.onmessage = handle_message
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,6 +1,2 @@
|
||||
require "js"
|
||||
|
||||
xworker = JS::eval("return xworker")
|
||||
|
||||
puts "What is 2 + 3?"
|
||||
puts xworker.sync.input("What is 2 + 3?")
|
||||
puts $xworker[:sync].call("input", "What is 2 + 3?")
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
require "js"
|
||||
|
||||
xworker = JS::eval("return xworker")
|
||||
|
||||
def on_message(event)
|
||||
puts event[:data]
|
||||
xworker.postMessage('Ruby: Hello MicroPython 👋')
|
||||
$xworker.call('postMessage', 'Ruby: Hello MicroPython 👋')
|
||||
end
|
||||
|
||||
xworker.onmessage = on_message
|
||||
$xworker[:onmessage] = -> (event) { on_message event }
|
||||
|
||||
Reference in New Issue
Block a user