mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 10:17:23 -05:00
Implement PyScript custom <script type> (#1548)
* updated MicroPython to latest in order to have `globals` API available * reduced code around helpers for both MicroPython and Pyodide as now these are more aligned * updated all dependencies and brought in latest [coincident/window](https://github.com/WebReflection/coincident#coincidentwindow) goodness to any `xworker`, preserving the `sync` previous behavior * using [@ungap/structured-clone/json](https://github.com/ungap/structured-clone#tojson) as *coincident* default `parse` and `stringify` utility to allow recursive and more complex data to travel back from the *Worker* (forward data is still fully [structured clone algorithm compatible](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)) * renamed all *plugin/s* references to *custom/s* as plugin as a word was too misleading * changed *custom types* helpers logic to allow any single node to have its own version of the interpreter wrapper, and all the extra fields it carries with it, including a way to augment every interpreter execution, among as every worker code execution * created a `custom` folder where I've landed the very first `pyscript.js` custom type * created an exhaustive test page to demonstrate the current abilities of *PyScript Next* among its ability to expose utilities that can be used to create *PyScript* plugins
This commit is contained in:
committed by
GitHub
parent
0a7e1ce0d7
commit
f6dfc5361e
@@ -9,3 +9,4 @@ pyscript.core/types/
|
||||
pyscript.core/esm/worker/xworker.js
|
||||
pyscript.core/cjs/package.json
|
||||
pyscript.core/min.js
|
||||
pyscript.core/pyscript.js
|
||||
|
||||
2
pyscript.core/.gitignore
vendored
2
pyscript.core/.gitignore
vendored
@@ -3,6 +3,6 @@ coverage/
|
||||
node_modules/
|
||||
cjs/
|
||||
!cjs/package.json
|
||||
min.js
|
||||
core.js
|
||||
esm/worker/xworker.js
|
||||
types/
|
||||
|
||||
@@ -19,7 +19,7 @@ This project requires some automatic artifact creation to:
|
||||
|
||||
* create a _Worker_ as a _Blob_ based on the same code used by this repo
|
||||
* create automatically the list of runtimes available via the module
|
||||
* create the `min.js` file used by most integration tests
|
||||
* create the `core.js` file used by most integration tests
|
||||
* create a sha256 version of the Blob content for CSP cases
|
||||
|
||||
Accordingly, to build latest project:
|
||||
|
||||
@@ -12,8 +12,6 @@ import { getRuntimeID } from "./loader.js";
|
||||
import { io } from "./interpreter/_utils.js";
|
||||
import { addAllListeners } from "./listeners.js";
|
||||
|
||||
import workerHooks from "./worker/hooks.js";
|
||||
|
||||
export const CUSTOM_SELECTORS = [];
|
||||
|
||||
/**
|
||||
@@ -26,7 +24,6 @@ export const CUSTOM_SELECTORS = [];
|
||||
* @prop {(path:string, data:ArrayBuffer) => void} writeFile an utility to write a file in the virtual FS, if available
|
||||
*/
|
||||
|
||||
const patched = new Map();
|
||||
const types = new Map();
|
||||
const waitList = new Map();
|
||||
|
||||
@@ -52,7 +49,7 @@ export const handleCustomType = (node) => {
|
||||
} = options;
|
||||
const name = getRuntimeID(runtime, version);
|
||||
const id = env || `${name}${config ? `|${config}` : ""}`;
|
||||
const { interpreter: engine, XWorker } = getDetails(
|
||||
const { interpreter: engine, XWorker: Worker } = getDetails(
|
||||
runtime,
|
||||
id,
|
||||
name,
|
||||
@@ -60,87 +57,79 @@ export const handleCustomType = (node) => {
|
||||
config,
|
||||
);
|
||||
engine.then((interpreter) => {
|
||||
if (!patched.has(id)) {
|
||||
const module = create(defaultRegistry.get(runtime));
|
||||
const {
|
||||
onBeforeRun,
|
||||
onBeforeRunAsync,
|
||||
onAfterRun,
|
||||
onAfterRunAsync,
|
||||
codeBeforeRunWorker,
|
||||
codeBeforeRunWorkerAsync,
|
||||
codeAfterRunWorker,
|
||||
codeAfterRunWorkerAsync,
|
||||
} = options;
|
||||
const module = create(defaultRegistry.get(runtime));
|
||||
|
||||
// These two loops mimic a `new Map(arrayContent)` without needing
|
||||
// the new Map overhead so that [name, [before, after]] can be easily destructured
|
||||
// and new sync or async patches become easy to add (when the logic is the same).
|
||||
const {
|
||||
onBeforeRun,
|
||||
onBeforeRunAsync,
|
||||
onAfterRun,
|
||||
onAfterRunAsync,
|
||||
codeBeforeRunWorker,
|
||||
codeBeforeRunWorkerAsync,
|
||||
codeAfterRunWorker,
|
||||
codeAfterRunWorkerAsync,
|
||||
} = options;
|
||||
|
||||
// patch sync
|
||||
for (const [name, [before, after]] of [
|
||||
["run", [onBeforeRun, onAfterRun]],
|
||||
]) {
|
||||
const method = module[name];
|
||||
module[name] = function (interpreter, code) {
|
||||
if (before) before.call(this, resolved, node);
|
||||
const result = method.call(
|
||||
this,
|
||||
interpreter,
|
||||
code,
|
||||
);
|
||||
if (after) after.call(this, resolved, node);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
const hooks = {
|
||||
beforeRun: codeBeforeRunWorker?.(),
|
||||
beforeRunAsync: codeBeforeRunWorkerAsync?.(),
|
||||
afterRun: codeAfterRunWorker?.(),
|
||||
afterRunAsync: codeAfterRunWorkerAsync?.(),
|
||||
};
|
||||
|
||||
// patch async
|
||||
for (const [name, [before, after]] of [
|
||||
["runAsync", [onBeforeRunAsync, onAfterRunAsync]],
|
||||
]) {
|
||||
const method = module[name];
|
||||
module[name] = async function (interpreter, code) {
|
||||
if (before)
|
||||
await before.call(this, resolved, node);
|
||||
const result = await method.call(
|
||||
this,
|
||||
interpreter,
|
||||
code,
|
||||
);
|
||||
if (after)
|
||||
await after.call(this, resolved, node);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
const XWorker = function XWorker(...args) {
|
||||
return Worker.apply(hooks, args);
|
||||
};
|
||||
|
||||
// setup XWorker hooks, allowing strings to be forwarded to the worker
|
||||
// whenever it's created, as functions can't possibly be serialized
|
||||
// unless these are pure with no outer scope access (or globals vars)
|
||||
// so that making it strings disambiguate about their running context.
|
||||
workerHooks.set(XWorker, {
|
||||
beforeRun: codeBeforeRunWorker,
|
||||
beforeRunAsync: codeBeforeRunWorkerAsync,
|
||||
afterRun: codeAfterRunWorker,
|
||||
afterRunAsync: codeAfterRunWorkerAsync,
|
||||
});
|
||||
// These two loops mimic a `new Map(arrayContent)` without needing
|
||||
// the new Map overhead so that [name, [before, after]] can be easily destructured
|
||||
// and new sync or async patches become easy to add (when the logic is the same).
|
||||
|
||||
module.setGlobal(interpreter, "XWorker", XWorker);
|
||||
|
||||
const resolved = {
|
||||
type,
|
||||
interpreter,
|
||||
XWorker,
|
||||
io: io.get(interpreter),
|
||||
config: structuredClone(configs.get(name)),
|
||||
run: module.run.bind(module, interpreter),
|
||||
runAsync: module.runAsync.bind(module, interpreter),
|
||||
// patch sync
|
||||
for (const [name, [before, after]] of [
|
||||
["run", [onBeforeRun, onAfterRun]],
|
||||
]) {
|
||||
const method = module[name];
|
||||
module[name] = function (interpreter, code) {
|
||||
if (before) before.call(this, resolved, node);
|
||||
const result = method.call(this, interpreter, code);
|
||||
if (after) after.call(this, resolved, node);
|
||||
return result;
|
||||
};
|
||||
|
||||
patched.set(id, resolved);
|
||||
resolve(resolved);
|
||||
}
|
||||
|
||||
onRuntimeReady?.(patched.get(id), node);
|
||||
// patch async
|
||||
for (const [name, [before, after]] of [
|
||||
["runAsync", [onBeforeRunAsync, onAfterRunAsync]],
|
||||
]) {
|
||||
const method = module[name];
|
||||
module[name] = async function (interpreter, code) {
|
||||
if (before) await before.call(this, resolved, node);
|
||||
const result = await method.call(
|
||||
this,
|
||||
interpreter,
|
||||
code,
|
||||
);
|
||||
if (after) await after.call(this, resolved, node);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
module.setGlobal(interpreter, "XWorker", XWorker);
|
||||
|
||||
const resolved = {
|
||||
type,
|
||||
interpreter,
|
||||
XWorker,
|
||||
io: io.get(interpreter),
|
||||
config: structuredClone(configs.get(name)),
|
||||
run: module.run.bind(module, interpreter),
|
||||
runAsync: module.runAsync.bind(module, interpreter),
|
||||
};
|
||||
|
||||
resolve(resolved);
|
||||
|
||||
onRuntimeReady?.(resolved, node);
|
||||
});
|
||||
}
|
||||
}
|
||||
195
pyscript.core/esm/custom/pyscript.js
Normal file
195
pyscript.core/esm/custom/pyscript.js
Normal file
@@ -0,0 +1,195 @@
|
||||
import "@ungap/with-resolvers";
|
||||
import { $ } from "basic-devtools";
|
||||
|
||||
import { define } from "../index.js";
|
||||
import { queryTarget } from "../script-handler.js";
|
||||
import { defineProperty } from "../utils.js";
|
||||
import { getText } from "../fetch-utils.js";
|
||||
|
||||
// TODO: should this utility be in core instead?
|
||||
import { robustFetch as fetch } from "./pyscript/fetch.js";
|
||||
|
||||
// append ASAP CSS to avoid showing content
|
||||
document.head.appendChild(document.createElement("style")).textContent = `
|
||||
py-script, py-config {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
(async () => {
|
||||
// create a unique identifier when/if needed
|
||||
let id = 0;
|
||||
const getID = (prefix = "py") => `${prefix}-${id++}`;
|
||||
|
||||
// find the shared config for all py-script elements
|
||||
let config;
|
||||
let pyConfig = $("py-config");
|
||||
if (pyConfig) config = pyConfig.getAttribute("src") || pyConfig.textContent;
|
||||
else {
|
||||
pyConfig = $('script[type="py"]');
|
||||
config = pyConfig?.getAttribute("config");
|
||||
}
|
||||
|
||||
if (/^https?:\/\//.test(config)) config = await fetch(config).then(getText);
|
||||
|
||||
// generic helper to disambiguate between custom element and script
|
||||
const isScript = (element) => element.tagName === "SCRIPT";
|
||||
|
||||
// helper for all script[type="py"] out there
|
||||
const before = (script) => {
|
||||
defineProperty(document, "currentScript", {
|
||||
configurable: true,
|
||||
get: () => script,
|
||||
});
|
||||
};
|
||||
|
||||
const after = () => {
|
||||
delete document.currentScript;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a generic DOM Element, tries to fetch the 'src' attribute, if present.
|
||||
* It either throws an error if the 'src' can't be fetched or it returns a fallback
|
||||
* content as source.
|
||||
*/
|
||||
const fetchSource = async (tag) => {
|
||||
if (tag.hasAttribute("src")) {
|
||||
try {
|
||||
const response = await fetch(tag.getAttribute("src"));
|
||||
return response.then(getText);
|
||||
} catch (error) {
|
||||
// TODO _createAlertBanner(err) instead ?
|
||||
alert(error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return tag.textContent;
|
||||
};
|
||||
|
||||
// common life-cycle handlers for any node
|
||||
const bootstrapNodeAndPlugins = (pyodide, element, callback, hook) => {
|
||||
if (isScript(element)) callback(element);
|
||||
for (const fn of hooks[hook]) fn(pyodide, element);
|
||||
};
|
||||
|
||||
const addDisplay = (element) => {
|
||||
const id = isScript(element) ? element.target.id : element.id;
|
||||
return `
|
||||
# this code is just for demo purpose but the basics work
|
||||
def _display(what, target="${id}", append=True):
|
||||
from js import document
|
||||
element = document.getElementById(target)
|
||||
element.textContent = what
|
||||
display = _display
|
||||
`;
|
||||
};
|
||||
|
||||
// define the module as both `<script type="py">` and `<py-script>`
|
||||
define("py", {
|
||||
config,
|
||||
env: "py-script",
|
||||
interpreter: "pyodide",
|
||||
codeBeforeRunWorker() {
|
||||
const { codeBeforeRunWorker: set } = hooks;
|
||||
const prefix = 'print("codeBeforeRunWorker")';
|
||||
return [prefix].concat(...set).join("\n");
|
||||
},
|
||||
codeAfterRunWorker() {
|
||||
const { codeAfterRunWorker: set } = hooks;
|
||||
const prefix = 'print("codeAfterRunWorker")';
|
||||
return [prefix].concat(...set).join("\n");
|
||||
},
|
||||
onBeforeRun(pyodide, element) {
|
||||
bootstrapNodeAndPlugins(pyodide, element, before, "onBeforeRun");
|
||||
pyodide.interpreter.runPython(addDisplay(element));
|
||||
},
|
||||
onBeforeRunAync(pyodide, element) {
|
||||
pyodide.interpreter.runPython(addDisplay(element));
|
||||
bootstrapNodeAndPlugins(
|
||||
pyodide,
|
||||
element,
|
||||
before,
|
||||
"onBeforeRunAync",
|
||||
);
|
||||
},
|
||||
onAfterRun(pyodide, element) {
|
||||
bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRun");
|
||||
},
|
||||
onAfterRunAsync(pyodide, element) {
|
||||
bootstrapNodeAndPlugins(pyodide, element, after, "onAfterRunAsync");
|
||||
},
|
||||
async onRuntimeReady(pyodide, element) {
|
||||
// allows plugins to do whatever they want with the element
|
||||
// before regular stuff happens in here
|
||||
for (const callback of hooks.onRuntimeReady)
|
||||
callback(pyodide, element);
|
||||
if (isScript(element)) {
|
||||
const {
|
||||
attributes: { async: isAsync, target },
|
||||
} = element;
|
||||
const hasTarget = !!target?.value;
|
||||
const show = hasTarget
|
||||
? queryTarget(target.value)
|
||||
: document.createElement("script-py");
|
||||
|
||||
if (!hasTarget) element.after(show);
|
||||
if (!show.id) show.id = getID();
|
||||
|
||||
// allows the code to retrieve the target element via
|
||||
// document.currentScript.target if needed
|
||||
defineProperty(element, "target", { value: show });
|
||||
|
||||
pyodide[`run${isAsync ? "Async" : ""}`](
|
||||
await fetchSource(element),
|
||||
);
|
||||
} else {
|
||||
// resolve PyScriptElement to allow connectedCallback
|
||||
element._pyodide.resolve(pyodide);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
class PyScriptElement extends HTMLElement {
|
||||
constructor() {
|
||||
if (!super().id) this.id = getID();
|
||||
this._pyodide = Promise.withResolvers();
|
||||
this.srcCode = "";
|
||||
this.executed = false;
|
||||
}
|
||||
async connectedCallback() {
|
||||
if (!this.executed) {
|
||||
this.executed = true;
|
||||
const { run } = await this._pyodide.promise;
|
||||
this.srcCode = await fetchSource(this);
|
||||
this.textContent = "";
|
||||
const result = run(this.srcCode);
|
||||
if (!this.textContent && result) this.textContent = result;
|
||||
this.style.display = "block";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("py-script", PyScriptElement);
|
||||
})();
|
||||
|
||||
export const hooks = {
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRun: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onBeforeRunAync: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRun: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onAfterRunAsync: new Set(),
|
||||
/** @type {Set<function>} */
|
||||
onRuntimeReady: new Set(),
|
||||
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRunWorker: new Set(),
|
||||
/** @type {Set<string>} */
|
||||
codeBeforeRunWorkerAsync: new Set(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRunWorker: new Set(),
|
||||
/** @type {Set<string>} */
|
||||
codeAfterRunWorkerAsync: new Set(),
|
||||
};
|
||||
80
pyscript.core/esm/custom/pyscript/exceptions.js
Normal file
80
pyscript.core/esm/custom/pyscript/exceptions.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const CLOSEBUTTON = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill="currentColor" width="12px"><path d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/></svg>`;
|
||||
|
||||
/**
|
||||
* These error codes are used to identify the type of error that occurred.
|
||||
* @see https://docs.pyscript.net/latest/reference/exceptions.html?highlight=errors
|
||||
*/
|
||||
export const ErrorCode = {
|
||||
GENERIC: "PY0000", // Use this only for development then change to a more specific error code
|
||||
FETCH_ERROR: "PY0001",
|
||||
FETCH_NAME_ERROR: "PY0002",
|
||||
// Currently these are created depending on error code received from fetching
|
||||
FETCH_UNAUTHORIZED_ERROR: "PY0401",
|
||||
FETCH_FORBIDDEN_ERROR: "PY0403",
|
||||
FETCH_NOT_FOUND_ERROR: "PY0404",
|
||||
FETCH_SERVER_ERROR: "PY0500",
|
||||
FETCH_UNAVAILABLE_ERROR: "PY0503",
|
||||
BAD_CONFIG: "PY1000",
|
||||
MICROPIP_INSTALL_ERROR: "PY1001",
|
||||
BAD_PLUGIN_FILE_EXTENSION: "PY2000",
|
||||
NO_DEFAULT_EXPORT: "PY2001",
|
||||
TOP_LEVEL_AWAIT: "PY9000",
|
||||
};
|
||||
|
||||
export class UserError extends Error {
|
||||
constructor(errorCode, message = "", messageType = "text") {
|
||||
super(`(${errorCode}): ${message}`);
|
||||
this.errorCode = errorCode;
|
||||
this.messageType = messageType;
|
||||
this.name = "UserError";
|
||||
}
|
||||
}
|
||||
|
||||
export class FetchError extends UserError {
|
||||
constructor(errorCode, message) {
|
||||
super(errorCode, message);
|
||||
this.name = "FetchError";
|
||||
}
|
||||
}
|
||||
|
||||
export class InstallError extends UserError {
|
||||
constructor(errorCode, message) {
|
||||
super(errorCode, message);
|
||||
this.name = "InstallError";
|
||||
}
|
||||
}
|
||||
|
||||
export function _createAlertBanner(
|
||||
message,
|
||||
level,
|
||||
messageType = "text",
|
||||
logMessage = true,
|
||||
) {
|
||||
switch (`log-${level}-${logMessage}`) {
|
||||
case "log-error-true":
|
||||
console.error(message);
|
||||
break;
|
||||
case "log-warning-true":
|
||||
console.warn(message);
|
||||
break;
|
||||
}
|
||||
|
||||
const content = messageType === "html" ? "innerHTML" : "textContent";
|
||||
const banner = Object.assign(document.createElement("div"), {
|
||||
className: `alert-banner py-${level}`,
|
||||
[content]: message,
|
||||
});
|
||||
|
||||
if (level === "warning") {
|
||||
const closeButton = Object.assign(document.createElement("button"), {
|
||||
id: "alert-close-button",
|
||||
innerHTML: CLOSEBUTTON,
|
||||
});
|
||||
|
||||
banner.appendChild(closeButton).addEventListener("click", () => {
|
||||
banner.remove();
|
||||
});
|
||||
}
|
||||
|
||||
document.body.prepend(banner);
|
||||
}
|
||||
63
pyscript.core/esm/custom/pyscript/fetch.js
Normal file
63
pyscript.core/esm/custom/pyscript/fetch.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { FetchError, ErrorCode } from "./exceptions";
|
||||
|
||||
/**
|
||||
* This is a fetch wrapper that handles any non 200 responses and throws a
|
||||
* FetchError with the right ErrorCode. This is useful because our FetchError
|
||||
* will automatically create an alert banner.
|
||||
*
|
||||
* @param {string} url - URL to fetch
|
||||
* @param {Request} [options] - options to pass to fetch
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
export async function robustFetch(url, options) {
|
||||
let response;
|
||||
|
||||
// Note: We need to wrap fetch into a try/catch block because fetch
|
||||
// throws a TypeError if the URL is invalid such as http://blah.blah
|
||||
try {
|
||||
response = await fetch(url, options);
|
||||
} catch (err) {
|
||||
const error = err;
|
||||
let errMsg;
|
||||
if (url.startsWith("http")) {
|
||||
errMsg =
|
||||
`Fetching from URL ${url} failed with error ` +
|
||||
`'${error.message}'. Are your filename and path correct?`;
|
||||
} else {
|
||||
errMsg = `PyScript: Access to local files
|
||||
(using [[fetch]] configurations in <py-config>)
|
||||
is not available when directly opening a HTML file;
|
||||
you must use a webserver to serve the additional files.
|
||||
See <a style="text-decoration: underline;" href="https://github.com/pyscript/pyscript/issues/257#issuecomment-1119595062">this reference</a>
|
||||
on starting a simple webserver with Python.
|
||||
`;
|
||||
}
|
||||
throw new FetchError(ErrorCode.FETCH_ERROR, errMsg);
|
||||
}
|
||||
|
||||
// Note that response.ok is true for 200-299 responses
|
||||
if (!response.ok) {
|
||||
const errorMsg = `Fetching from URL ${url} failed with error ${response.status} (${response.statusText}). Are your filename and path correct?`;
|
||||
switch (response.status) {
|
||||
case 404:
|
||||
throw new FetchError(ErrorCode.FETCH_NOT_FOUND_ERROR, errorMsg);
|
||||
case 401:
|
||||
throw new FetchError(
|
||||
ErrorCode.FETCH_UNAUTHORIZED_ERROR,
|
||||
errorMsg,
|
||||
);
|
||||
case 403:
|
||||
throw new FetchError(ErrorCode.FETCH_FORBIDDEN_ERROR, errorMsg);
|
||||
case 500:
|
||||
throw new FetchError(ErrorCode.FETCH_SERVER_ERROR, errorMsg);
|
||||
case 503:
|
||||
throw new FetchError(
|
||||
ErrorCode.FETCH_UNAVAILABLE_ERROR,
|
||||
errorMsg,
|
||||
);
|
||||
default:
|
||||
throw new FetchError(ErrorCode.FETCH_ERROR, errorMsg);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import xworker from "./worker/class.js";
|
||||
import { handle } from "./script-handler.js";
|
||||
import { assign } from "./utils.js";
|
||||
import { selectors, prefixes } from "./interpreters.js";
|
||||
import { CUSTOM_SELECTORS, handleCustomType } from "./custom-types.js";
|
||||
import { CUSTOM_SELECTORS, handleCustomType } from "./custom.js";
|
||||
import { listener, addAllListeners } from "./listeners.js";
|
||||
|
||||
export { define, whenDefined } from "./custom-types.js";
|
||||
export { define, whenDefined } from "./custom.js";
|
||||
export const XWorker = xworker();
|
||||
|
||||
const INTERPRETER_SELECTORS = selectors.join(",");
|
||||
|
||||
@@ -7,6 +7,12 @@ export const run = (interpreter, code) => interpreter.runPython(clean(code));
|
||||
export const runAsync = (interpreter, code) =>
|
||||
interpreter.runPythonAsync(clean(code));
|
||||
|
||||
export const setGlobal = (interpreter, name, value) =>
|
||||
interpreter.globals.set(name, value);
|
||||
|
||||
export const deleteGlobal = (interpreter, name) =>
|
||||
interpreter.globals.delete(name);
|
||||
|
||||
export const writeFile = ({ FS }, path, buffer) =>
|
||||
writeFileUtil(FS, path, buffer);
|
||||
/* c8 ignore stop */
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { fetchPaths, stdio } from "./_utils.js";
|
||||
import { run, runAsync, writeFile } from "./_python.js";
|
||||
import {
|
||||
run,
|
||||
runAsync,
|
||||
setGlobal,
|
||||
deleteGlobal,
|
||||
writeFile,
|
||||
} from "./_python.js";
|
||||
|
||||
const type = "micropython";
|
||||
|
||||
@@ -7,7 +13,7 @@ const type = "micropython";
|
||||
/* c8 ignore start */
|
||||
export default {
|
||||
type,
|
||||
module: (version = "1.20.0-239") =>
|
||||
module: (version = "1.20.0-253") =>
|
||||
`https://cdn.jsdelivr.net/npm/@micropython/micropython-webassembly-pyscript@${version}/micropython.mjs`,
|
||||
async engine({ loadMicroPython }, config, url) {
|
||||
const { stderr, stdout, get } = stdio();
|
||||
@@ -16,16 +22,8 @@ export default {
|
||||
if (config.fetch) await fetchPaths(this, runtime, config.fetch);
|
||||
return runtime;
|
||||
},
|
||||
setGlobal(interpreter, name, value) {
|
||||
const id = `__pyscript_${this.type}_${name}`;
|
||||
globalThis[id] = value;
|
||||
this.run(interpreter, `from js import ${id};${name}=${id};`);
|
||||
},
|
||||
deleteGlobal(interpreter, name) {
|
||||
const id = `__pyscript_${this.type}_${name}`;
|
||||
this.run(interpreter, `del ${id};del ${name}`);
|
||||
delete globalThis[id];
|
||||
},
|
||||
setGlobal,
|
||||
deleteGlobal,
|
||||
run,
|
||||
runAsync,
|
||||
writeFile,
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { fetchPaths, stdio } from "./_utils.js";
|
||||
import { run, runAsync, writeFile } from "./_python.js";
|
||||
import {
|
||||
run,
|
||||
runAsync,
|
||||
setGlobal,
|
||||
deleteGlobal,
|
||||
writeFile,
|
||||
} from "./_python.js";
|
||||
|
||||
const type = "pyodide";
|
||||
|
||||
@@ -24,12 +30,8 @@ export default {
|
||||
}
|
||||
return interpreter;
|
||||
},
|
||||
setGlobal(interpreter, name, value) {
|
||||
interpreter.globals.set(name, value);
|
||||
},
|
||||
deleteGlobal(interpreter, name) {
|
||||
interpreter.globals.delete(name);
|
||||
},
|
||||
setGlobal,
|
||||
deleteGlobal,
|
||||
run,
|
||||
runAsync,
|
||||
writeFile,
|
||||
|
||||
@@ -12,7 +12,7 @@ const getRoot = (script) => {
|
||||
return parent;
|
||||
};
|
||||
|
||||
const queryTarget = (script, idOrSelector) => {
|
||||
export const queryTarget = (script, idOrSelector) => {
|
||||
const root = getRoot(script);
|
||||
return root.getElementById(idOrSelector) || $(idOrSelector, root);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
// Please check via `npm run size` that worker code is not much
|
||||
// bigger than it used to be before any changes is applied to this file.
|
||||
|
||||
import coincident from "coincident/structured";
|
||||
import * as JSON from "@ungap/structured-clone/json";
|
||||
import coincident from "coincident/window";
|
||||
|
||||
import { create } from "../utils.js";
|
||||
import { registry } from "../interpreters.js";
|
||||
@@ -37,9 +38,15 @@ const add = (type, fn) => {
|
||||
);
|
||||
};
|
||||
|
||||
const { proxy: sync, window, isWindowProxy } = coincident(self, JSON);
|
||||
|
||||
const xworker = {
|
||||
// allows synchronous utilities between this worker and the main thread
|
||||
sync: coincident(self),
|
||||
sync,
|
||||
// allow access to the main thread world
|
||||
window,
|
||||
// allow introspection for foreign (main thread) refrences
|
||||
isWindowProxy,
|
||||
// standard worker related events / features
|
||||
onerror() {},
|
||||
onmessage() {},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import coincident from "coincident/structured";
|
||||
import * as JSON from "@ungap/structured-clone/json";
|
||||
import coincident from "coincident/window";
|
||||
import xworker from "./xworker.js";
|
||||
import { assign, defineProperties, absoluteURL } from "../utils.js";
|
||||
import { getText } from "../fetch-utils.js";
|
||||
import workerHooks from "./hooks.js";
|
||||
|
||||
/**
|
||||
* @typedef {Object} WorkerOptions custom configuration
|
||||
@@ -19,9 +19,9 @@ export default (...args) =>
|
||||
* @returns {Worker}
|
||||
*/
|
||||
function XWorker(url, options) {
|
||||
const hooks = workerHooks.get(XWorker);
|
||||
const worker = xworker();
|
||||
const { postMessage } = worker;
|
||||
const hooks = this instanceof XWorker ? void 0 : this;
|
||||
if (args.length) {
|
||||
const [type, version] = args;
|
||||
options = assign({}, options || { type, version });
|
||||
@@ -39,7 +39,7 @@ export default (...args) =>
|
||||
),
|
||||
},
|
||||
sync: {
|
||||
value: coincident(worker),
|
||||
value: coincident(worker, JSON).proxy,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export default new WeakMap();
|
||||
@@ -2,7 +2,7 @@
|
||||
"imports": {
|
||||
"http://pyodide": "./test/mocked/pyodide.mjs",
|
||||
"https://cdn.jsdelivr.net/pyodide/v0.23.2/full/pyodide.mjs": "./test/mocked/pyodide.mjs",
|
||||
"https://cdn.jsdelivr.net/npm/@micropython/micropython-webassembly-pyscript@1.20.0-239/micropython.mjs": "./test/mocked/micropython.mjs",
|
||||
"https://cdn.jsdelivr.net/npm/@micropython/micropython-webassembly-pyscript@1.20.0-253/micropython.mjs": "./test/mocked/micropython.mjs",
|
||||
"https://cdn.jsdelivr.net/npm/basic-toml@0.3.1/es.js": "./test/mocked/toml.mjs"
|
||||
}
|
||||
}
|
||||
|
||||
87
pyscript.core/package-lock.json
generated
87
pyscript.core/package-lock.json
generated
@@ -9,9 +9,10 @@
|
||||
"version": "0.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ungap/structured-clone": "^1.2.0",
|
||||
"@ungap/with-resolvers": "^0.1.0",
|
||||
"basic-devtools": "^0.1.6",
|
||||
"coincident": "^0.4.1"
|
||||
"coincident": "^0.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@node-loader/import-maps": "^1.1.0",
|
||||
@@ -19,10 +20,10 @@
|
||||
"@rollup/plugin-terser": "^0.4.3",
|
||||
"ascjs": "^5.0.1",
|
||||
"c8": "^8.0.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint": "^8.43.0",
|
||||
"linkedom": "^0.14.26",
|
||||
"rollup": "^3.25.1",
|
||||
"static-handler": "^0.4.1",
|
||||
"static-handler": "^0.4.2",
|
||||
"typescript": "^5.1.3"
|
||||
}
|
||||
},
|
||||
@@ -92,9 +93,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "8.42.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz",
|
||||
"integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==",
|
||||
"version": "8.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz",
|
||||
"integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
@@ -348,9 +349,9 @@
|
||||
"integrity": "sha512-g7f0IkJdPW2xhY7H4iE72DAsIyfuwEFc6JWc2tYFwKDMWWAF699vGjrM348cwQuOXgHpe1gWFe+Eiyjx/ewvvw=="
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"version": "8.9.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
|
||||
"integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@@ -534,9 +535,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/coincident": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/coincident/-/coincident-0.4.1.tgz",
|
||||
"integrity": "sha512-CiHZVx/eUano0G+sSc0S0pXTBH51q0W7pMw1ScRa57BgfBw8l19pMW2Z6sVDHjOih5c9NYrP0budO8SslJv2sA==",
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/coincident/-/coincident-0.7.2.tgz",
|
||||
"integrity": "sha512-/hNcCDfousAW04kxOeuwzrM+gIQZpFDCYKXvDex/DJxvIcDL57WM32zGO9t8xT34cpBJUl0GNEclpaOx7/rk4A==",
|
||||
"dependencies": {
|
||||
"@ungap/structured-clone": "^1.2.0"
|
||||
}
|
||||
@@ -764,15 +765,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.42.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz",
|
||||
"integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==",
|
||||
"version": "8.43.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz",
|
||||
"integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.4.0",
|
||||
"@eslint/eslintrc": "^2.0.3",
|
||||
"@eslint/js": "8.42.0",
|
||||
"@eslint/js": "8.43.0",
|
||||
"@humanwhocodes/config-array": "^0.11.10",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
@@ -1796,12 +1797,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/static-handler": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/static-handler/-/static-handler-0.4.1.tgz",
|
||||
"integrity": "sha512-atl+UofbqCECS4Ag1ZZIjC606I1I40CuDsdq4YQ5Vq3vTsUbvqVaNG4evFsKktlnPcEm217vBUSEFKyi9lsJ8Q==",
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/static-handler/-/static-handler-0.4.2.tgz",
|
||||
"integrity": "sha512-Szbk521mneb5npxg3SEyoufsHr2osAzxMy71W2zFCzLB8wLhHYvKUDCMkLY6imi+fIqkpfas3rzXGBQf99aeEA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mime-types": "^2.1.34"
|
||||
"mime-types": "^2.1.35"
|
||||
},
|
||||
"bin": {
|
||||
"static-handler": "static-handler.cjs"
|
||||
@@ -1873,9 +1874,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.18.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.18.0.tgz",
|
||||
"integrity": "sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==",
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.18.1.tgz",
|
||||
"integrity": "sha512-j1n0Ao919h/Ai5r43VAnfV/7azUYW43GPxK7qSATzrsERfW7+y2QW9Cp9ufnRF5CQUWbnLSo7UJokSWCqg4tsQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
@@ -2118,9 +2119,9 @@
|
||||
}
|
||||
},
|
||||
"@eslint/js": {
|
||||
"version": "8.42.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz",
|
||||
"integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==",
|
||||
"version": "8.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz",
|
||||
"integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==",
|
||||
"dev": true
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
@@ -2306,9 +2307,9 @@
|
||||
"integrity": "sha512-g7f0IkJdPW2xhY7H4iE72DAsIyfuwEFc6JWc2tYFwKDMWWAF699vGjrM348cwQuOXgHpe1gWFe+Eiyjx/ewvvw=="
|
||||
},
|
||||
"acorn": {
|
||||
"version": "8.8.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
|
||||
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
|
||||
"version": "8.9.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
|
||||
"integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-jsx": {
|
||||
@@ -2447,9 +2448,9 @@
|
||||
}
|
||||
},
|
||||
"coincident": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/coincident/-/coincident-0.4.1.tgz",
|
||||
"integrity": "sha512-CiHZVx/eUano0G+sSc0S0pXTBH51q0W7pMw1ScRa57BgfBw8l19pMW2Z6sVDHjOih5c9NYrP0budO8SslJv2sA==",
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/coincident/-/coincident-0.7.2.tgz",
|
||||
"integrity": "sha512-/hNcCDfousAW04kxOeuwzrM+gIQZpFDCYKXvDex/DJxvIcDL57WM32zGO9t8xT34cpBJUl0GNEclpaOx7/rk4A==",
|
||||
"requires": {
|
||||
"@ungap/structured-clone": "^1.2.0"
|
||||
}
|
||||
@@ -2615,15 +2616,15 @@
|
||||
"dev": true
|
||||
},
|
||||
"eslint": {
|
||||
"version": "8.42.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz",
|
||||
"integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==",
|
||||
"version": "8.43.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz",
|
||||
"integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.4.0",
|
||||
"@eslint/eslintrc": "^2.0.3",
|
||||
"@eslint/js": "8.42.0",
|
||||
"@eslint/js": "8.43.0",
|
||||
"@humanwhocodes/config-array": "^0.11.10",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
@@ -3363,12 +3364,12 @@
|
||||
}
|
||||
},
|
||||
"static-handler": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/static-handler/-/static-handler-0.4.1.tgz",
|
||||
"integrity": "sha512-atl+UofbqCECS4Ag1ZZIjC606I1I40CuDsdq4YQ5Vq3vTsUbvqVaNG4evFsKktlnPcEm217vBUSEFKyi9lsJ8Q==",
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/static-handler/-/static-handler-0.4.2.tgz",
|
||||
"integrity": "sha512-Szbk521mneb5npxg3SEyoufsHr2osAzxMy71W2zFCzLB8wLhHYvKUDCMkLY6imi+fIqkpfas3rzXGBQf99aeEA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mime-types": "^2.1.34"
|
||||
"mime-types": "^2.1.35"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
@@ -3413,9 +3414,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"terser": {
|
||||
"version": "5.18.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.18.0.tgz",
|
||||
"integrity": "sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==",
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.18.1.tgz",
|
||||
"integrity": "sha512-j1n0Ao919h/Ai5r43VAnfV/7azUYW43GPxK7qSATzrsERfW7+y2QW9Cp9ufnRF5CQUWbnLSo7UJokSWCqg4tsQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
{
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "./cjs/index.js",
|
||||
"types": "./types/index.d.ts",
|
||||
"scripts": {
|
||||
"server": "npx static-handler --cors --coep --coop --corp .",
|
||||
"build": "npm run rollup:xworker && npm run rollup:min && eslint esm/ && npm run ts && npm run cjs && npm run test",
|
||||
"build": "npm run rollup:xworker && npm run rollup:core && npm run rollup:pyscript && eslint esm/ && npm run ts && npm run cjs && npm run test",
|
||||
"cjs": "ascjs --no-default esm cjs",
|
||||
"rollup:min": "rollup --config rollup/min.config.js",
|
||||
"rollup:core": "rollup --config rollup/core.config.js",
|
||||
"rollup:pyscript": "rollup --config rollup/pyscript.config.js",
|
||||
"rollup:xworker": "rollup --config rollup/xworker.config.js",
|
||||
"test": "c8 --100 node --experimental-loader @node-loader/import-maps test/index.js ",
|
||||
"test:html": "npm run test && c8 report -r html",
|
||||
"coverage": "mkdir -p ./coverage; c8 report --reporter=text-lcov > ./coverage/lcov.info",
|
||||
"size": "npm run size:module && npm run size:worker",
|
||||
"size:module": "echo module is $(cat min.js | brotli | wc -c) bytes once compressed",
|
||||
"size:module": "echo module is $(cat core.js | brotli | wc -c) bytes once compressed",
|
||||
"size:worker": "echo worker is $(cat esm/worker/xworker.js | brotli | wc -c) bytes once compressed",
|
||||
"ts": "tsc -p ."
|
||||
},
|
||||
@@ -27,10 +28,10 @@
|
||||
"@rollup/plugin-terser": "^0.4.3",
|
||||
"ascjs": "^5.0.1",
|
||||
"c8": "^8.0.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint": "^8.43.0",
|
||||
"linkedom": "^0.14.26",
|
||||
"rollup": "^3.25.1",
|
||||
"static-handler": "^0.4.1",
|
||||
"static-handler": "^0.4.2",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"module": "./esm/index.js",
|
||||
@@ -43,13 +44,14 @@
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"unpkg": "min.js",
|
||||
"unpkg": "core.js",
|
||||
"dependencies": {
|
||||
"@ungap/structured-clone": "^1.2.0",
|
||||
"@ungap/with-resolvers": "^0.1.0",
|
||||
"basic-devtools": "^0.1.6",
|
||||
"coincident": "^0.4.1"
|
||||
"coincident": "^0.7.2"
|
||||
},
|
||||
"worker": {
|
||||
"blob": "sha256-obDch1OaxOZKhaCpSW9vEAHJk01N+9kYvSiGkNmeJRU="
|
||||
"blob": "sha256-llcU6BJSNpU+9XN3CXzTrR1Js0KQaXygnnKiD039CTQ="
|
||||
}
|
||||
}
|
||||
|
||||
2
pyscript.core/pyscript.js
Normal file
2
pyscript.core/pyscript.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
||||
// This file generates /min.js minified version of the module, which is
|
||||
// This file generates /core.js minified version of the module, which is
|
||||
// the default exported as npm entry.
|
||||
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
@@ -13,6 +13,6 @@ export default {
|
||||
plugins: process.env.NO_MIN ? [nodeResolve()] : [nodeResolve(), terser()],
|
||||
output: {
|
||||
esModule: true,
|
||||
file: "./min.js",
|
||||
file: "./core.js",
|
||||
},
|
||||
};
|
||||
18
pyscript.core/rollup/pyscript.config.js
Normal file
18
pyscript.core/rollup/pyscript.config.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// This file generates /core.js minified version of the module, which is
|
||||
// the default exported as npm entry.
|
||||
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
import terser from "@rollup/plugin-terser";
|
||||
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
createRequire(import.meta.url)("./build_xworker.cjs");
|
||||
|
||||
export default {
|
||||
input: "./esm/custom/pyscript.js",
|
||||
plugins: process.env.NO_MIN ? [nodeResolve()] : [nodeResolve(), terser()],
|
||||
output: {
|
||||
esModule: true,
|
||||
file: "./pyscript.js",
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
// This file generates /min.js minified version of the module, which is
|
||||
// This file generates /core.js minified version of the module, which is
|
||||
// the default exported as npm entry.
|
||||
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="pyodide" async>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="micropython">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="micropython" config="./fetch.toml">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="pyodide">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="pyodide">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="pyodide" version="0.23.2">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="pyodide" config="./config.toml">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="pyodide" config="./config.json">
|
||||
|
||||
@@ -46,6 +46,26 @@ buf = io.BytesIO()
|
||||
plt.savefig(buf, format="png")
|
||||
buf.seek(0)
|
||||
|
||||
js.xworker.postMessage(
|
||||
"data:image/png;base64," + base64.b64encode(buf.read()).decode("UTF-8")
|
||||
)
|
||||
# how it was (including main thread counter part)
|
||||
# js.xworker.postMessage(
|
||||
# "data:image/png;base64," + base64.b64encode(buf.read()).decode("UTF-8")
|
||||
# )
|
||||
|
||||
# how it is now via structured coincident/window
|
||||
document = xworker.window.document
|
||||
img = document.createElement("img")
|
||||
img.style.transform = "scale(.5)"
|
||||
img.src = "data:image/png;base64," + base64.b64encode(buf.read()).decode("UTF-8")
|
||||
|
||||
# document.body.append(img) fails for some reason I don't understand
|
||||
# same would be for document.querySelector("#image").append(img)
|
||||
# those would work in any JS counterpart though ... but for demo sake:
|
||||
document.querySelector("#image").innerHTML = img.outerHTML
|
||||
|
||||
# about pyodide issue
|
||||
print(xworker.window.Array(1, 2)) # this works
|
||||
xworker.window.console.log(1, 2) # this works too
|
||||
# xworker.window.console.log(xworker.window.Array(1, 2)) # this doesn't
|
||||
# xworker.window.console.log([1, 2]) # also this doesn't
|
||||
|
||||
# xworker.window.console.log(to_js({})) # this doesn't neither
|
||||
|
||||
@@ -6,19 +6,23 @@
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="micropython">
|
||||
def show_image(event):
|
||||
from js import document
|
||||
img = document.createElement("img")
|
||||
img.style.transform = "scale(.5)"
|
||||
img.src = event.data
|
||||
document.body.appendChild(img)
|
||||
|
||||
w = XWorker('./matplot.py', **{'type': 'pyodide', 'config': './config.toml'})
|
||||
w.onmessage = show_image
|
||||
|
||||
# xworker.window made the following completely unnecessary
|
||||
|
||||
# def show_image(event):
|
||||
# from js import document
|
||||
# img = document.createElement("img")
|
||||
# img.style.transform = "scale(.5)"
|
||||
# img.src = event.data
|
||||
# document.querySelector("#image").append(img)
|
||||
|
||||
# w.onmessage = show_image
|
||||
</script>
|
||||
<div id="image"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="micropython">
|
||||
|
||||
@@ -6,4 +6,12 @@ export const loadMicroPython = () => ({
|
||||
python.target = document.currentScript.target;
|
||||
}
|
||||
},
|
||||
globals: {
|
||||
set(name, value) {
|
||||
globalThis[name] = value;
|
||||
},
|
||||
delete(name) {
|
||||
delete globalThis[name];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="pyodide">
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
{
|
||||
"imports": {
|
||||
"basic-devtools": "../node_modules/basic-devtools/esm/index.js",
|
||||
"coincident/structured": "../node_modules/coincident/structured.js",
|
||||
"@ungap/with-resolvers": "../node_modules/@ungap/with-resolvers/index.js"
|
||||
"coincident/window": "../node_modules/coincident/window.js",
|
||||
"@ungap/with-resolvers": "../node_modules/@ungap/with-resolvers/index.js",
|
||||
"@ungap/structured-clone/json": "../node_modules/@ungap/structured-clone/esm/json.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{ "imports": { "@pyscript/core": "../../min.js" } }
|
||||
{ "imports": { "@pyscript/core": "../../core.js" } }
|
||||
</script>
|
||||
<script type="module">
|
||||
import { define, whenDefined } from "@pyscript/core";
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{ "imports": { "@pyscript/core": "../../min.js" } }
|
||||
{ "imports": { "@pyscript/core": "../../core.js" } }
|
||||
</script>
|
||||
<script type="module">
|
||||
import { define } from "@pyscript/core";
|
||||
|
||||
@@ -3,24 +3,30 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>PyScript</title>
|
||||
<style>
|
||||
py-script {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{ "imports": { "@pyscript/core": "../../min.js" } }
|
||||
<title>PyScript Next</title>
|
||||
<script type="importmap">{ "imports": { "@pyscript/element": "../../pyscript.js" } }</script>
|
||||
<script type="module">
|
||||
// how would PyScript plugins add their own behavior?
|
||||
import { hooks } from "@pyscript/element";
|
||||
let counter = 0;
|
||||
hooks.onBeforeRun.add((pyodide, { localName }) => {
|
||||
// console.log(++counter, 'elements so far', localName, pyodide);
|
||||
});
|
||||
</script>
|
||||
<script type="module" src="py-script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<fieldset>
|
||||
<legend id="dary"></legend>
|
||||
Something something about something ...
|
||||
</fieldset>
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = "../"
|
||||
to_folder = "./"
|
||||
files = ["a.py", "b.py"]
|
||||
</py-config>
|
||||
|
||||
<!-- <py-script next> -->
|
||||
<py-script>
|
||||
import js
|
||||
import a, b
|
||||
@@ -29,8 +35,24 @@
|
||||
'Hello Web!'
|
||||
</py-script>
|
||||
<py-script>
|
||||
# note the target is this element itself
|
||||
display('second <py-script>')
|
||||
</py-script>
|
||||
<py-script>
|
||||
# note this is late to the party simply because
|
||||
# pyodide needs to be bootstrapped in the Worker too
|
||||
XWorker('../a.py')
|
||||
'OK'
|
||||
</py-script>
|
||||
|
||||
<!-- <script type="py"> -->
|
||||
<script type="py">
|
||||
# not the target is inferred as companion element
|
||||
display('first <script type="py">')
|
||||
</script>
|
||||
<script type="py">
|
||||
# note the target here is different
|
||||
display('second <script type="py">', target="dary")
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { define } from "@pyscript/core";
|
||||
|
||||
// append ASAP CSS to avoid showing content
|
||||
document.head.appendChild(document.createElement("style")).textContent = `
|
||||
py-script, py-config {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
// create a unique identifier when/if needed
|
||||
let id = 0;
|
||||
const getID = (prefix = "py-script") => `${prefix}-${id++}`;
|
||||
|
||||
let bootstrap = true,
|
||||
XWorker,
|
||||
sharedRuntime;
|
||||
const sharedPyodide = new Promise((resolve) => {
|
||||
const pyConfig = document.querySelector("py-config");
|
||||
const config = pyConfig?.getAttribute("src") || pyConfig?.textContent;
|
||||
define("py", {
|
||||
config,
|
||||
interpreter: "pyodide",
|
||||
codeBeforeRunWorker: `print('codeBeforeRunWorker')`,
|
||||
codeAfterRunWorker: `print('codeAfterRunWorker')`,
|
||||
onBeforeRun(pyodide, node) {
|
||||
pyodide.interpreter.globals.set("XWorker", XWorker);
|
||||
console.log("onBeforeRun", sharedRuntime === pyodide, node);
|
||||
},
|
||||
onAfterRun(pyodide, node) {
|
||||
console.log("onAfterRun", sharedRuntime === pyodide, node);
|
||||
},
|
||||
async onRuntimeReady(pyodide) {
|
||||
// bootstrap the shared runtime once
|
||||
// as each node as plugin gets onRuntimeReady called once
|
||||
// because no custom-element is strictly needed
|
||||
if (bootstrap) {
|
||||
bootstrap = false;
|
||||
sharedRuntime = pyodide;
|
||||
XWorker = pyodide.XWorker;
|
||||
pyodide.io.stdout = (message) => {
|
||||
console.log("🐍", pyodide.type, message);
|
||||
};
|
||||
// do any module / JS injection in here such as
|
||||
// Element, display, and friends ... then:
|
||||
resolve(pyodide);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/** @type {WeakSet<PyScriptElement>} */
|
||||
const known = new WeakSet();
|
||||
|
||||
class PyScriptElement extends HTMLElement {
|
||||
constructor() {
|
||||
if (!super().id) this.id = getID();
|
||||
}
|
||||
async connectedCallback() {
|
||||
if (!known.has(this)) {
|
||||
known.add(this);
|
||||
// sharedPyodide contains various helpers including run and runAsync
|
||||
const { run } = await sharedPyodide;
|
||||
// do any stuff needed to finalize this element bootstrap
|
||||
// (i.e. check src attribute and so on)
|
||||
this.replaceChildren(run(this.textContent) || "");
|
||||
// reveal the node on the page
|
||||
this.style.display = "block";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("py-script", PyScriptElement);
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>python events</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="pyodide">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
{
|
||||
"imports": {
|
||||
"basic-devtools": "../node_modules/basic-devtools/esm/index.js",
|
||||
"coincident/structured": "../node_modules/coincident/structured.js",
|
||||
"@ungap/with-resolvers": "../node_modules/@ungap/with-resolvers/index.js"
|
||||
"coincident/window": "../node_modules/coincident/window.js",
|
||||
"@ungap/with-resolvers": "../node_modules/@ungap/with-resolvers/index.js",
|
||||
"@ungap/structured-clone/json": "../node_modules/@ungap/structured-clone/esm/json.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
<script type="module">
|
||||
customElements.define(
|
||||
"shadow-dom",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script type="module" src="../min.js"></script>
|
||||
<script type="module" src="../core.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<table cellspacing="2" cellpadding="2">
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
{
|
||||
"imports": {
|
||||
"basic-devtools": "../node_modules/basic-devtools/esm/index.js",
|
||||
"coincident/structured": "../node_modules/coincident/structured.js",
|
||||
"@ungap/with-resolvers": "../node_modules/@ungap/with-resolvers/index.js"
|
||||
"coincident/window": "../node_modules/coincident/window.js",
|
||||
"@ungap/with-resolvers": "../node_modules/@ungap/with-resolvers/index.js",
|
||||
"@ungap/structured-clone/json": "../node_modules/@ungap/structured-clone/esm/json.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
{
|
||||
"imports": {
|
||||
"basic-devtools": "../node_modules/basic-devtools/esm/index.js",
|
||||
"coincident/structured": "../node_modules/coincident/structured.js",
|
||||
"@ungap/with-resolvers": "../node_modules/@ungap/with-resolvers/index.js"
|
||||
"coincident/window": "../node_modules/coincident/window.js",
|
||||
"@ungap/with-resolvers": "../node_modules/@ungap/with-resolvers/index.js",
|
||||
"@ungap/structured-clone/json": "../node_modules/@ungap/structured-clone/esm/json.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
{
|
||||
"imports": {
|
||||
"basic-devtools": "../../node_modules/basic-devtools/esm/index.js",
|
||||
"coincident/structured": "../../node_modules/coincident/structured.js",
|
||||
"coincident/window": "../../node_modules/coincident/window.js",
|
||||
"@ungap/with-resolvers": "../../node_modules/@ungap/with-resolvers/index.js",
|
||||
"@ungap/structured-clone/json": "../../node_modules/@ungap/structured-clone/esm/json.js",
|
||||
"@pyscript/core": "../../esm/index.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
{
|
||||
"imports": {
|
||||
"basic-devtools": "../../node_modules/basic-devtools/esm/index.js",
|
||||
"coincident/structured": "../../node_modules/coincident/structured.js",
|
||||
"coincident/window": "../../node_modules/coincident/window.js",
|
||||
"@ungap/with-resolvers": "../../node_modules/@ungap/with-resolvers/index.js",
|
||||
"@ungap/structured-clone/json": "../../node_modules/@ungap/structured-clone/esm/json.js",
|
||||
"@pyscript/core": "../../esm/index.js"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user