Add pyscript module in both Main and Workers (#1619)

This commit is contained in:
Andrea Giammarchi
2023-08-03 10:44:17 +02:00
committed by GitHub
parent 2774e49ab9
commit 8a01a56e51
6 changed files with 211 additions and 142 deletions

View File

@@ -20,7 +20,7 @@ repos:
- id: check-yaml - id: check-yaml
- id: detect-private-key - id: detect-private-key
- id: end-of-file-fixer - id: end-of-file-fixer
exclude: \.min\.js$ exclude: pyscript\.next/core.*|\.min\.js$
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/charliermarsh/ruff-pre-commit - repo: https://github.com/charliermarsh/ruff-pre-commit

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +1,11 @@
import "@ungap/with-resolvers"; import "@ungap/with-resolvers";
import { $ } from "basic-devtools"; import { $ } from "basic-devtools";
import { define } from "polyscript"; import { define, XWorker } from "polyscript";
// TODO: this is not strictly polyscript related but handy ... not sure // TODO: this is not strictly polyscript related but handy ... not sure
// we should factor this utility out a part but this works anyway. // we should factor this utility out a part but this works anyway.
import { queryTarget } from "../node_modules/polyscript/esm/script-handler.js"; import { queryTarget } from "../node_modules/polyscript/esm/script-handler.js";
import { Hook } from "../node_modules/polyscript/esm/worker/hooks.js";
import { robustFetch as fetch } from "./fetch.js"; import { robustFetch as fetch } from "./fetch.js";
@@ -12,7 +13,9 @@ const { defineProperty } = Object;
const getText = (body) => body.text(); const getText = (body) => body.text();
(async () => { // allows lazy element features on code evaluation
let currentElement;
// create a unique identifier when/if needed // create a unique identifier when/if needed
let id = 0; let id = 0;
const getID = (prefix = "py") => `${prefix}-${id++}`; const getID = (prefix = "py") => `${prefix}-${id++}`;
@@ -68,19 +71,54 @@ const getText = (body) => body.text();
for (const fn of hooks[hook]) fn(pyodide, element); for (const fn of hooks[hook]) fn(pyodide, element);
}; };
const addDisplay = (element) => { const registerModule = ({ XWorker: $XWorker, interpreter, io }) => {
const id = isScript(element) ? element.target.id : element.id; // automatically use the pyscript stderr (when/if defined)
return ` // this defaults to console.error
# this code is just for demo purpose but the basics work function PyWorker(...args) {
def _display(what, target="${id}", append=True): const worker = $XWorker(...args);
from js import document worker.onerror = ({ error }) => io.stderr(error);
element = document.getElementById(target) return worker;
if append: }
element.append(what) interpreter.registerJsModule("pyscript", {
else: PyWorker,
element.textContent = what document,
display = _display window,
`; // a getter to ensure if multiple scripts with same
// env (py) runs, their execution code will have the correct
// display reference with automatic target
get display() {
const id = isScript(currentElement)
? currentElement.target.id
: currentElement.id;
// TODO: decide which feature of display we want to keep
return (what, target = id, append = true) => {
const element = document.getElementById(target);
if (append) element.append(what);
else element.textContent = what;
};
},
});
};
const workerPyScriptModule = [
"from pyodide_js import FS",
`FS.writeFile('./pyscript.py', '${[
"import polyscript",
"document=polyscript.xworker.window.document",
"window=polyscript.xworker.window",
"sync=polyscript.xworker.sync",
].join(";")}')`,
].join(";");
const workerHooks = {
codeBeforeRunWorker: () =>
[workerPyScriptModule, ...hooks.codeBeforeRunWorker].join("\n"),
codeBeforeRunWorkerAsync: () =>
[workerPyScriptModule, ...hooks.codeBeforeRunWorkerAsync].join("\n"),
codeAfterRunWorker: () => [...hooks.codeAfterRunWorker].join("\n"),
codeAfterRunWorkerAsync: () =>
[...hooks.codeAfterRunWorkerAsync].join("\n"),
}; };
// define the module as both `<script type="py">` and `<py-script>` // define the module as both `<script type="py">` and `<py-script>`
@@ -88,24 +126,14 @@ const getText = (body) => body.text();
config, config,
env: "py-script", env: "py-script",
interpreter: "pyodide", interpreter: "pyodide",
codeBeforeRunWorker() { ...workerHooks,
return [...hooks.codeBeforeRunWorker].join("\n");
},
codeAfterRunWorker() {
return [...hooks.codeAfterRunWorker].join("\n");
},
onBeforeRun(pyodide, element) { onBeforeRun(pyodide, element) {
currentElement = element;
bootstrapNodeAndPlugins(pyodide, element, before, "onBeforeRun"); bootstrapNodeAndPlugins(pyodide, element, before, "onBeforeRun");
pyodide.interpreter.runPython(addDisplay(element));
}, },
onBeforeRunAync(pyodide, element) { onBeforeRunAync(pyodide, element) {
pyodide.interpreter.runPython(addDisplay(element)); currentElement = element;
bootstrapNodeAndPlugins( bootstrapNodeAndPlugins(pyodide, element, before, "onBeforeRunAync");
pyodide,
element,
before,
"onBeforeRunAync",
);
}, },
onAfterRun(pyodide, element) { onAfterRun(pyodide, element) {
bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRun"); bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRun");
@@ -114,6 +142,7 @@ const getText = (body) => body.text();
bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRunAsync"); bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRunAsync");
}, },
async onInterpreterReady(pyodide, element) { async onInterpreterReady(pyodide, element) {
registerModule(pyodide, element);
// allows plugins to do whatever they want with the element // allows plugins to do whatever they want with the element
// before regular stuff happens in here // before regular stuff happens in here
for (const callback of hooks.onInterpreterReady) for (const callback of hooks.onInterpreterReady)
@@ -134,9 +163,7 @@ const getText = (body) => body.text();
// document.currentScript.target if needed // document.currentScript.target if needed
defineProperty(element, "target", { value: show }); defineProperty(element, "target", { value: show });
pyodide[`run${isAsync ? "Async" : ""}`]( pyodide[`run${isAsync ? "Async" : ""}`](await fetchSource(element));
await fetchSource(element),
);
} else { } else {
// resolve PyScriptElement to allow connectedCallback // resolve PyScriptElement to allow connectedCallback
element._pyodide.resolve(pyodide); element._pyodide.resolve(pyodide);
@@ -165,7 +192,18 @@ const getText = (body) => body.text();
} }
customElements.define("py-script", PyScriptElement); customElements.define("py-script", PyScriptElement);
})();
export function PyWorker(file, options) {
// this propagates pyscript worker hooks without needing a pyscript
// bootstrap + it passes arguments and enforces `pyodide`
// as the interpreter to use in the worker, as all hooks assume that
// and as `pyodide` is the only default interpreter that can deal with
// all the features we need to deliver pyscript out there.
return XWorker.call(new Hook(null, workerHooks), file, {
...options,
type: "pyodide",
});
}
export const hooks = { export const hooks = {
/** @type {Set<function>} */ /** @type {Set<function>} */

View File

@@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PyScript Next</title>
<link rel="stylesheet" href="../core.css" />
<script type="module">
import { PyWorker } from '../core.js';
PyWorker('./worker.py'/*, options allowed except `type` */);
// the type is overwritten as "pyodide" in PyScript as the module
// lives in that env too
</script>
<script>
// this is only to test non-blocking nature/bootstrap
addEventListener('DOMContentLoaded', () => {
const div = document.body.appendChild(
document.createElement('div')
);
(function monitor() {
const date = new Date;
div.textContent = `${date.getSeconds()}.${date.getMilliseconds()}`;
requestAnimationFrame(monitor);
}());
});
</script>
</head>
</html>

View File

@@ -0,0 +1,3 @@
from pyscript import document
document.body.append("Hello World")