mirror of
https://github.com/pyscript/pyscript.git
synced 2026-02-28 14:01:35 -05:00
WIP: Bringing PyScript.next PoC to the main project (#1507)
* kill unwrapped_remote (#1490) * kill unwrapped_remote * linting * don't use callKwargs for python plugins * fix tests and improve types * Bringing PyScript.next PoC to the main project * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Madhur Tandon <20173739+madhur-tandon@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
4467898473
commit
339e40063a
12
pyscript.core/.eslintrc.json
Normal file
12
pyscript.core/.eslintrc.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2022": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {}
|
||||
}
|
||||
30
pyscript.core/.github/workflows/node.js.yml
vendored
Normal file
30
pyscript.core/.github/workflows/node.js.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "npm"
|
||||
- run: npm ci
|
||||
- run: npm run build --if-present
|
||||
- run: npm test
|
||||
- run: npm run coverage --if-present
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
9
pyscript.core/.gitignore
vendored
Normal file
9
pyscript.core/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
.DS_Store
|
||||
.nyc_output
|
||||
*.log
|
||||
coverage/
|
||||
node_modules/
|
||||
cjs/
|
||||
!cjs/package.json
|
||||
min.js
|
||||
esm/worker/xworker.js
|
||||
19
pyscript.core/.npmignore
Normal file
19
pyscript.core/.npmignore
Normal file
@@ -0,0 +1,19 @@
|
||||
.DS_Store
|
||||
.nyc_output
|
||||
.eslintrc.json
|
||||
.github/
|
||||
.travis.yml
|
||||
.eslintrc.json
|
||||
*.log
|
||||
coverage/
|
||||
micropython/
|
||||
node_modules/
|
||||
pyscript/
|
||||
rollup/
|
||||
test/
|
||||
index.html
|
||||
node.importmap
|
||||
sw.js
|
||||
tsconfig.json
|
||||
cjs/worker/_template.js
|
||||
esm/worker/_template.js
|
||||
1
pyscript.core/.npmrc
Normal file
1
pyscript.core/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
package-lock=true
|
||||
120
pyscript.core/README.md
Normal file
120
pyscript.core/README.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# pyscript
|
||||
|
||||
[](https://github.com/WebReflection/python/actions/workflows/node.js.yml) [](https://coveralls.io/github/WebReflection/python?branch=api)
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
Please check the PR around current API and help me out changing or improving it, thank you!
|
||||
https://github.com/WebReflection/python/blob/c50ab771ffb14d2fbb499219d67200cf02e0cd5f/API.md
|
||||
|
||||
---
|
||||
|
||||
### Development
|
||||
|
||||
The working folder (source code of truth) is the `./esm` one, while the `./cjs` is populated as dual module and to test (but it's 1:1 code, no trnaspilation except for imports/exports).
|
||||
|
||||
Please be sure the following line is prsent in your `.git/hooks/pre-commit` file to always test and check coverage before committing.
|
||||
|
||||
```sh
|
||||
exec npx c8 --100 npm test > /dev/null
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The goal of this repository is to explore a few _PyScript_ related topics, such as:
|
||||
|
||||
- **bootstrap time**: do we need to do everything we currently do by default?
|
||||
- **ESM / npm module**: why are we still attaching things to the global?
|
||||
- **dependencies**: do we need to include a whole _TOML_ v0.4 parser by default?
|
||||
- **DX / UX**: what if we simplify most pain points we already know while solving all problems we also will or already have moving forward?
|
||||
- **YAGNI**: thoughts about current features that look more like a workaround
|
||||
|
||||
Following each topic is discussed with findings, ideas, and proof of concepts.
|
||||
|
||||
## Bootstrap Time
|
||||
|
||||
Once bundled, the current PoC weigths **2.7K** (~1K compressed) as opposite of current _pyscript.js_ which is **1.2MB** (~230K compressed).
|
||||
By no mean the current PoC can replace all _pyscript.js_ features, but I believe a smaller and better architected core could help creating even more projects around our offer.
|
||||
|
||||
**Notable findings**
|
||||
|
||||
- even if no `<py-config>` is defined, we include a whole TOML library and we import _micropip_, _pyparsing_ and _pypackaging_ for an "_Hello World_"
|
||||
- there's a lot of code which only goal is to handle _HTML_ issues with parsed content and entities and _IDs_. This PoC never uses, neither needs, _IDs_ at all and it makes any target avilable within the Python code instead of needing to have discoverable _IDs_ (see recent MR around _IDs_ in Shadow DOM too) ... should we re-consider the need for _IDs_ and drop all the code around this topic in the (hopefully) near future?
|
||||
- there's no need to normalize any _python_ output with the usage of `<script>` because if the indentation is not broken the interpreter works just fine (this is extra code we could just remove from our codebase)
|
||||
- despite our possible improvements around previous point, the "_elephant in the room_" is _pyodide_ initialization time. We can save bandwidth and some millisencods but the main thread is blocked for ~1.5 seconds when _pyodide_ kicks in. Is there anything we can do to at least move its initialization elsewhere? (e.g. _Worker_)
|
||||
- adding a _cache all_ strategy for an extremely simple _Service Worker_ made bootstrap time predictable, either via this PoC or via _pyscript_. Is there any particular reason we are not using a _Service Worker_ everywhere we offer or demo _pyscript_?
|
||||
- once initialized, both _pyodide_ and _micropython_ expose the version ... why do we attach a version before loading these runtimes?
|
||||
|
||||
## As ESM / `npm` module
|
||||
|
||||
Both _pyscript_, current _micropython_ file, and (IIRC) _pyodide_ leak something on the _global_ context. Not only this is generally considered a bad practice but it also plays badly with the idea that multiple interpreters _could_ coexist in the same page (already possible with this PoC).
|
||||
|
||||
Not being a module also means we don't get to benefit from easy install and all _CDNs_ that for free would allow any developer to use our offer in the wild, including local projects bootstrapped via all the usual/common Web related tools.
|
||||
|
||||
What would it take to actually use _pyscript_ as a module or why are we still using these fairly outdated and problematic/conflicting practices instead?
|
||||
|
||||
## Dependencies
|
||||
|
||||
We all agree that _TOML_ is likely the preferred choice for Python users to setup the environment but it's not clear why we need to embed a 3rd party fork that parses the whole _TOML_ standard within our code out of the box, or why we do initialize a package manager when it's not even needed, asking the core to download extra stuff by default even for cases such stuff was not desired nor required.
|
||||
|
||||
Accordingly, if there's no particular reason I am not aware about, should we include via dynamic `import` the _TOML_ parser **and** botstrap `micropip` and the rest only if the config exists and not by default?
|
||||
|
||||
If it has to be there by default, why isn't the package manager already embedded within _pyodide_ or _micropython_ (`mip`) and initialized internally?
|
||||
|
||||
Last, but not least, as our config consist of 3 or 4 fields and nothing else, do we really need a whole TOML 3rd party parer instead of a super-simple one that just gets the job done, in case we want to embed that in core? Reading _config_'s specs it feels like we're slightly overdoing it in there and we also have issues with the config order (which btw could also be the case for JSON configurations).
|
||||
|
||||
## DX / UX
|
||||
|
||||
We currently have (imho) some hard to explain limitation around _pyscript_, most notably:
|
||||
|
||||
- we have a `<py-config>` custom element where only a single one can be used and everything else is ignored. The reasons for this (I am guessing) are:
|
||||
- we have a single interpreter at the time ... but how would this scale when multiple interpreteres are wanted/desired?
|
||||
- there's no relation between a `<py-config>` and a `<py-script>` element ... even their definition order on the page doesn't matter so that if multiple `<py-script>` on a page have as last node a `<py-config>` they all follow that rule, making it impossible to grant either runtime or config per each `<py-script>`. Aren't we trapped by this decoupling of components that are, in fact, strongly coupled and constrained by such _implicit_ coupling? What's the plan forward here?
|
||||
- in all our live examples but 2 all we need is *a single `<py-script>` tag and, occasionally, a single `<py-config>` tag and I wonder if this is effectily the main use-case to address, while the need for multiple `<py-script>` that all share same config and environment look like needed only to display results in different places of the page, something easy to address via IDs when elements are well known or via the `js.document.querySelector(...)` API we expose through *pyodide* and *micropython*. Shouldn't we instead find a better way to relate same env when more `<py-script>` are meant, so that we bootstrap only one *main* env and we refer to such *env\* via attributes?
|
||||
- there's no way to display Python results within `<py-script>` custom element if this is within unexpected places: tables, trs, tds, options, sources, and what's not, current PoC offers an escape hatch to assign the target at runtime without any of the caveats we have with custom elements
|
||||
- we recently introduced `py-*` events for any node but that implicitly blocks multiple runtimes per page ... should we find a compromise/solution to this, since the `env` attribute could be used as `py-env` target too?
|
||||
|
||||
The current PoC couple _env_ and _config_ through a single component, isolating every script, runtime, and environment from each other.
|
||||
|
||||
Not only that, multiple _python scripts_ can share the same configuration whenever that's desired and in multiple pages or different parts of the page but these don't necessarily need to share the same environment.
|
||||
|
||||
The current PoC indeed allows multiple config and multiple interpreters (_pyodide_ or _micropython_) per page, downloading each only once but initializing these per each _script_ when desired.
|
||||
|
||||
A unique identifier, that is not an ID, could also relate each script to the same env as it's done now (shared _pyodide_ runtime across all _py-script_ tags) but that'd be an extra feature, not the default, and it can be orchestrated explicitly, example
|
||||
|
||||
```html
|
||||
<script type="py" config="shared-demo.toml">
|
||||
from js import document
|
||||
# target here is the related current script node
|
||||
document.currentScript.target.textContent = 'a'
|
||||
</script>
|
||||
<hr />
|
||||
<script type="py" env="shared-env">
|
||||
# this env shares nothing with the previous/default one
|
||||
from js import document
|
||||
# target here is the related current script node
|
||||
# which is different from the previous one
|
||||
document.currentScript.target.textContent = 'b'
|
||||
</script>
|
||||
<hr />
|
||||
<script type="py" env="shared-env">
|
||||
# this env shares globals only with the previous script
|
||||
from js import document
|
||||
# target here is the related current script node
|
||||
# which is also again different from the previous one
|
||||
document.currentScript.target.textContent = 'c'
|
||||
</script>
|
||||
```
|
||||
|
||||
I (personally) believe that optionally coupling _env_, _config_, and _runtime_ to each single script provides the base to distribute _pyscript_ components in the wild and without ever causing issues, so in few words this is not only a more robust and clean approach to what we have already, where two 3rd party _pyscript_ components can't coexist within the same page because of the points previously mentioned, but it's way more expicit than having multiple `<py-config>` potentially ignored by the page because one was already present / parsed / used within another component that landed before.
|
||||
|
||||
## YAGNI
|
||||
|
||||
This is a super-personal take around current _pyscript_ state of affairs:
|
||||
|
||||
- the splash screen reminds me good'ol days with Flash Player ... ideally we should have a fast boostrap that makes such practice irrelevant
|
||||
- the `interpreters` in the config, when only a config can run, is weird to say the least ... it's theoretically an array of details, with names and stuff, and I think nobody cares about it ... the current `runtime` attribute looks much easier to explain and reason about, plus, as previously mentioned, is confined within the _script_ that is running current python code
|
||||
- the whole TOML parser for our explicit use case looks like replace-able with a single RegExp or a split line loop (see https://github.com/WebReflection/basic-toml#readme)
|
||||
- plugins should never be embedded out of the box ... we might want to provide a list of plugins via a `plugins` attribute and handle these ourselves but I think that having a `@pyscript/core` module any plugin can use to register itself would be better at scale and architecture
|
||||
8
pyscript.core/esm/fetch-utils.js
Normal file
8
pyscript.core/esm/fetch-utils.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @param {Response} response */
|
||||
export const getBuffer = (response) => response.arrayBuffer();
|
||||
|
||||
/** @param {Response} response */
|
||||
export const getJSON = (response) => response.json();
|
||||
|
||||
/** @param {Response} response */
|
||||
export const getText = (response) => response.text();
|
||||
109
pyscript.core/esm/index.js
Normal file
109
pyscript.core/esm/index.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import { $x, $$ } from "basic-devtools";
|
||||
|
||||
import xworker from "./worker/class.js";
|
||||
import { handle, runtimes } from "./script-handler.js";
|
||||
import { all, assign, create, defineProperty } from "./utils.js";
|
||||
import { registry, selectors, prefixes } from "./runtimes.js";
|
||||
import { PLUGINS_SELECTORS, handlePlugin } from "./plugins.js";
|
||||
|
||||
export { registerPlugin } from "./plugins.js";
|
||||
export const XWorker = xworker();
|
||||
|
||||
const RUNTIME_SELECTOR = selectors.join(",");
|
||||
|
||||
// ensure both runtime and its queue are awaited then returns the runtime
|
||||
const awaitRuntime = async (key) => {
|
||||
const { runtime, queue } = runtimes.get(key);
|
||||
return (await all([runtime, queue]))[0];
|
||||
};
|
||||
|
||||
defineProperty(globalThis, "pyscript", {
|
||||
value: {
|
||||
env: new Proxy(create(null), { get: (_, name) => awaitRuntime(name) }),
|
||||
},
|
||||
});
|
||||
|
||||
let index = 0;
|
||||
globalThis.__events = new Map();
|
||||
|
||||
// attributes are tested via integration / e2e
|
||||
/* c8 ignore next 17 */
|
||||
const listener = async (event) => {
|
||||
const { type, currentTarget } = event;
|
||||
for (let { name, value, ownerElement: el } of $x(
|
||||
`./@*[${prefixes.map((p) => `name()="${p}${type}"`).join(" or ")}]`,
|
||||
currentTarget,
|
||||
)) {
|
||||
name = name.slice(0, -(type.length + 1));
|
||||
const runtime = await awaitRuntime(
|
||||
el.getAttribute(`${name}-env`) || name,
|
||||
);
|
||||
const i = index++;
|
||||
try {
|
||||
globalThis.__events.set(i, event);
|
||||
registry.get(name).runEvent(runtime, value, i);
|
||||
} finally {
|
||||
globalThis.__events.delete(i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// attributes are tested via integration / e2e
|
||||
/* c8 ignore next 8 */
|
||||
for (let { name, ownerElement: el } of $x(
|
||||
`.//@*[${prefixes.map((p) => `starts-with(name(),"${p}")`).join(" or ")}]`,
|
||||
)) {
|
||||
name = name.slice(name.indexOf("-") + 1);
|
||||
if (name !== "env") el.addEventListener(name, listener);
|
||||
}
|
||||
|
||||
const mo = new MutationObserver((records) => {
|
||||
for (const { type, target, attributeName, addedNodes } of records) {
|
||||
// attributes are tested via integration / e2e
|
||||
/* c8 ignore next 17 */
|
||||
if (type === "attributes") {
|
||||
const i = attributeName.indexOf("-") + 1;
|
||||
if (i) {
|
||||
const prefix = attributeName.slice(0, i);
|
||||
for (const p of prefixes) {
|
||||
if (prefix === p) {
|
||||
const type = attributeName.slice(i);
|
||||
if (type !== "env") {
|
||||
const method = target.hasAttribute(attributeName)
|
||||
? "add"
|
||||
: "remove";
|
||||
target[`${method}EventListener`](type, listener);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for (const node of addedNodes) {
|
||||
if (node.nodeType === 1) {
|
||||
if (node.matches(RUNTIME_SELECTOR)) handle(node);
|
||||
else {
|
||||
$$(RUNTIME_SELECTOR, node).forEach(handle);
|
||||
if (!PLUGINS_SELECTORS.length) continue;
|
||||
handlePlugin(node);
|
||||
$$(PLUGINS_SELECTORS.join(","), node).forEach(handlePlugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const observe = (root) => {
|
||||
mo.observe(root, { childList: true, subtree: true, attributes: true });
|
||||
return root;
|
||||
};
|
||||
|
||||
const { attachShadow } = Element.prototype;
|
||||
assign(Element.prototype, {
|
||||
attachShadow(init) {
|
||||
return observe(attachShadow.call(this, init));
|
||||
},
|
||||
});
|
||||
|
||||
$$(RUNTIME_SELECTOR, observe(document)).forEach(handle);
|
||||
39
pyscript.core/esm/loader.js
Normal file
39
pyscript.core/esm/loader.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { runtime } from "./runtimes.js";
|
||||
import { absoluteURL, resolve } from "./utils.js";
|
||||
import { parse } from "./toml.js";
|
||||
import { getJSON, getText } from "./fetch-utils.js";
|
||||
|
||||
/**
|
||||
* @param {string} id the runtime name @ version identifier
|
||||
* @param {string} [config] optional config file to parse
|
||||
* @returns
|
||||
*/
|
||||
export const getRuntime = (id, config) => {
|
||||
let options = {};
|
||||
if (config) {
|
||||
// REQUIRES INTEGRATION TEST
|
||||
/* c8 ignore start */
|
||||
if (config.endsWith(".json")) options = fetch(config).then(getJSON);
|
||||
else if (config.endsWith(".toml"))
|
||||
options = fetch(config).then(getText).then(parse);
|
||||
else {
|
||||
try {
|
||||
options = JSON.parse(config);
|
||||
} catch (_) {
|
||||
options = parse(config);
|
||||
}
|
||||
// make the config a URL to be able to retrieve relative paths from it
|
||||
config = absoluteURL("./config.txt");
|
||||
}
|
||||
/* c8 ignore stop */
|
||||
}
|
||||
return resolve(options).then((options) => runtime[id](options, config));
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} type the runtime type
|
||||
* @param {string} [version] the optional runtime version
|
||||
* @returns
|
||||
*/
|
||||
export const getRuntimeID = (type, version = "") =>
|
||||
`${type}@${version}`.replace(/@$/, "");
|
||||
85
pyscript.core/esm/plugins.js
Normal file
85
pyscript.core/esm/plugins.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import { $$ } from "basic-devtools";
|
||||
|
||||
import { getDetails } from "./script-handler.js";
|
||||
import { registry, configs } from "./runtimes.js";
|
||||
import { getRuntimeID } from "./loader.js";
|
||||
import { io } from "./runtime/_utils.js";
|
||||
|
||||
export const PLUGINS_SELECTORS = [];
|
||||
|
||||
/**
|
||||
* @typedef {Object} Runtime plugin configuration
|
||||
* @prop {string} type the runtime type
|
||||
* @prop {object} runtime the bootstrapped runtime
|
||||
* @prop {(url:string, options?: object) => Worker} XWorker an XWorker constructor that defaults to same runtime on the Worker.
|
||||
* @prop {object} config a cloned config used to bootstrap the runtime
|
||||
* @prop {(code:string) => any} run an utility to run code within the runtime
|
||||
* @prop {(code:string) => Promise<any>} runAsync an utility to run code asynchronously within the runtime
|
||||
* @prop {(path:string, data:ArrayBuffer) => void} writeFile an utility to write a file in the virtual FS, if available
|
||||
*/
|
||||
|
||||
// REQUIRES INTEGRATION TEST
|
||||
/* c8 ignore start */
|
||||
/**
|
||||
* @param {Element} node any DOM element registered via plugin.
|
||||
*/
|
||||
export const handlePlugin = (node) => {
|
||||
for (const name of PLUGINS_SELECTORS) {
|
||||
if (node.matches(name)) {
|
||||
const { options, known } = plugins.get(name);
|
||||
if (!known.has(node)) {
|
||||
known.add(node);
|
||||
const { type, version, config, env, onRuntimeReady } = options;
|
||||
const name = getRuntimeID(type, version);
|
||||
const id = env || `${name}${config ? `|${config}` : ""}`;
|
||||
const { runtime: engine, XWorker } = getDetails(
|
||||
type,
|
||||
id,
|
||||
name,
|
||||
version,
|
||||
config,
|
||||
);
|
||||
engine.then((runtime) => {
|
||||
const module = registry.get(type);
|
||||
onRuntimeReady(node, {
|
||||
type,
|
||||
runtime,
|
||||
XWorker,
|
||||
io: io.get(runtime),
|
||||
config: structuredClone(configs.get(name)),
|
||||
run: module.run.bind(module, runtime),
|
||||
runAsync: module.runAsync.bind(module, runtime),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {Map<string, {options:object, known:WeakSet<Element>}>}
|
||||
*/
|
||||
const plugins = new Map();
|
||||
|
||||
/**
|
||||
* @typedef {Object} PluginOptions plugin configuration
|
||||
* @prop {string} type the runtime/interpreter type to receive
|
||||
* @prop {string} [version] the optional runtime version to use
|
||||
* @prop {string} [config] the optional config to use within such runtime
|
||||
* @prop {string} [env] the optional environment to use
|
||||
* @prop {(node: Element, runtime: Runtime) => void} onRuntimeReady the callback that will be invoked once
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allows plugins and components on the page to receive runtimes to execute any code.
|
||||
* @param {string} name the unique plugin name
|
||||
* @param {PluginOptions} options the plugin configuration
|
||||
*/
|
||||
export const registerPlugin = (name, options) => {
|
||||
if (PLUGINS_SELECTORS.includes(name))
|
||||
throw new Error(`plugin ${name} already registered`);
|
||||
PLUGINS_SELECTORS.push(name);
|
||||
plugins.set(name, { options, known: new WeakSet() });
|
||||
$$(name).forEach(handlePlugin);
|
||||
};
|
||||
/* c8 ignore stop */
|
||||
123
pyscript.core/esm/runtime/_utils.js
Normal file
123
pyscript.core/esm/runtime/_utils.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import { getBuffer } from "../fetch-utils.js";
|
||||
import { absoluteURL } from "../utils.js";
|
||||
|
||||
// REQUIRES INTEGRATION TEST
|
||||
/* c8 ignore start */
|
||||
export const io = new WeakMap();
|
||||
export const stdio = (init) => {
|
||||
const context = init || console;
|
||||
const localIO = {
|
||||
stderr: (context.stderr || console.error).bind(context),
|
||||
stdout: (context.stdout || console.log).bind(context),
|
||||
};
|
||||
return {
|
||||
stderr: (...args) => localIO.stderr(...args),
|
||||
stdout: (...args) => localIO.stdout(...args),
|
||||
async get(engine) {
|
||||
const runtime = await engine;
|
||||
io.set(runtime, localIO);
|
||||
return runtime;
|
||||
},
|
||||
};
|
||||
};
|
||||
/* c8 ignore stop */
|
||||
|
||||
// This should be the only helper needed for all Emscripten based FS exports
|
||||
export const writeFile = (FS, path, buffer) => {
|
||||
const { parentPath, name } = FS.analyzePath(path, true);
|
||||
FS.mkdirTree(parentPath);
|
||||
return FS.writeFile([parentPath, name].join("/"), new Uint8Array(buffer), {
|
||||
canOwn: true,
|
||||
});
|
||||
};
|
||||
|
||||
// This is instead a fallback for Lua or others
|
||||
export const writeFileShim = (FS, path, buffer) => {
|
||||
path = resolve(FS, path);
|
||||
mkdirTree(FS, dirname(path));
|
||||
return FS.writeFile(path, new Uint8Array(buffer), { canOwn: true });
|
||||
};
|
||||
|
||||
const dirname = (path) => {
|
||||
const tree = path.split("/");
|
||||
tree.pop();
|
||||
return tree.join("/");
|
||||
};
|
||||
|
||||
const mkdirTree = (FS, path) => {
|
||||
const current = [];
|
||||
for (const branch of path.split("/")) {
|
||||
current.push(branch);
|
||||
if (branch) FS.mkdir(current.join("/"));
|
||||
}
|
||||
};
|
||||
|
||||
const resolve = (FS, path) => {
|
||||
const tree = [];
|
||||
for (const branch of path.split("/")) {
|
||||
switch (branch) {
|
||||
case "":
|
||||
break;
|
||||
case ".":
|
||||
break;
|
||||
case "..":
|
||||
tree.pop();
|
||||
break;
|
||||
default:
|
||||
tree.push(branch);
|
||||
}
|
||||
}
|
||||
return [FS.cwd()].concat(tree).join("/").replace(/^\/+/, "/");
|
||||
};
|
||||
|
||||
import { all, isArray } from "../utils.js";
|
||||
|
||||
const calculateFetchPaths = (config_fetch) => {
|
||||
// REQUIRES INTEGRATION TEST
|
||||
/* c8 ignore start */
|
||||
for (const { files, to_file, from = "" } of config_fetch) {
|
||||
if (files !== undefined && to_file !== undefined)
|
||||
throw new Error(
|
||||
`Cannot use 'to_file' and 'files' parameters together!`,
|
||||
);
|
||||
if (files === undefined && to_file === undefined && from.endsWith("/"))
|
||||
throw new Error(
|
||||
`Couldn't determine the filename from the path ${from}, please supply 'to_file' parameter.`,
|
||||
);
|
||||
}
|
||||
/* c8 ignore stop */
|
||||
return config_fetch.flatMap(
|
||||
({ from = "", to_folder = ".", to_file, files }) => {
|
||||
if (isArray(files))
|
||||
return files.map((file) => ({
|
||||
url: joinPaths([from, file]),
|
||||
path: joinPaths([to_folder, file]),
|
||||
}));
|
||||
const filename = to_file || from.slice(1 + from.lastIndexOf("/"));
|
||||
return [{ url: from, path: joinPaths([to_folder, filename]) }];
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const joinPaths = (parts) => {
|
||||
const res = parts
|
||||
.map((part) => part.trim().replace(/(^[/]*|[/]*$)/g, ""))
|
||||
.filter((p) => p !== "" && p !== ".")
|
||||
.join("/");
|
||||
|
||||
return parts[0].startsWith("/") ? `/${res}` : res;
|
||||
};
|
||||
|
||||
const fetchResolved = (config_fetch, url) =>
|
||||
fetch(absoluteURL(url, base.get(config_fetch)));
|
||||
|
||||
export const base = new WeakMap();
|
||||
|
||||
export const fetchPaths = (module, runtime, config_fetch) =>
|
||||
all(
|
||||
calculateFetchPaths(config_fetch).map(({ url, path }) =>
|
||||
fetchResolved(config_fetch, url)
|
||||
.then(getBuffer)
|
||||
.then((buffer) => module.writeFile(runtime, path, buffer)),
|
||||
),
|
||||
);
|
||||
35
pyscript.core/esm/runtime/micropython.js
Normal file
35
pyscript.core/esm/runtime/micropython.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { fetchPaths, stdio, writeFile } from "./_utils.js";
|
||||
|
||||
const type = "micropython";
|
||||
|
||||
// REQUIRES INTEGRATION TEST
|
||||
/* c8 ignore start */
|
||||
const worker = (method) =>
|
||||
function (runtime, code, xworker) {
|
||||
globalThis.xworker = xworker;
|
||||
return this[method](runtime, `from js import xworker;${code}`);
|
||||
};
|
||||
|
||||
export default {
|
||||
type: [type, "mpy"],
|
||||
module: () => `http://localhost:8080/micropython/micropython.mjs`,
|
||||
async engine({ loadMicroPython }, config, url) {
|
||||
const { stderr, stdout, get } = stdio();
|
||||
url = url.replace(/\.m?js$/, ".wasm");
|
||||
const runtime = await get(loadMicroPython({ stderr, stdout, url }));
|
||||
if (config.fetch) await fetchPaths(this, runtime, config.fetch);
|
||||
return runtime;
|
||||
},
|
||||
run: (runtime, code) => runtime.runPython(code),
|
||||
runAsync: (runtime, code) => runtime.runPythonAsync(code),
|
||||
runEvent(runtime, code, key) {
|
||||
return this.run(
|
||||
runtime,
|
||||
`import js;event=js.__events.get(${key});${code}`,
|
||||
);
|
||||
},
|
||||
runWorker: worker("run"),
|
||||
runWorkerAsync: worker("runAsync"),
|
||||
writeFile: ({ FS }, path, buffer) => writeFile(FS, path, buffer),
|
||||
};
|
||||
/* c8 ignore stop */
|
||||
41
pyscript.core/esm/runtime/pyodide.js
Normal file
41
pyscript.core/esm/runtime/pyodide.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { fetchPaths, stdio, writeFile } from "./_utils.js";
|
||||
|
||||
const type = "pyodide";
|
||||
|
||||
// REQUIRES INTEGRATION TEST
|
||||
/* c8 ignore start */
|
||||
const worker = (method) =>
|
||||
function (runtime, code, xworker) {
|
||||
globalThis.xworker = xworker;
|
||||
return this[method](runtime, `from js import xworker;${code}`);
|
||||
};
|
||||
|
||||
export default {
|
||||
type: [type, "py"],
|
||||
module: (version = "0.22.1") =>
|
||||
`https://cdn.jsdelivr.net/pyodide/v${version}/full/pyodide.mjs`,
|
||||
async engine({ loadPyodide }, config) {
|
||||
const { stderr, stdout, get } = stdio();
|
||||
const runtime = await get(loadPyodide({ stderr, stdout }));
|
||||
if (config.fetch) await fetchPaths(this, runtime, config.fetch);
|
||||
if (config.packages) {
|
||||
await runtime.loadPackage("micropip");
|
||||
const micropip = await runtime.pyimport("micropip");
|
||||
await micropip.install(config.packages);
|
||||
micropip.destroy();
|
||||
}
|
||||
return runtime;
|
||||
},
|
||||
run: (runtime, code) => runtime.runPython(code),
|
||||
runAsync: (runtime, code) => runtime.runPythonAsync(code),
|
||||
runEvent(runtime, code, key) {
|
||||
return this.run(
|
||||
runtime,
|
||||
`import js;event=js.__events.get(${key});${code}`,
|
||||
);
|
||||
},
|
||||
runWorker: worker("run"),
|
||||
runWorkerAsync: worker("runAsync"),
|
||||
writeFile: ({ FS }, path, buffer) => writeFile(FS, path, buffer),
|
||||
};
|
||||
/* c8 ignore stop */
|
||||
49
pyscript.core/esm/runtime/ruby.js
Normal file
49
pyscript.core/esm/runtime/ruby.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import { fetchPaths } from "./_utils.js";
|
||||
|
||||
const type = "ruby";
|
||||
|
||||
// MISSING:
|
||||
// * there is no VFS apparently or I couldn't reach any
|
||||
// * I've no idea how to override the stderr and stdout
|
||||
// * I've no idea how to import packages
|
||||
|
||||
// REQUIRES INTEGRATION TEST
|
||||
/* c8 ignore start */
|
||||
const worker = (method) =>
|
||||
function (runtime, code, xworker) {
|
||||
globalThis.xworker = xworker;
|
||||
return this[method](
|
||||
runtime,
|
||||
`require "js";xworker=JS::eval("return xworker");${code}`,
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
experimental: true,
|
||||
type: [type, "rb"],
|
||||
module: (version = "2.0.0") =>
|
||||
`https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@${version}/dist/browser.esm.js`,
|
||||
async engine({ DefaultRubyVM }, config, url) {
|
||||
const response = await fetch(
|
||||
`${url.slice(0, url.lastIndexOf("/"))}/ruby.wasm`,
|
||||
);
|
||||
const module = await WebAssembly.compile(await response.arrayBuffer());
|
||||
const { vm: runtime } = await DefaultRubyVM(module);
|
||||
if (config.fetch) await fetchPaths(this, runtime, config.fetch);
|
||||
return runtime;
|
||||
},
|
||||
run: (runtime, code) => runtime.eval(code),
|
||||
runAsync: (runtime, code) => runtime.evalAsync(code),
|
||||
runEvent(runtime, code, key) {
|
||||
return this.run(
|
||||
runtime,
|
||||
`require "js";event=JS::eval("return __events.get(${key})");${code}`,
|
||||
);
|
||||
},
|
||||
runWorker: worker("run"),
|
||||
runWorkerAsync: worker("runAsync"),
|
||||
writeFile: () => {
|
||||
throw new Error(`writeFile is not supported in ${type}`);
|
||||
},
|
||||
};
|
||||
/* c8 ignore stop */
|
||||
45
pyscript.core/esm/runtime/wasmoon.js
Normal file
45
pyscript.core/esm/runtime/wasmoon.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { fetchPaths, stdio, writeFileShim } from "./_utils.js";
|
||||
|
||||
const type = "wasmoon";
|
||||
|
||||
// REQUIRES INTEGRATION TEST
|
||||
/* c8 ignore start */
|
||||
const worker = (method) =>
|
||||
function (runtime, code, xworker) {
|
||||
runtime.global.set("xworker", xworker);
|
||||
return this[method](runtime, code);
|
||||
};
|
||||
|
||||
export default {
|
||||
type: [type, "lua"],
|
||||
module: (version = "1.15.0") =>
|
||||
`https://cdn.jsdelivr.net/npm/wasmoon@${version}/+esm`,
|
||||
async engine({ LuaFactory, LuaLibraries }, config) {
|
||||
const { stderr, stdout, get } = stdio();
|
||||
const runtime = await get(new LuaFactory().createEngine());
|
||||
runtime.global.getTable(LuaLibraries.Base, (index) => {
|
||||
runtime.global.setField(index, "print", stdout);
|
||||
runtime.global.setField(index, "printErr", stderr);
|
||||
});
|
||||
if (config.fetch) await fetchPaths(this, runtime, config.fetch);
|
||||
return runtime;
|
||||
},
|
||||
run: (runtime, code) => runtime.doStringSync(code),
|
||||
runAsync: (runtime, code) => runtime.doString(code),
|
||||
runEvent(runtime, code, key) {
|
||||
runtime.global.set("event", globalThis.__events.get(key));
|
||||
return this.run(runtime, code);
|
||||
},
|
||||
runWorker: worker("run"),
|
||||
runWorkerAsync: worker("runAsync"),
|
||||
writeFile: (
|
||||
{
|
||||
cmodule: {
|
||||
module: { FS },
|
||||
},
|
||||
},
|
||||
path,
|
||||
buffer,
|
||||
) => writeFileShim(FS, path, buffer),
|
||||
};
|
||||
/* c8 ignore stop */
|
||||
57
pyscript.core/esm/runtimes.js
Normal file
57
pyscript.core/esm/runtimes.js
Normal file
@@ -0,0 +1,57 @@
|
||||
// ⚠️ Part of this file is automatically generated
|
||||
// The :RUNTIMES comment is a delimiter and no code should be written/changed after
|
||||
// See rollup/build_runtimes.cjs to know more
|
||||
|
||||
import { base } from "./runtime/_utils.js";
|
||||
|
||||
/** @type {Map<string, object>} */
|
||||
export const registry = new Map();
|
||||
|
||||
/** @type {Map<string, object>} */
|
||||
export const configs = new Map();
|
||||
|
||||
/** @type {string[]} */
|
||||
export const selectors = [];
|
||||
|
||||
/** @type {string[]} */
|
||||
export const prefixes = [];
|
||||
|
||||
export const runtime = new Proxy(new Map(), {
|
||||
get(map, id) {
|
||||
if (!map.has(id)) {
|
||||
const [type, ...rest] = id.split("@");
|
||||
const runtime = registry.get(type);
|
||||
const url = /^https?:\/\//i.test(rest)
|
||||
? rest[0]
|
||||
: runtime.module(...rest);
|
||||
map.set(id, {
|
||||
url,
|
||||
module: import(url),
|
||||
engine: runtime.engine.bind(runtime),
|
||||
});
|
||||
}
|
||||
const { url, module, engine } = map.get(id);
|
||||
return (config, baseURL) =>
|
||||
module.then((module) => {
|
||||
configs.set(id, config);
|
||||
const fetch = config?.fetch;
|
||||
if (fetch) base.set(fetch, baseURL);
|
||||
return engine(module, config, url);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const register = (runtime) => {
|
||||
for (const type of [].concat(runtime.type)) {
|
||||
registry.set(type, runtime);
|
||||
selectors.push(`script[type="${type}"]`);
|
||||
prefixes.push(`${type}-`);
|
||||
}
|
||||
};
|
||||
|
||||
//:RUNTIMES
|
||||
import micropython from "./runtime/micropython.js";
|
||||
import pyodide from "./runtime/pyodide.js";
|
||||
import ruby from "./runtime/ruby.js";
|
||||
import wasmoon from "./runtime/wasmoon.js";
|
||||
for (const runtime of [micropython, pyodide, ruby, wasmoon]) register(runtime);
|
||||
136
pyscript.core/esm/script-handler.js
Normal file
136
pyscript.core/esm/script-handler.js
Normal file
@@ -0,0 +1,136 @@
|
||||
import { $ } from "basic-devtools";
|
||||
|
||||
import xworker from "./worker/class.js";
|
||||
import { getRuntime, getRuntimeID } from "./loader.js";
|
||||
import { registry } from "./runtimes.js";
|
||||
import { all, resolve, defineProperty, absoluteURL } from "./utils.js";
|
||||
import { getText } from "./fetch-utils.js";
|
||||
|
||||
const getRoot = (script) => {
|
||||
let parent = script;
|
||||
while (parent.parentNode) parent = parent.parentNode;
|
||||
return parent;
|
||||
};
|
||||
|
||||
const queryTarget = (script, idOrSelector) => {
|
||||
const root = getRoot(script);
|
||||
return root.getElementById(idOrSelector) || $(idOrSelector, root);
|
||||
};
|
||||
|
||||
const targets = new WeakMap();
|
||||
const targetDescriptor = {
|
||||
get() {
|
||||
let target = targets.get(this);
|
||||
if (!target) {
|
||||
target = document.createElement(`${this.type}-script`);
|
||||
targets.set(this, target);
|
||||
handle(this);
|
||||
}
|
||||
return target;
|
||||
},
|
||||
set(target) {
|
||||
if (typeof target === "string")
|
||||
targets.set(this, queryTarget(this, target));
|
||||
else {
|
||||
targets.set(this, target);
|
||||
handle(this);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const handled = new WeakMap();
|
||||
|
||||
export const runtimes = new Map();
|
||||
|
||||
const execute = async (script, source, XWorker, isAsync) => {
|
||||
const module = registry.get(script.type);
|
||||
/* c8 ignore next */
|
||||
if (module.experimental)
|
||||
console.warn(`The ${script.type} runtime is experimental`);
|
||||
const [runtime, content] = await all([handled.get(script).runtime, source]);
|
||||
try {
|
||||
// temporarily override inherited document.currentScript in a non writable way
|
||||
// but it deletes it right after to preserve native behavior (as it's sync: no trouble)
|
||||
defineProperty(globalThis, "XWorker", {
|
||||
configurable: true,
|
||||
get: () => XWorker,
|
||||
});
|
||||
defineProperty(document, "currentScript", {
|
||||
configurable: true,
|
||||
get: () => script,
|
||||
});
|
||||
return module[isAsync ? "runAsync" : "run"](runtime, content);
|
||||
} finally {
|
||||
delete globalThis.XWorker;
|
||||
delete document.currentScript;
|
||||
}
|
||||
};
|
||||
|
||||
const getValue = (ref, prefix) => {
|
||||
const value = ref?.value;
|
||||
return value ? prefix + value : "";
|
||||
};
|
||||
|
||||
export const getDetails = (type, id, name, version, config) => {
|
||||
if (!runtimes.has(id)) {
|
||||
const details = {
|
||||
runtime: getRuntime(name, config),
|
||||
queue: resolve(),
|
||||
XWorker: xworker(type, version),
|
||||
};
|
||||
runtimes.set(id, details);
|
||||
// enable sane defaults when single runtime *of kind* is used in the page
|
||||
// this allows `xxx-*` attributes to refer to such runtime without `env` around
|
||||
if (!runtimes.has(type)) runtimes.set(type, details);
|
||||
}
|
||||
return runtimes.get(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {HTMLScriptElement} script a special type of <script>
|
||||
*/
|
||||
export const handle = async (script) => {
|
||||
// known node, move its companion target after
|
||||
// vDOM or other use cases where the script is a tracked element
|
||||
if (handled.has(script)) {
|
||||
const { target } = script;
|
||||
if (target) {
|
||||
// if the script is in the head just append target to the body
|
||||
if (script.closest("head")) document.body.append(target);
|
||||
// in any other case preserve the script position
|
||||
else script.after(target);
|
||||
}
|
||||
}
|
||||
// new script to handle ... allow newly created scripts to work
|
||||
// just exactly like any other script would
|
||||
else {
|
||||
// allow a shared config among scripts, beside runtime,
|
||||
// and/or source code with different config or runtime
|
||||
const {
|
||||
attributes: { async: isAsync, config, env, target, version },
|
||||
src,
|
||||
type,
|
||||
} = script;
|
||||
const versionValue = version?.value;
|
||||
const name = getRuntimeID(type, versionValue);
|
||||
const targetValue = getValue(target, "");
|
||||
let configValue = getValue(config, "|");
|
||||
const id = getValue(env, "") || `${name}${configValue}`;
|
||||
configValue = configValue.slice(1);
|
||||
if (configValue) configValue = absoluteURL(configValue);
|
||||
const details = getDetails(type, id, name, versionValue, configValue);
|
||||
|
||||
handled.set(
|
||||
defineProperty(script, "target", targetDescriptor),
|
||||
details,
|
||||
);
|
||||
|
||||
if (targetValue) targets.set(script, queryTarget(script, targetValue));
|
||||
|
||||
// start fetching external resources ASAP
|
||||
const source = src ? fetch(src).then(getText) : script.textContent;
|
||||
details.queue = details.queue.then(() =>
|
||||
execute(script, source, details.XWorker, !!isAsync),
|
||||
);
|
||||
}
|
||||
};
|
||||
8
pyscript.core/esm/toml.js
Normal file
8
pyscript.core/esm/toml.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// lazy TOML parser (fast-toml might be a better alternative)
|
||||
const TOML_LIB = `https://unpkg.com/basic-toml@0.3.1/es.js`;
|
||||
|
||||
/**
|
||||
* @param {string} text TOML text to parse
|
||||
* @returns {object} the resulting JS object
|
||||
*/
|
||||
export const parse = async (text) => (await import(TOML_LIB)).parse(text);
|
||||
11
pyscript.core/esm/utils.js
Normal file
11
pyscript.core/esm/utils.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { isArray } = Array;
|
||||
|
||||
const { assign, create, defineProperty } = Object;
|
||||
|
||||
const { all, resolve } = new Proxy(Promise, {
|
||||
get: ($, name) => $[name].bind($),
|
||||
});
|
||||
|
||||
const absoluteURL = (path, base = location.href) => new URL(path, base).href;
|
||||
|
||||
export { isArray, assign, create, defineProperty, all, resolve, absoluteURL };
|
||||
53
pyscript.core/esm/worker/_template.js
Normal file
53
pyscript.core/esm/worker/_template.js
Normal file
@@ -0,0 +1,53 @@
|
||||
// ⚠️ This file is used to generate xworker.js
|
||||
// That means if any import is circular or brings in too much
|
||||
// that would be a higher payload for every worker.
|
||||
// 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 { registry } from "../runtimes.js";
|
||||
import { getRuntime, getRuntimeID } from "../loader.js";
|
||||
|
||||
let engine, run, runtimeEvent;
|
||||
const add = (type, fn) => {
|
||||
addEventListener(
|
||||
type,
|
||||
fn ||
|
||||
(async (event) => {
|
||||
const runtime = await engine;
|
||||
runtimeEvent = event;
|
||||
run(runtime, `xworker.on${type}(xworker.event);`, xworker);
|
||||
}),
|
||||
!!fn && { once: true },
|
||||
);
|
||||
};
|
||||
const xworker = {
|
||||
onerror() {},
|
||||
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.
|
||||
get event() {
|
||||
const event = runtimeEvent;
|
||||
if (!event) throw new Error("Unauthorized event access");
|
||||
runtimeEvent = void 0;
|
||||
return event;
|
||||
},
|
||||
};
|
||||
add("message", ({ data: { options, code } }) => {
|
||||
engine = (async () => {
|
||||
const { type, version, config, async: isAsync } = options;
|
||||
const engine = await getRuntime(getRuntimeID(type, version), config);
|
||||
const details = registry.get(type);
|
||||
(run = details[`runWorker${isAsync ? "Async" : ""}`].bind(details))(
|
||||
engine,
|
||||
code,
|
||||
(globalThis.xworker = xworker),
|
||||
);
|
||||
return engine;
|
||||
})();
|
||||
add("error");
|
||||
add("message");
|
||||
add("messageerror");
|
||||
});
|
||||
35
pyscript.core/esm/worker/class.js
Normal file
35
pyscript.core/esm/worker/class.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import xworker from "./xworker.js";
|
||||
import { assign, defineProperty, absoluteURL } from "../utils.js";
|
||||
import { getText } from "../fetch-utils.js";
|
||||
|
||||
/**
|
||||
* @typedef {Object} WorkerOptions plugin configuration
|
||||
* @prop {string} type the runtime/interpreter type to use
|
||||
* @prop {string} [version] the optional runtime version to use
|
||||
* @prop {string} [config] the optional config to use within such runtime
|
||||
*/
|
||||
|
||||
export default (...args) =>
|
||||
/**
|
||||
* A XWorker is a Worker facade able to bootstrap a channel with any desired runtime.
|
||||
* @param {string} url the remote file to evaluate on bootstrap
|
||||
* @param {WorkerOptions} [options] optional arguments to define the runtime to use
|
||||
* @returns {Worker}
|
||||
*/
|
||||
function XWorker(url, options) {
|
||||
const worker = xworker();
|
||||
const { postMessage } = worker;
|
||||
if (args.length) {
|
||||
const [type, version] = args;
|
||||
options = assign({}, options || { type, version });
|
||||
if (!options.type) options.type = type;
|
||||
}
|
||||
if (options?.config) options.config = absoluteURL(options.config);
|
||||
const bootstrap = fetch(url)
|
||||
.then(getText)
|
||||
.then((code) => postMessage.call(worker, { options, code }));
|
||||
return defineProperty(worker, "postMessage", {
|
||||
value: (data, ...rest) =>
|
||||
bootstrap.then(() => postMessage.call(worker, data, ...rest)),
|
||||
});
|
||||
};
|
||||
37
pyscript.core/index.html
Normal file
37
pyscript.core/index.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<li><a href="./test/pyscript.html">pyscript</a></li>
|
||||
<li><a href="./test/index.html">pyodide</a></li>
|
||||
<li>
|
||||
<a href="./test/matplot.worker.html">matplot worker (toml)</a>
|
||||
</li>
|
||||
<li><a href="./test/matplot.json.html">matplot json</a></li>
|
||||
<li><a href="./test/matplot.html">matplot toml</a></li>
|
||||
<li><a href="./test/many.html">all together</a></li>
|
||||
<li><a href="./test/micropython.html">micropython</a></li>
|
||||
<li><a href="./test/remote.html">remote micropython</a></li>
|
||||
<li><a href="./test/shadow-dom.html">shadow-dom</a></li>
|
||||
<li><a href="./test/table.html">table</a></li>
|
||||
<li><a href="./test/env.html">env</a></li>
|
||||
<li><a href="./test/isolated.html">isolated</a></li>
|
||||
<li><a href="./test/fetch.html">config fetch</a></li>
|
||||
<li><a href="./test/py-events.html">py-* events</a></li>
|
||||
<li><a href="./test/ruby.html">ruby</a></li>
|
||||
<li><a href="./test/wasmoon.html">lua</a></li>
|
||||
<li><a href="./test/async.html">async</a></li>
|
||||
<li><a href="./test/worker/">worker</a></li>
|
||||
<li><a href="./test/plugins/">plugins</a></li>
|
||||
<li>
|
||||
<a href="./test/plugins/py-script.html">plugins - PyScript</a>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
3
pyscript.core/micropython/README.md
Normal file
3
pyscript.core/micropython/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# MicroPython
|
||||
|
||||
This folder contains a preview of MicroPython and it needs a `localhost` server to be discovered by integration tests.
|
||||
6422
pyscript.core/micropython/micropython.mjs
Normal file
6422
pyscript.core/micropython/micropython.mjs
Normal file
File diff suppressed because it is too large
Load Diff
BIN
pyscript.core/micropython/micropython.wasm
Normal file
BIN
pyscript.core/micropython/micropython.wasm
Normal file
Binary file not shown.
8
pyscript.core/node.importmap
Normal file
8
pyscript.core/node.importmap
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"imports": {
|
||||
"http://pyodide": "./test/mocked/pyodide.mjs",
|
||||
"https://cdn.jsdelivr.net/pyodide/v0.22.1/full/pyodide.mjs": "./test/mocked/pyodide.mjs",
|
||||
"http://localhost:8080/micropython/micropython.mjs": "./test/mocked/micropython.mjs",
|
||||
"https://unpkg.com/basic-toml@0.3.1/es.js": "./test/mocked/toml.mjs"
|
||||
}
|
||||
}
|
||||
3519
pyscript.core/package-lock.json
generated
Normal file
3519
pyscript.core/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
53
pyscript.core/package.json
Normal file
53
pyscript.core/package.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@pyscript/core",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "./cjs/index.js",
|
||||
"types": "./types/index.d.ts",
|
||||
"scripts": {
|
||||
"server": "npx static-handler --cors .",
|
||||
"build": "npm run rollup:xworker && npm run rollup:min && 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:xworker": "rollup --config rollup/xworker.config.js",
|
||||
"test": "c8 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:worker": "echo worker is $(cat esm/worker/xworker.js | brotli | wc -c) bytes once compressed",
|
||||
"ts": "tsc -p ."
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@node-loader/import-maps": "^1.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.1.0",
|
||||
"@rollup/plugin-terser": "^0.4.3",
|
||||
"ascjs": "^5.0.1",
|
||||
"c8": "^7.14.0",
|
||||
"eslint": "^8.41.0",
|
||||
"linkedom": "^0.14.26",
|
||||
"rollup": "^3.23.0",
|
||||
"static-handler": "^0.3.2",
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"module": "./esm/index.js",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/esm/index.d.ts",
|
||||
"import": "./esm/index.js",
|
||||
"default": "./cjs/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"unpkg": "min.js",
|
||||
"dependencies": {
|
||||
"basic-devtools": "^0.1.6"
|
||||
},
|
||||
"worker": {
|
||||
"blob": "sha256-gI4kK2AXlU8ZeNni68iQv7KcVZlHjloKCe2H3Ah7ePE="
|
||||
}
|
||||
}
|
||||
3
pyscript.core/pyscript/README.md
Normal file
3
pyscript.core/pyscript/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# PyScript
|
||||
|
||||
This folder contains an older version of PyScript and it needs a `localhost` server to be discovered by integration tests.
|
||||
334
pyscript.core/pyscript/pyscript.css
Normal file
334
pyscript.core/pyscript/pyscript.css
Normal file
@@ -0,0 +1,334 @@
|
||||
/* py-config - not a component */
|
||||
py-config {
|
||||
display: none;
|
||||
}
|
||||
/* py-{el} - components not defined */
|
||||
py-script:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-repl:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-title:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-inputbox:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-button:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
py-box:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
|
||||
"Noto Color Emoji";
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.spinner::after {
|
||||
content: "";
|
||||
box-sizing: border-box;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
top: calc(40% - 20px);
|
||||
left: calc(50% - 20px);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.spinner.smooth::after {
|
||||
border-top: 4px solid rgba(255, 255, 255, 1);
|
||||
border-left: 4px solid rgba(255, 255, 255, 1);
|
||||
border-right: 4px solid rgba(255, 255, 255, 0);
|
||||
animation: spinner 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spinner {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
display: block;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 0.8rem;
|
||||
margin-top: 6rem;
|
||||
}
|
||||
|
||||
/* Pop-up second layer begin */
|
||||
|
||||
.py-overlay {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
transition: opacity 500ms;
|
||||
visibility: hidden;
|
||||
color: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.py-overlay {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.py-pop-up {
|
||||
text-align: center;
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.py-pop-up p {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.py-pop-up a {
|
||||
position: absolute;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 200%;
|
||||
top: 3.5%;
|
||||
right: 5%;
|
||||
}
|
||||
|
||||
/* Pop-up second layer end */
|
||||
.alert-banner {
|
||||
position: relative;
|
||||
padding: 0.5rem 1.5rem 0.5rem 0.5rem;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.alert-banner p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.py-error {
|
||||
background-color: #ffe9e8;
|
||||
border: solid;
|
||||
border-color: #f0625f;
|
||||
color: #9d041c;
|
||||
}
|
||||
|
||||
.py-warning {
|
||||
background-color: rgb(255, 244, 229);
|
||||
border: solid;
|
||||
border-color: #ffa016;
|
||||
color: #794700;
|
||||
}
|
||||
|
||||
.alert-banner.py-error > #alert-close-button {
|
||||
color: #9d041c;
|
||||
}
|
||||
|
||||
.alert-banner.py-warning > #alert-close-button {
|
||||
color: #794700;
|
||||
}
|
||||
|
||||
#alert-close-button {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
top: 0.5rem;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.py-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.py-box div.py-box-child * {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.py-repl-box {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.py-repl-editor {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgba(209, 213, 219, var(--tw-border-opacity));
|
||||
border-width: 1px;
|
||||
position: relative;
|
||||
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgba(59, 130, 246, 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
position: relative;
|
||||
|
||||
box-sizing: border-box;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: rgb(209, 213, 219);
|
||||
}
|
||||
|
||||
.editor-box:hover button {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.py-repl-run-button {
|
||||
opacity: 0;
|
||||
bottom: 0.25rem;
|
||||
right: 0.25rem;
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
-webkit-appearance: button;
|
||||
text-transform: none;
|
||||
font-family: inherit;
|
||||
font-size: 100%;
|
||||
margin: 0;
|
||||
text-rendering: auto;
|
||||
letter-spacing: normal;
|
||||
word-spacing: normal;
|
||||
line-height: normal;
|
||||
text-transform: none;
|
||||
text-indent: 0px;
|
||||
text-shadow: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
align-items: flex-start;
|
||||
cursor: default;
|
||||
box-sizing: border-box;
|
||||
background-color: -internal-light-dark(rgb(239, 239, 239), rgb(59, 59, 59));
|
||||
margin: 0em;
|
||||
padding: 1px 6px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.py-repl-run-button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.py-title {
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.py-title h1 {
|
||||
font-weight: 700;
|
||||
font-size: 1.875rem;
|
||||
}
|
||||
|
||||
.py-input {
|
||||
padding: 0.5rem;
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgba(209, 213, 219, var(--tw-border-opacity));
|
||||
border-width: 1px;
|
||||
border-radius: 0.25rem;
|
||||
margin-right: 0.75rem;
|
||||
border-style: solid;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.py-box input.py-input {
|
||||
width: -webkit-fill-available;
|
||||
}
|
||||
|
||||
.central-content {
|
||||
max-width: 20rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
input {
|
||||
text-rendering: auto;
|
||||
color: -internal-light-dark(black, white);
|
||||
letter-spacing: normal;
|
||||
word-spacing: normal;
|
||||
line-height: normal;
|
||||
text-transform: none;
|
||||
text-indent: 0px;
|
||||
text-shadow: none;
|
||||
display: inline-block;
|
||||
text-align: start;
|
||||
appearance: auto;
|
||||
-webkit-rtl-ordering: logical;
|
||||
background-color: -internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59));
|
||||
margin: 0em;
|
||||
padding: 1px 2px;
|
||||
border-width: 2px;
|
||||
border-style: inset;
|
||||
border-color: -internal-light-dark(rgb(118, 118, 118), rgb(133, 133, 133));
|
||||
border-image: initial;
|
||||
}
|
||||
|
||||
.py-button {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(255, 255, 255, var(--tw-text-opacity));
|
||||
padding: 0.5rem;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(37, 99, 235, var(--tw-bg-opacity));
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgba(37, 99, 235, var(--tw-border-opacity));
|
||||
border-width: 1px;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.py-li-element p {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.py-li-element p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
font-size: 100%;
|
||||
line-height: 1.15;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.line-through {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* ===== py-terminal plugin ===== */
|
||||
/* XXX: it would be nice if these rules were stored in e.g. pyterminal.css and
|
||||
bundled together at build time (by rollup?) */
|
||||
|
||||
.py-terminal {
|
||||
min-height: 10em;
|
||||
background-color: black;
|
||||
color: white;
|
||||
padding: 0.5rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.py-terminal-hidden {
|
||||
display: none;
|
||||
}
|
||||
36385
pyscript.core/pyscript/pyscript.js
Normal file
36385
pyscript.core/pyscript/pyscript.js
Normal file
File diff suppressed because one or more lines are too long
1
pyscript.core/pyscript/pyscript.js.map
Normal file
1
pyscript.core/pyscript/pyscript.js.map
Normal file
File diff suppressed because one or more lines are too long
32
pyscript.core/rollup/build_runtimes.cjs
Normal file
32
pyscript.core/rollup/build_runtimes.cjs
Normal file
@@ -0,0 +1,32 @@
|
||||
// ⚠️ This files modifies at build time esm/runtimes.js so that
|
||||
// it's impossible to forget to export a runtime from esm/runtime folder.
|
||||
|
||||
const { join, resolve } = require("node:path");
|
||||
const { readdirSync, readFileSync, writeFileSync } = require("node:fs");
|
||||
|
||||
const RUNTIMES_DIR = resolve(join(__dirname, "..", "esm", "runtime"));
|
||||
const RUNTIMES_JS = resolve(join(__dirname, "..", "esm", "runtimes.js"));
|
||||
|
||||
const createRuntimes = () => {
|
||||
const runtimes = [];
|
||||
for (const file of readdirSync(RUNTIMES_DIR)) {
|
||||
// ignore files starting with underscore
|
||||
if (/^[a-z].+?\.js/.test(file)) runtimes.push(file.slice(0, -3));
|
||||
}
|
||||
// generate the output to append at the end of the file
|
||||
const output = [];
|
||||
for (const runtime of runtimes)
|
||||
output.push(`import ${runtime} from './runtime/${runtime}.js';`);
|
||||
output.push(
|
||||
`for (const runtime of [${runtimes.join(", ")}]) register(runtime);`,
|
||||
);
|
||||
return output.join("\n");
|
||||
};
|
||||
|
||||
writeFileSync(
|
||||
RUNTIMES_JS,
|
||||
// find //:RUNTIMES comment and replace anything after that
|
||||
readFileSync(RUNTIMES_JS)
|
||||
.toString()
|
||||
.replace(/(\/\/:RUNTIMES)([\S\s]*)$/, `$1\n${createRuntimes()}\n`),
|
||||
);
|
||||
30
pyscript.core/rollup/build_xworker.cjs
Normal file
30
pyscript.core/rollup/build_xworker.cjs
Normal file
@@ -0,0 +1,30 @@
|
||||
// ⚠️ This files creates esm/worker/xworker.js in a way that it can be loaded
|
||||
// through a Blob and as a string, allowing Workers to run within any page.
|
||||
// This still needs special CSP care when CSP rules are applied to the page
|
||||
// and this file is also creating a unique sha256 version of that very same
|
||||
// text content to allow CSP rules to play nicely with it.
|
||||
|
||||
const { join, resolve } = require("node:path");
|
||||
const { readdirSync, readFileSync, rmSync, writeFileSync } = require("node:fs");
|
||||
const { createHash } = require("node:crypto");
|
||||
|
||||
const WORKERS_DIR = resolve(join(__dirname, "..", "esm", "worker"));
|
||||
const PACKAGE_JSON = resolve(join(__dirname, "..", "package.json"));
|
||||
|
||||
for (const file of readdirSync(WORKERS_DIR)) {
|
||||
if (file.startsWith("__")) {
|
||||
const js = JSON.stringify(
|
||||
readFileSync(join(WORKERS_DIR, file)).toString(),
|
||||
);
|
||||
const hash = createHash("sha256");
|
||||
hash.update(js);
|
||||
const json = require(PACKAGE_JSON);
|
||||
json.worker = { blob: "sha256-" + hash.digest("base64") };
|
||||
writeFileSync(PACKAGE_JSON, JSON.stringify(json, null, " "));
|
||||
writeFileSync(
|
||||
join(WORKERS_DIR, "xworker.js"),
|
||||
`/* c8 ignore next */\nexport default () => new Worker(URL.createObjectURL(new Blob([${js}],{type:'application/javascript'})),{type:'module'});`,
|
||||
);
|
||||
rmSync(join(WORKERS_DIR, file));
|
||||
}
|
||||
}
|
||||
18
pyscript.core/rollup/min.config.js
Normal file
18
pyscript.core/rollup/min.config.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// This file generates /min.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/index.js",
|
||||
plugins: [nodeResolve(), terser()],
|
||||
output: {
|
||||
esModule: true,
|
||||
file: "./min.js",
|
||||
},
|
||||
};
|
||||
24
pyscript.core/rollup/xworker.config.js
Normal file
24
pyscript.core/rollup/xworker.config.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// This file generates /min.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";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
|
||||
createRequire(import.meta.url)("./build_runtimes.cjs");
|
||||
|
||||
const WORKERS_DIR = resolve(
|
||||
join(dirname(fileURLToPath(import.meta.url)), "..", "esm", "worker"),
|
||||
);
|
||||
|
||||
export default {
|
||||
input: join(WORKERS_DIR, "_template.js"),
|
||||
plugins: [nodeResolve(), terser()],
|
||||
output: {
|
||||
esModule: true,
|
||||
file: join(WORKERS_DIR, "__template.js"),
|
||||
},
|
||||
};
|
||||
12
pyscript.core/sw.js
Normal file
12
pyscript.core/sw.js
Normal file
@@ -0,0 +1,12 @@
|
||||
addEventListener("fetch", (event) => {
|
||||
event.respondWith(
|
||||
(async () => {
|
||||
const cache = await caches.open("python-script");
|
||||
const cachedResponse = await cache.match(event.request);
|
||||
if (cachedResponse) return cachedResponse;
|
||||
const networkResponse = await fetch(event.request);
|
||||
event.waitUntil(cache.put(event.request, networkResponse.clone()));
|
||||
return networkResponse;
|
||||
})(),
|
||||
);
|
||||
});
|
||||
27
pyscript.core/test/_utils.js
Normal file
27
pyscript.core/test/_utils.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const { writeFileShim } = require("../cjs/runtime/_utils.js");
|
||||
|
||||
const assert = require("./assert.js");
|
||||
|
||||
const FS = {
|
||||
mkdir(...args) {
|
||||
this.mkdir_args = args;
|
||||
},
|
||||
cwd: () => __dirname,
|
||||
writeFile(...args) {
|
||||
this.writeFile_args = args;
|
||||
},
|
||||
};
|
||||
|
||||
writeFileShim(FS, "./test/abc.js", []);
|
||||
assert(JSON.stringify(FS.mkdir_args), `["${__dirname}/test"]`);
|
||||
assert(
|
||||
JSON.stringify(FS.writeFile_args),
|
||||
`["${__dirname}/test/abc.js",{},{"canOwn":true}]`,
|
||||
);
|
||||
|
||||
writeFileShim(FS, "/./../abc.js", []);
|
||||
assert(JSON.stringify(FS.mkdir_args), `["${__dirname}"]`);
|
||||
assert(
|
||||
JSON.stringify(FS.writeFile_args),
|
||||
`["${__dirname}/abc.js",{},{"canOwn":true}]`,
|
||||
);
|
||||
1
pyscript.core/test/a.py
Normal file
1
pyscript.core/test/a.py
Normal file
@@ -0,0 +1 @@
|
||||
x = "hello from A"
|
||||
10
pyscript.core/test/assert.js
Normal file
10
pyscript.core/test/assert.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const assert = (current, expected, message = "Unexpected Error") => {
|
||||
if (!Object.is(current, expected)) {
|
||||
console.error(`\x1b[1m${message}\x1b[0m`);
|
||||
console.error(" expected", expected);
|
||||
console.error(" got", current);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = assert;
|
||||
20
pyscript.core/test/async.html
Normal file
20
pyscript.core/test/async.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py" async>
|
||||
from js import document, fetch
|
||||
|
||||
response = await fetch("async.html")
|
||||
document.body.appendChild(
|
||||
document.createElement('pre')
|
||||
).textContent = await response.text()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1
pyscript.core/test/b.py
Normal file
1
pyscript.core/test/b.py
Normal file
@@ -0,0 +1 @@
|
||||
x = "hello from B"
|
||||
3
pyscript.core/test/config.json
Normal file
3
pyscript.core/test/config.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"packages": ["matplotlib"]
|
||||
}
|
||||
3
pyscript.core/test/config.toml
Normal file
3
pyscript.core/test/config.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
packages = [
|
||||
"matplotlib"
|
||||
]
|
||||
8
pyscript.core/test/counter.js
Normal file
8
pyscript.core/test/counter.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const div = document.body.appendChild(document.createElement("div"));
|
||||
div.style.cssText = "position:fixed;top:0;left:0";
|
||||
|
||||
let i = 0;
|
||||
(function counter() {
|
||||
div.textContent = ++i;
|
||||
requestAnimationFrame(counter);
|
||||
})();
|
||||
36
pyscript.core/test/env.html
Normal file
36
pyscript.core/test/env.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<script type="mpy">
|
||||
import sys
|
||||
import js
|
||||
version = sys.version
|
||||
js.document.currentScript.target.textContent = version
|
||||
</script>
|
||||
<hr />
|
||||
<script type="mpy">
|
||||
import js
|
||||
js.document.currentScript.target.textContent = version
|
||||
</script>
|
||||
<hr />
|
||||
<script type="py">
|
||||
import sys
|
||||
import js
|
||||
version = sys.version
|
||||
js.document.currentScript.target.textContent = version
|
||||
</script>
|
||||
<hr />
|
||||
<script type="py" target="last-script-target">
|
||||
import js
|
||||
js.document.currentScript.target.textContent = version
|
||||
</script>
|
||||
<div id="last-script-target"></div>
|
||||
</body>
|
||||
</html>
|
||||
17
pyscript.core/test/fetch.html
Normal file
17
pyscript.core/test/fetch.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<script type="mpy" config="./fetch.toml">
|
||||
import js
|
||||
import a, b
|
||||
js.console.log(a.x)
|
||||
js.console.log(b.x)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
2
pyscript.core/test/fetch.toml
Normal file
2
pyscript.core/test/fetch.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[[fetch]]
|
||||
files = ["./a.py", "./b.py"]
|
||||
2
pyscript.core/test/icon.svg
Normal file
2
pyscript.core/test/icon.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M31.885 16c-8.124 0-7.617 3.523-7.617 3.523l.01 3.65h7.752v1.095H21.197S16 23.678 16 31.876c0 8.196 4.537 7.906 4.537 7.906h2.708v-3.804s-.146-4.537 4.465-4.537h7.688s4.32.07 4.32-4.175v-7.019S40.374 16 31.885 16zm-4.275 2.454c.771 0 1.395.624 1.395 1.395s-.624 1.395-1.395 1.395a1.393 1.393 0 0 1-1.395-1.395c0-.771.624-1.395 1.395-1.395z" fill="url(#a)"/><path d="M32.115 47.833c8.124 0 7.617-3.523 7.617-3.523l-.01-3.65H31.97v-1.095h10.832S48 40.155 48 31.958c0-8.197-4.537-7.906-4.537-7.906h-2.708v3.803s.146 4.537-4.465 4.537h-7.688s-4.32-.07-4.32 4.175v7.019s-.656 4.247 7.833 4.247zm4.275-2.454a1.393 1.393 0 0 1-1.395-1.395c0-.77.624-1.394 1.395-1.394s1.395.623 1.395 1.394c0 .772-.624 1.395-1.395 1.395z" fill="url(#b)"/><defs><linearGradient id="a" x1="19.075" y1="18.782" x2="34.898" y2="34.658" gradientUnits="userSpaceOnUse"><stop stop-color="#387EB8"/><stop offset="1" stop-color="#366994"/></linearGradient><linearGradient id="b" x1="28.809" y1="28.882" x2="45.803" y2="45.163" gradientUnits="userSpaceOnUse"><stop stop-color="#FFE052"/><stop offset="1" stop-color="#FFC331"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
17
pyscript.core/test/index.html
Normal file
17
pyscript.core/test/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py">
|
||||
import sys
|
||||
import js
|
||||
js.document.currentScript.target.textContent = sys.version
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
209
pyscript.core/test/index.js
Normal file
209
pyscript.core/test/index.js
Normal file
@@ -0,0 +1,209 @@
|
||||
const assert = require("./assert.js");
|
||||
require("./_utils.js");
|
||||
|
||||
const { fetch } = globalThis;
|
||||
|
||||
const tick = (ms = 10) => new Promise(($) => setTimeout($, ms));
|
||||
|
||||
const clear = (python) => {
|
||||
for (const [key, value] of Object.entries(python)) {
|
||||
if (typeof value === "object") python[key] = null;
|
||||
else python[key] = "";
|
||||
}
|
||||
};
|
||||
|
||||
const patchFetch = (callback) => {
|
||||
Object.defineProperty(globalThis, "fetch", {
|
||||
configurable: true,
|
||||
get() {
|
||||
try {
|
||||
return callback;
|
||||
} finally {
|
||||
globalThis.fetch = fetch;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const { parseHTML } = require("linkedom");
|
||||
const { document, window } = parseHTML("...");
|
||||
|
||||
globalThis.document = document;
|
||||
globalThis.Element = window.Element;
|
||||
globalThis.MutationObserver = window.MutationObserver;
|
||||
globalThis.XPathResult = {};
|
||||
globalThis.XPathEvaluator =
|
||||
window.XPathEvaluator ||
|
||||
class XPathEvaluator {
|
||||
createExpression() {
|
||||
return { evaluate: () => [] };
|
||||
}
|
||||
};
|
||||
|
||||
const { registerPlugin } = require("../cjs");
|
||||
|
||||
(async () => {
|
||||
// shared 3rd party mocks
|
||||
const {
|
||||
python: pyodide,
|
||||
setTarget,
|
||||
loadPyodide,
|
||||
} = await import("./mocked/pyodide.mjs");
|
||||
const { python: micropython } = await import("./mocked/micropython.mjs");
|
||||
|
||||
// shared helpers
|
||||
const div = document.createElement("div");
|
||||
const shadowRoot = div.attachShadow({ mode: "open" });
|
||||
const content = `
|
||||
import sys
|
||||
import js
|
||||
js.document.currentScript.target.textContent = sys.version
|
||||
`;
|
||||
|
||||
const { URL } = globalThis;
|
||||
globalThis.URL = function (href) {
|
||||
return { href };
|
||||
};
|
||||
globalThis.location = { href: "" };
|
||||
|
||||
// all tests
|
||||
for (const test of [
|
||||
async function versionedRuntime() {
|
||||
document.head.innerHTML = `<script type="py" version="0.22.1">${content}</script>`;
|
||||
await tick();
|
||||
assert(pyodide.content, content);
|
||||
assert(pyodide.target.tagName, "PY-SCRIPT");
|
||||
},
|
||||
|
||||
async function basicExpectations() {
|
||||
document.head.innerHTML = `<script type="py">${content}</script>`;
|
||||
await tick();
|
||||
assert(pyodide.content, content);
|
||||
assert(pyodide.target.tagName, "PY-SCRIPT");
|
||||
},
|
||||
|
||||
async function foreignRuntime() {
|
||||
document.head.innerHTML = `<script type="py" version="http://pyodide">${content}</script>`;
|
||||
await tick();
|
||||
assert(pyodide.content, content);
|
||||
assert(pyodide.target.tagName, "PY-SCRIPT");
|
||||
},
|
||||
|
||||
async function basicMicroPython() {
|
||||
document.head.innerHTML = `<script type="mpy">${content}</script>`;
|
||||
await tick();
|
||||
assert(micropython.content, content);
|
||||
assert(micropython.target.tagName, "MPY-SCRIPT");
|
||||
const script = document.head.firstElementChild;
|
||||
document.body.appendChild(script);
|
||||
await tick();
|
||||
assert(script.nextSibling, micropython.target);
|
||||
micropython.target = null;
|
||||
},
|
||||
|
||||
async function exernalResourceInShadowRoot() {
|
||||
patchFetch(() =>
|
||||
Promise.resolve({ text: () => Promise.resolve("OK") }),
|
||||
);
|
||||
shadowRoot.innerHTML = `
|
||||
<my-plugin></my-plugin>
|
||||
<script src="./whatever" env="unique" type="py" target="my-plugin"></script>
|
||||
`.trim();
|
||||
await tick();
|
||||
assert(pyodide.content, "OK");
|
||||
assert(pyodide.target.tagName, "MY-PLUGIN");
|
||||
},
|
||||
|
||||
async function explicitTargetNode() {
|
||||
setTarget(div);
|
||||
shadowRoot.innerHTML = `
|
||||
<my-plugin></my-plugin>
|
||||
<script type="py"></script>
|
||||
`.trim();
|
||||
await tick();
|
||||
assert(pyodide.target, div);
|
||||
},
|
||||
|
||||
async function explicitTargetAsString() {
|
||||
setTarget("my-plugin");
|
||||
shadowRoot.innerHTML = `
|
||||
<my-plugin></my-plugin>
|
||||
<script type="py"></script>
|
||||
`.trim();
|
||||
await tick();
|
||||
assert(pyodide.target.tagName, "MY-PLUGIN");
|
||||
},
|
||||
|
||||
async function jsonConfig() {
|
||||
const packages = {};
|
||||
patchFetch(() => Promise.resolve({ json: () => ({ packages }) }));
|
||||
shadowRoot.innerHTML = `<script config="./whatever.json" type="py"></script>`;
|
||||
await tick();
|
||||
assert(pyodide.packages, packages);
|
||||
},
|
||||
|
||||
async function tomlConfig() {
|
||||
const jsonPackages = JSON.stringify({
|
||||
packages: { a: Math.random() },
|
||||
});
|
||||
patchFetch(() =>
|
||||
Promise.resolve({ text: () => Promise.resolve(jsonPackages) }),
|
||||
);
|
||||
shadowRoot.innerHTML = `<script config="./whatever.toml" type="py"></script>`;
|
||||
// there are more promises in here let's increase the tick delay to avoid flaky tests
|
||||
await tick(20);
|
||||
assert(
|
||||
JSON.stringify({ packages: pyodide.packages }),
|
||||
jsonPackages,
|
||||
);
|
||||
},
|
||||
|
||||
async function fetchConfig() {
|
||||
const jsonPackages = JSON.stringify({
|
||||
fetch: [
|
||||
{ files: ["./a.py", "./b.py"] },
|
||||
{ from: "utils" },
|
||||
{ from: "/utils", files: ["c.py"] },
|
||||
],
|
||||
});
|
||||
patchFetch(() =>
|
||||
Promise.resolve({
|
||||
arrayBuffer: () => Promise.resolve([]),
|
||||
text: () => Promise.resolve(jsonPackages),
|
||||
}),
|
||||
);
|
||||
shadowRoot.innerHTML = `
|
||||
<script type="py" config="./fetch.toml">
|
||||
import js
|
||||
import a, b
|
||||
js.console.log(a.x)
|
||||
js.console.log(b.x)
|
||||
</script>
|
||||
`;
|
||||
await tick(10);
|
||||
},
|
||||
|
||||
async function testDefaultRuntime() {
|
||||
const py = await pyscript.env.py;
|
||||
const keys = Object.keys(loadPyodide()).join(",");
|
||||
assert(Object.keys(py).join(","), keys);
|
||||
|
||||
const unique = await pyscript.env.unique;
|
||||
assert(Object.keys(unique).join(","), keys);
|
||||
},
|
||||
|
||||
async function pyEvents() {
|
||||
shadowRoot.innerHTML = `
|
||||
<button py-click="test()">test</button>
|
||||
<button py-env="unique" py-click="test()">test</button>
|
||||
`;
|
||||
await tick(20);
|
||||
},
|
||||
]) {
|
||||
await test();
|
||||
clear(pyodide);
|
||||
clear(micropython);
|
||||
}
|
||||
|
||||
globalThis.URL = URL;
|
||||
})();
|
||||
24
pyscript.core/test/isolated.html
Normal file
24
pyscript.core/test/isolated.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py">
|
||||
# define a "global" variable and print it
|
||||
shared_env = 1
|
||||
print(shared_env)
|
||||
</script>
|
||||
<script type="py">
|
||||
# just print the "global" variable from same env
|
||||
print(shared_env)
|
||||
</script>
|
||||
<script type="py" env="another-one">
|
||||
# see the error
|
||||
print(shared_env)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
17
pyscript.core/test/manifest.json
Normal file
17
pyscript.core/test/manifest.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"background_color": "#ffffff",
|
||||
"description": "PythonScript",
|
||||
"display": "standalone",
|
||||
"name": "PythonScript",
|
||||
"orientation": "any",
|
||||
"short_name": "PythonScript",
|
||||
"start_url": "./",
|
||||
"theme_color": "#ffffff",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon.svg",
|
||||
"sizes": "144x144",
|
||||
"type": "image/svg+xml"
|
||||
}
|
||||
]
|
||||
}
|
||||
65
pyscript.core/test/many.html
Normal file
65
pyscript.core/test/many.html
Normal file
@@ -0,0 +1,65 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py" version="0.23.2">
|
||||
import sys
|
||||
import js
|
||||
js.document.currentScript.target.textContent = sys.version
|
||||
</script>
|
||||
<script type="mpy" src="three.py"></script>
|
||||
<script type="py" config="./config.json">
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.tri as tri
|
||||
import numpy as np
|
||||
import base64
|
||||
import io
|
||||
|
||||
import js
|
||||
|
||||
# First create the x and y coordinates of the points.
|
||||
n_angles = 36
|
||||
n_radii = 8
|
||||
min_radius = 0.25
|
||||
radii = np.linspace(min_radius, 0.95, n_radii)
|
||||
|
||||
angles = np.linspace(0, 2 * np.pi, n_angles, endpoint=False)
|
||||
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
|
||||
angles[:, 1::2] += np.pi / n_angles
|
||||
|
||||
x = (radii * np.cos(angles)).flatten()
|
||||
y = (radii * np.sin(angles)).flatten()
|
||||
z = (np.cos(radii) * np.cos(3 * angles)).flatten()
|
||||
|
||||
# Create the Triangulation; no triangles so Delaunay triangulation created.
|
||||
triang = tri.Triangulation(x, y)
|
||||
|
||||
# Mask off unwanted triangles.
|
||||
triang.set_mask(np.hypot(x[triang.triangles].mean(axis=1),
|
||||
y[triang.triangles].mean(axis=1))
|
||||
< min_radius)
|
||||
|
||||
fig1, ax1 = plt.subplots()
|
||||
ax1.set_aspect('equal')
|
||||
tpc = ax1.tripcolor(triang, z, shading='flat')
|
||||
fig1.colorbar(tpc)
|
||||
ax1.set_title('tripcolor of Delaunay triangulation, flat shading')
|
||||
|
||||
buf = io.BytesIO()
|
||||
plt.savefig(buf, format='png')
|
||||
buf.seek(0)
|
||||
|
||||
img = js.document.createElement("img")
|
||||
img.style.transform = "scale(.5)"
|
||||
img.src = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')
|
||||
js.document.currentScript.target.appendChild(img)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
59
pyscript.core/test/matplot.html
Normal file
59
pyscript.core/test/matplot.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py" config="./config.toml">
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.tri as tri
|
||||
import numpy as np
|
||||
import base64
|
||||
import io
|
||||
|
||||
import js
|
||||
|
||||
# First create the x and y coordinates of the points.
|
||||
n_angles = 36
|
||||
n_radii = 8
|
||||
min_radius = 0.25
|
||||
radii = np.linspace(min_radius, 0.95, n_radii)
|
||||
|
||||
angles = np.linspace(0, 2 * np.pi, n_angles, endpoint=False)
|
||||
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
|
||||
angles[:, 1::2] += np.pi / n_angles
|
||||
|
||||
x = (radii * np.cos(angles)).flatten()
|
||||
y = (radii * np.sin(angles)).flatten()
|
||||
z = (np.cos(radii) * np.cos(3 * angles)).flatten()
|
||||
|
||||
# Create the Triangulation; no triangles so Delaunay triangulation created.
|
||||
triang = tri.Triangulation(x, y)
|
||||
|
||||
# Mask off unwanted triangles.
|
||||
triang.set_mask(np.hypot(x[triang.triangles].mean(axis=1),
|
||||
y[triang.triangles].mean(axis=1))
|
||||
< min_radius)
|
||||
|
||||
fig1, ax1 = plt.subplots()
|
||||
ax1.set_aspect('equal')
|
||||
tpc = ax1.tripcolor(triang, z, shading='flat')
|
||||
fig1.colorbar(tpc)
|
||||
ax1.set_title('tripcolor of Delaunay triangulation, flat shading')
|
||||
|
||||
buf = io.BytesIO()
|
||||
plt.savefig(buf, format='png')
|
||||
buf.seek(0)
|
||||
|
||||
img = js.document.createElement("img")
|
||||
img.style.transform = "scale(.5)"
|
||||
img.src = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')
|
||||
js.document.currentScript.target.appendChild(img)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
59
pyscript.core/test/matplot.json.html
Normal file
59
pyscript.core/test/matplot.json.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py" config="./config.json">
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.tri as tri
|
||||
import numpy as np
|
||||
import base64
|
||||
import io
|
||||
|
||||
import js
|
||||
|
||||
# First create the x and y coordinates of the points.
|
||||
n_angles = 36
|
||||
n_radii = 8
|
||||
min_radius = 0.25
|
||||
radii = np.linspace(min_radius, 0.95, n_radii)
|
||||
|
||||
angles = np.linspace(0, 2 * np.pi, n_angles, endpoint=False)
|
||||
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
|
||||
angles[:, 1::2] += np.pi / n_angles
|
||||
|
||||
x = (radii * np.cos(angles)).flatten()
|
||||
y = (radii * np.sin(angles)).flatten()
|
||||
z = (np.cos(radii) * np.cos(3 * angles)).flatten()
|
||||
|
||||
# Create the Triangulation; no triangles so Delaunay triangulation created.
|
||||
triang = tri.Triangulation(x, y)
|
||||
|
||||
# Mask off unwanted triangles.
|
||||
triang.set_mask(np.hypot(x[triang.triangles].mean(axis=1),
|
||||
y[triang.triangles].mean(axis=1))
|
||||
< min_radius)
|
||||
|
||||
fig1, ax1 = plt.subplots()
|
||||
ax1.set_aspect('equal')
|
||||
tpc = ax1.tripcolor(triang, z, shading='flat')
|
||||
fig1.colorbar(tpc)
|
||||
ax1.set_title('tripcolor of Delaunay triangulation, flat shading')
|
||||
|
||||
buf = io.BytesIO()
|
||||
plt.savefig(buf, format='png')
|
||||
buf.seek(0)
|
||||
|
||||
img = js.document.createElement("img")
|
||||
img.style.transform = "scale(.5)"
|
||||
img.src = 'data:image/png;base64,' + base64.b64encode(buf.read()).decode('UTF-8')
|
||||
js.document.currentScript.target.appendChild(img)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
51
pyscript.core/test/matplot.py
Normal file
51
pyscript.core/test/matplot.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import js
|
||||
import matplotlib
|
||||
|
||||
try:
|
||||
js.document
|
||||
except AttributeError:
|
||||
matplotlib.use("agg")
|
||||
|
||||
import base64
|
||||
import io
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.tri as tri
|
||||
import numpy as np
|
||||
|
||||
# First create the x and y coordinates of the points.
|
||||
n_angles = 36
|
||||
n_radii = 8
|
||||
min_radius = 0.25
|
||||
radii = np.linspace(min_radius, 0.95, n_radii)
|
||||
|
||||
angles = np.linspace(0, 2 * np.pi, n_angles, endpoint=False)
|
||||
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
|
||||
angles[:, 1::2] += np.pi / n_angles
|
||||
|
||||
x = (radii * np.cos(angles)).flatten()
|
||||
y = (radii * np.sin(angles)).flatten()
|
||||
z = (np.cos(radii) * np.cos(3 * angles)).flatten()
|
||||
|
||||
# Create the Triangulation; no triangles so Delaunay triangulation created.
|
||||
triang = tri.Triangulation(x, y)
|
||||
|
||||
# Mask off unwanted triangles.
|
||||
triang.set_mask(
|
||||
np.hypot(x[triang.triangles].mean(axis=1), y[triang.triangles].mean(axis=1))
|
||||
< min_radius
|
||||
)
|
||||
|
||||
fig1, ax1 = plt.subplots()
|
||||
ax1.set_aspect("equal")
|
||||
tpc = ax1.tripcolor(triang, z, shading="flat")
|
||||
fig1.colorbar(tpc)
|
||||
ax1.set_title("tripcolor of Delaunay triangulation, flat shading")
|
||||
|
||||
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")
|
||||
)
|
||||
26
pyscript.core/test/matplot.worker.html
Normal file
26
pyscript.core/test/matplot.worker.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="mpy">
|
||||
from js import XWorker
|
||||
|
||||
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': 'py', 'config': './config.toml'})
|
||||
w.onmessage = show_image
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
18
pyscript.core/test/micropython.html
Normal file
18
pyscript.core/test/micropython.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="mpy">
|
||||
import sys
|
||||
import js
|
||||
js.document.currentScript.target.textContent = sys.version
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
7
pyscript.core/test/mocked/micropython.mjs
Normal file
7
pyscript.core/test/mocked/micropython.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
export const python = { content: "", target: null };
|
||||
export const loadMicroPython = () => ({
|
||||
runPython(content) {
|
||||
python.content = content;
|
||||
python.target = document.currentScript.target;
|
||||
},
|
||||
});
|
||||
40
pyscript.core/test/mocked/pyodide.mjs
Normal file
40
pyscript.core/test/mocked/pyodide.mjs
Normal file
@@ -0,0 +1,40 @@
|
||||
import { basename, dirname } from "node:path";
|
||||
|
||||
let target;
|
||||
export const setTarget = (value) => {
|
||||
target = value;
|
||||
};
|
||||
export const python = { content: "", target: null, packages: null };
|
||||
export const loadPyodide = () => ({
|
||||
loadPackage() {},
|
||||
pyimport() {
|
||||
return {
|
||||
install(packages) {
|
||||
python.packages = packages;
|
||||
},
|
||||
destroy() {},
|
||||
};
|
||||
},
|
||||
runPython(content) {
|
||||
python.content = content;
|
||||
if (target) {
|
||||
document.currentScript.target = target;
|
||||
target = void 0;
|
||||
}
|
||||
python.target = document.currentScript.target;
|
||||
},
|
||||
FS: {
|
||||
mkdirTree() {},
|
||||
writeFile() {},
|
||||
analyzePath: (path) => ({
|
||||
parentPath: dirname(path),
|
||||
name: basename(path),
|
||||
}),
|
||||
},
|
||||
_module: {
|
||||
PATH: { dirname },
|
||||
PATH_FS: {
|
||||
resolve: (path) => path,
|
||||
},
|
||||
},
|
||||
});
|
||||
1
pyscript.core/test/mocked/toml.mjs
Normal file
1
pyscript.core/test/mocked/toml.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export const parse = (text) => JSON.parse(text);
|
||||
18
pyscript.core/test/no-sw.html
Normal file
18
pyscript.core/test/no-sw.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py">
|
||||
import sys
|
||||
import js
|
||||
js.document.currentScript.target.textContent = sys.version
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
37
pyscript.core/test/order.html
Normal file
37
pyscript.core/test/order.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"basic-devtools": "../node_modules/basic-devtools/esm/index.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="../esm/index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="mpy" src="http://localhost:7357/print-a.py"></script>
|
||||
<script type="mpy">
|
||||
print('B')
|
||||
</script>
|
||||
<script type="mpy" src="http://localhost:7357/print-a.py"></script>
|
||||
<script type="mpy">
|
||||
print('C')
|
||||
</script>
|
||||
<script type="mpy">
|
||||
print('D')
|
||||
</script>
|
||||
<pre>
|
||||
A
|
||||
B
|
||||
A
|
||||
C
|
||||
D</pre
|
||||
>
|
||||
</body>
|
||||
</html>
|
||||
11
pyscript.core/test/order.js
Normal file
11
pyscript.core/test/order.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { readFileSync } = require("fs");
|
||||
|
||||
require("http")
|
||||
.createServer((req, res) => {
|
||||
const content = readFileSync(__dirname + req.url);
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
setTimeout(() => {
|
||||
res.end(content);
|
||||
}, 1000);
|
||||
})
|
||||
.listen(7357);
|
||||
3
pyscript.core/test/package.json
Normal file
3
pyscript.core/test/package.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "commonjs"
|
||||
}
|
||||
35
pyscript.core/test/plugins/index.html
Normal file
35
pyscript.core/test/plugins/index.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>Plugins</title>
|
||||
<style>
|
||||
py-script {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{ "imports": { "@pyscript/core": "../../min.js" } }
|
||||
</script>
|
||||
<script type="module">
|
||||
import { registerPlugin } from "@pyscript/core";
|
||||
registerPlugin("mpy-script", {
|
||||
type: "micropython", // or just 'mpy'
|
||||
async onRuntimeReady(element, micropython) {
|
||||
console.log(micropython);
|
||||
// Somehow this doesn't work in MicroPython
|
||||
micropython.io.stdout = (message) => {
|
||||
console.log("🐍", micropython.type, message);
|
||||
};
|
||||
micropython.run(element.textContent);
|
||||
element.replaceChildren("See console ->");
|
||||
element.style.display = "block";
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<mpy-script> print('Hello Console!') </mpy-script>
|
||||
</body>
|
||||
</html>
|
||||
34
pyscript.core/test/plugins/lua.html
Normal file
34
pyscript.core/test/plugins/lua.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>Plugins</title>
|
||||
<style>
|
||||
py-script {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{ "imports": { "@pyscript/core": "../../min.js" } }
|
||||
</script>
|
||||
<script type="module">
|
||||
import { registerPlugin } from "@pyscript/core";
|
||||
registerPlugin("lua-script", {
|
||||
type: "wasmoon", // or just 'lua'
|
||||
async onRuntimeReady(element, wasmoon) {
|
||||
// Somehow this doesn't work in Wasmoon
|
||||
wasmoon.io.stdout = (message) => {
|
||||
console.log("🌑", wasmoon.type, message);
|
||||
};
|
||||
wasmoon.run(element.textContent);
|
||||
element.replaceChildren("See console ->");
|
||||
element.style.display = "block";
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<lua-script> print('Hello Console!') </lua-script>
|
||||
</body>
|
||||
</html>
|
||||
32
pyscript.core/test/plugins/py-script.html
Normal file
32
pyscript.core/test/plugins/py-script.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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" } }
|
||||
</script>
|
||||
<script type="module" src="py-script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<py-config>
|
||||
[[fetch]]
|
||||
from = "../"
|
||||
to_folder = "./"
|
||||
files = ["a.py", "b.py"]
|
||||
</py-config>
|
||||
<py-script>
|
||||
import js
|
||||
import a, b
|
||||
print('Hello Console!')
|
||||
js.console.log(a.x, b.x)
|
||||
'Hello Web!'
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
59
pyscript.core/test/plugins/py-script.js
Normal file
59
pyscript.core/test/plugins/py-script.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import { registerPlugin } 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;
|
||||
const sharedPyodide = new Promise((resolve) => {
|
||||
const pyConfig = document.querySelector("py-config");
|
||||
const config = pyConfig?.getAttribute("src") || pyConfig?.textContent;
|
||||
registerPlugin("py-script", {
|
||||
config,
|
||||
type: "pyodide", // or just 'py'
|
||||
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;
|
||||
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);
|
||||
1
pyscript.core/test/print-a.py
Normal file
1
pyscript.core/test/print-a.py
Normal file
@@ -0,0 +1 @@
|
||||
print("A")
|
||||
38
pyscript.core/test/py-events.html
Normal file
38
pyscript.core/test/py-events.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python events</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py">
|
||||
def print_version(event):
|
||||
import sys
|
||||
print(event.type)
|
||||
print(sys.version)
|
||||
</script>
|
||||
<button
|
||||
py-pointerdown="print_version(event)"
|
||||
py-click="print_version(event)"
|
||||
>
|
||||
python version
|
||||
</button>
|
||||
|
||||
<script type="mpy">
|
||||
def print_version(event):
|
||||
import sys
|
||||
print(event.type)
|
||||
print(sys.version)
|
||||
</script>
|
||||
<button
|
||||
mpy-pointerdown="print_version(event)"
|
||||
mpy-click="print_version(event)"
|
||||
>
|
||||
micropython version
|
||||
</button>
|
||||
</body>
|
||||
</html>
|
||||
19
pyscript.core/test/pyscript.html
Normal file
19
pyscript.core/test/pyscript.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>py-script</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<link rel="stylesheet" href="../pyscript/pyscript.css" />
|
||||
<script defer src="../pyscript/pyscript.js"></script>
|
||||
<title>py-script</title>
|
||||
</head>
|
||||
<body>
|
||||
<py-script>
|
||||
import sys
|
||||
display(sys.version, target="target")
|
||||
</py-script>
|
||||
<div id="target"></div>
|
||||
</body>
|
||||
</html>
|
||||
21
pyscript.core/test/remote.html
Normal file
21
pyscript.core/test/remote.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script defer src="./counter.js"></script>
|
||||
<script type="module" src="../min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script
|
||||
type="mpy"
|
||||
version="http://localhost:8080/micropython/micropython.mjs"
|
||||
>
|
||||
import sys
|
||||
import js
|
||||
js.document.currentScript.target.textContent = sys.version
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
26
pyscript.core/test/ruby.html
Normal file
26
pyscript.core/test/ruby.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>Ruby</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"basic-devtools": "../node_modules/basic-devtools/esm/index.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="../esm/index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="rb">
|
||||
def print_version(event)
|
||||
puts event[:type]
|
||||
print "ruby #{ RUBY_VERSION }p#{ RUBY_PATCHLEVEL }"
|
||||
end
|
||||
</script>
|
||||
<button rb-click="print_version(event)">ruby version</button>
|
||||
</body>
|
||||
</html>
|
||||
38
pyscript.core/test/shadow-dom.html
Normal file
38
pyscript.core/test/shadow-dom.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<script>
|
||||
navigator.serviceWorker.register("../sw.js");
|
||||
</script>
|
||||
<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">
|
||||
customElements.define(
|
||||
"shadow-dom",
|
||||
class extends HTMLElement {
|
||||
constructor() {
|
||||
const sd = super().attachShadow({ mode: "closed" });
|
||||
sd.appendChild(
|
||||
Object.assign(document.createElement("script"), {
|
||||
type: "mpy",
|
||||
textContent: `
|
||||
import sys
|
||||
import js
|
||||
js.document.currentScript.target.textContent = sys.version
|
||||
`,
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<shadow-dom></shadow-dom>
|
||||
</body>
|
||||
</html>
|
||||
9
pyscript.core/test/style.css
Normal file
9
pyscript.core/test/style.css
Normal file
@@ -0,0 +1,9 @@
|
||||
html {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
:root python-script {
|
||||
display: block;
|
||||
}
|
||||
34
pyscript.core/test/table.html
Normal file
34
pyscript.core/test/table.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<style>
|
||||
table {
|
||||
background-color: silver;
|
||||
}
|
||||
tr {
|
||||
background-color: white;
|
||||
color: black;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script type="module" src="../min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<table cellspacing="2" cellpadding="2">
|
||||
<tr>
|
||||
<td>check edge cases</td>
|
||||
<script type="mpy">
|
||||
import sys
|
||||
import js
|
||||
|
||||
td = js.document.createElement('td')
|
||||
td.textContent = 'OK'
|
||||
js.document.currentScript.target = td
|
||||
</script>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
49
pyscript.core/test/test.html
Normal file
49
pyscript.core/test/test.html
Normal file
@@ -0,0 +1,49 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"basic-devtools": "../node_modules/basic-devtools/esm/index.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="../esm/index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="py">
|
||||
def print_version():
|
||||
import sys
|
||||
print(sys.version)
|
||||
print("bad.", file=sys.stderr)
|
||||
</script>
|
||||
<button py-click="print_version()">python version</button>
|
||||
|
||||
<script type="mpy">
|
||||
def print_version():
|
||||
import sys
|
||||
print(sys.version)
|
||||
print("bad.", file=sys.stderr)
|
||||
</script>
|
||||
<button mpy-click="print_version()">micropython version</button>
|
||||
|
||||
<script type="lua">
|
||||
function print_version()
|
||||
print(_VERSION)
|
||||
printErr(123)
|
||||
end
|
||||
</script>
|
||||
<button lua-click="print_version()">lua version</button>
|
||||
|
||||
<script type="rb">
|
||||
def print_version()
|
||||
print "ruby #{ RUBY_VERSION }p#{ RUBY_PATCHLEVEL }"
|
||||
end
|
||||
</script>
|
||||
<button rb-click="print_version()">ruby version</button>
|
||||
</body>
|
||||
</html>
|
||||
3
pyscript.core/test/three.py
Normal file
3
pyscript.core/test/three.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import js
|
||||
|
||||
js.document.currentScript.target.textContent = 1 + 2
|
||||
35
pyscript.core/test/wasmoon.html
Normal file
35
pyscript.core/test/wasmoon.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>lua</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"basic-devtools": "../node_modules/basic-devtools/esm/index.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="../esm/index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="lua" config="./fetch.toml">
|
||||
local function read_file(path)
|
||||
local file = io.open(path, "rb")
|
||||
if not file then return nil end
|
||||
local content = file:read "*a"
|
||||
file:close()
|
||||
return content
|
||||
end
|
||||
|
||||
function print_version(event)
|
||||
print(event.type)
|
||||
print(_VERSION)
|
||||
print(read_file('/a.py'))
|
||||
end
|
||||
</script>
|
||||
<button lua-click="print_version(event)">lua version</button>
|
||||
</body>
|
||||
</html>
|
||||
77
pyscript.core/test/worker/index.html
Normal file
77
pyscript.core/test/worker/index.html
Normal file
@@ -0,0 +1,77 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<title>python workers</title>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"basic-devtools": "../../node_modules/basic-devtools/esm/index.js",
|
||||
"@pyscript/core": "../../esm/index.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script defer src="../counter.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- XWorker - JavaScript to MicroPython -->
|
||||
<script type="module">
|
||||
import { XWorker } from "@pyscript/core";
|
||||
|
||||
const w = new XWorker("./worker.py", { type: "mpy" });
|
||||
w.postMessage("JavaScript: Hello MicroPython 👋");
|
||||
w.onmessage = (event) => {
|
||||
console.log(event.data);
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- XWorker - MicroPython to MicroPython -->
|
||||
<script type="mpy">
|
||||
from js import XWorker
|
||||
|
||||
def handle_message(event):
|
||||
print(event.data)
|
||||
|
||||
w = XWorker('./worker.py')
|
||||
w.postMessage('MicroPython: Hello MicroPython 👋')
|
||||
w.onmessage = handle_message
|
||||
</script>
|
||||
|
||||
<!-- XWorker - MicroPython to Pyodide -->
|
||||
<script type="mpy">
|
||||
from js import XWorker
|
||||
|
||||
def handle_message(event):
|
||||
print(event.data)
|
||||
|
||||
w = XWorker('./worker.py', **{'type': 'py', 'async': True, 'config': '../fetch.toml'})
|
||||
w.postMessage('MicroPython: Hello Pyodide 👋')
|
||||
w.onmessage = handle_message
|
||||
</script>
|
||||
|
||||
<!-- XWorker - MicroPython to Lua -->
|
||||
<script type="mpy">
|
||||
from js import XWorker
|
||||
|
||||
def handle_message(event):
|
||||
print(event.data)
|
||||
|
||||
w = XWorker('./worker.lua', type='lua')
|
||||
w.postMessage('MicroPython: Hello Lua 👋')
|
||||
w.onmessage = handle_message
|
||||
</script>
|
||||
|
||||
<!-- XWorker - MicroPython to Ruby
|
||||
<script type="mpy">
|
||||
from js import XWorker
|
||||
|
||||
def handle_message(event):
|
||||
print(event.data)
|
||||
|
||||
w = XWorker('./worker.rb', type="rb")
|
||||
w.postMessage('MicroPython: Hello Ruby 👋')
|
||||
w.onmessage = handle_message
|
||||
</script><!---->
|
||||
</body>
|
||||
</html>
|
||||
6
pyscript.core/test/worker/worker.lua
Normal file
6
pyscript.core/test/worker/worker.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
function on_message(event)
|
||||
print(event.data)
|
||||
xworker.postMessage('Lua: Hello MicroPython 👋')
|
||||
end
|
||||
|
||||
xworker.onmessage = on_message
|
||||
9
pyscript.core/test/worker/worker.py
Normal file
9
pyscript.core/test/worker/worker.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from js import xworker
|
||||
|
||||
|
||||
def on_message(event):
|
||||
print(event.data)
|
||||
xworker.postMessage("Pyodide: Hello MicroPython 👋")
|
||||
|
||||
|
||||
xworker.onmessage = on_message
|
||||
10
pyscript.core/test/worker/worker.rb
Normal file
10
pyscript.core/test/worker/worker.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
require "js"
|
||||
|
||||
xworker = JS::eval("return xworker")
|
||||
|
||||
def on_message(event)
|
||||
puts event[:data]
|
||||
xworker.postMessage('Ruby: Hello MicroPython 👋')
|
||||
end
|
||||
|
||||
xworker.onmessage = on_message
|
||||
12
pyscript.core/tsconfig.json
Normal file
12
pyscript.core/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ES2022",
|
||||
"target": "ES2022",
|
||||
"moduleResolution": "Classic",
|
||||
"allowJs": true,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"declarationDir": "types"
|
||||
},
|
||||
"include": ["esm/index.js"]
|
||||
}
|
||||
3
pyscript.core/types/fetch-utils.d.ts
vendored
Normal file
3
pyscript.core/types/fetch-utils.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export function getBuffer(response: Response): Promise<ArrayBuffer>;
|
||||
export function getJSON(response: Response): Promise<any>;
|
||||
export function getText(response: Response): Promise<string>;
|
||||
5
pyscript.core/types/index.d.ts
vendored
Normal file
5
pyscript.core/types/index.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
export { registerPlugin } from "./plugins.js";
|
||||
export const XWorker: (
|
||||
url: string,
|
||||
options?: import("./worker/class.js").WorkerOptions,
|
||||
) => Worker;
|
||||
2
pyscript.core/types/loader.d.ts
vendored
Normal file
2
pyscript.core/types/loader.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export function getRuntime(id: string, config?: string): Promise<any>;
|
||||
export function getRuntimeID(type: string, version?: string): string;
|
||||
61
pyscript.core/types/plugins.d.ts
vendored
Normal file
61
pyscript.core/types/plugins.d.ts
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
export const PLUGINS_SELECTORS: any[];
|
||||
export function handlePlugin(node: Element): void;
|
||||
export function registerPlugin(name: string, options: PluginOptions): void;
|
||||
/**
|
||||
* plugin configuration
|
||||
*/
|
||||
export type Runtime = {
|
||||
/**
|
||||
* the runtime type
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* the bootstrapped runtime
|
||||
*/
|
||||
runtime: object;
|
||||
/**
|
||||
* an XWorker constructor that defaults to same runtime on the Worker.
|
||||
*/
|
||||
XWorker: (url: string, options?: object) => Worker;
|
||||
/**
|
||||
* a cloned config used to bootstrap the runtime
|
||||
*/
|
||||
config: object;
|
||||
/**
|
||||
* an utility to run code within the runtime
|
||||
*/
|
||||
run: (code: string) => any;
|
||||
/**
|
||||
* an utility to run code asynchronously within the runtime
|
||||
*/
|
||||
runAsync: (code: string) => Promise<any>;
|
||||
/**
|
||||
* an utility to write a file in the virtual FS, if available
|
||||
*/
|
||||
writeFile: (path: string, data: ArrayBuffer) => void;
|
||||
};
|
||||
/**
|
||||
* plugin configuration
|
||||
*/
|
||||
export type PluginOptions = {
|
||||
/**
|
||||
* the runtime/interpreter type to receive
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* the optional runtime version to use
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
* the optional config to use within such runtime
|
||||
*/
|
||||
config?: string;
|
||||
/**
|
||||
* the optional environment to use
|
||||
*/
|
||||
env?: string;
|
||||
/**
|
||||
* the callback that will be invoked once
|
||||
*/
|
||||
onRuntimeReady: (node: Element, runtime: Runtime) => void;
|
||||
};
|
||||
14
pyscript.core/types/runtime/_utils.d.ts
vendored
Normal file
14
pyscript.core/types/runtime/_utils.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export const io: WeakMap<object, any>;
|
||||
export function stdio(init: any): {
|
||||
stderr: (...args: any[]) => any;
|
||||
stdout: (...args: any[]) => any;
|
||||
get(engine: any): Promise<any>;
|
||||
};
|
||||
export function writeFile(FS: any, path: any, buffer: any): any;
|
||||
export function writeFileShim(FS: any, path: any, buffer: any): any;
|
||||
export const base: WeakMap<object, any>;
|
||||
export function fetchPaths(
|
||||
module: any,
|
||||
runtime: any,
|
||||
config_fetch: any,
|
||||
): Promise<any[]>;
|
||||
28
pyscript.core/types/runtime/micropython.d.ts
vendored
Normal file
28
pyscript.core/types/runtime/micropython.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
declare namespace _default {
|
||||
const type: string[];
|
||||
function module(): string;
|
||||
function engine(
|
||||
{
|
||||
loadMicroPython,
|
||||
}: {
|
||||
loadMicroPython: any;
|
||||
},
|
||||
config: any,
|
||||
url: any,
|
||||
): Promise<any>;
|
||||
function run(runtime: any, code: any): any;
|
||||
function runAsync(runtime: any, code: any): any;
|
||||
function runEvent(runtime: any, code: any, key: any): any;
|
||||
function runWorker(runtime: any, code: any, xworker: any): any;
|
||||
function runWorkerAsync(runtime: any, code: any, xworker: any): any;
|
||||
function writeFile(
|
||||
{
|
||||
FS,
|
||||
}: {
|
||||
FS: any;
|
||||
},
|
||||
path: any,
|
||||
buffer: any,
|
||||
): any;
|
||||
}
|
||||
export default _default;
|
||||
27
pyscript.core/types/runtime/pyodide.d.ts
vendored
Normal file
27
pyscript.core/types/runtime/pyodide.d.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
declare namespace _default {
|
||||
const type: string[];
|
||||
function module(version?: string): string;
|
||||
function engine(
|
||||
{
|
||||
loadPyodide,
|
||||
}: {
|
||||
loadPyodide: any;
|
||||
},
|
||||
config: any,
|
||||
): Promise<any>;
|
||||
function run(runtime: any, code: any): any;
|
||||
function runAsync(runtime: any, code: any): any;
|
||||
function runEvent(runtime: any, code: any, key: any): any;
|
||||
function runWorker(runtime: any, code: any, xworker: any): any;
|
||||
function runWorkerAsync(runtime: any, code: any, xworker: any): any;
|
||||
function writeFile(
|
||||
{
|
||||
FS,
|
||||
}: {
|
||||
FS: any;
|
||||
},
|
||||
path: any,
|
||||
buffer: any,
|
||||
): any;
|
||||
}
|
||||
export default _default;
|
||||
21
pyscript.core/types/runtime/ruby.d.ts
vendored
Normal file
21
pyscript.core/types/runtime/ruby.d.ts
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
declare namespace _default {
|
||||
const experimental: boolean;
|
||||
const type: string[];
|
||||
function module(version?: string): string;
|
||||
function engine(
|
||||
{
|
||||
DefaultRubyVM,
|
||||
}: {
|
||||
DefaultRubyVM: any;
|
||||
},
|
||||
config: any,
|
||||
url: any,
|
||||
): Promise<any>;
|
||||
function run(runtime: any, code: any): any;
|
||||
function runAsync(runtime: any, code: any): any;
|
||||
function runEvent(runtime: any, code: any, key: any): any;
|
||||
function runWorker(runtime: any, code: any, xworker: any): any;
|
||||
function runWorkerAsync(runtime: any, code: any, xworker: any): any;
|
||||
function writeFile(): never;
|
||||
}
|
||||
export default _default;
|
||||
35
pyscript.core/types/runtime/wasmoon.d.ts
vendored
Normal file
35
pyscript.core/types/runtime/wasmoon.d.ts
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
declare namespace _default {
|
||||
const type: string[];
|
||||
function module(version?: string): string;
|
||||
function engine(
|
||||
{
|
||||
LuaFactory,
|
||||
LuaLibraries,
|
||||
}: {
|
||||
LuaFactory: any;
|
||||
LuaLibraries: any;
|
||||
},
|
||||
config: any,
|
||||
): Promise<any>;
|
||||
function run(runtime: any, code: any): any;
|
||||
function runAsync(runtime: any, code: any): any;
|
||||
function runEvent(runtime: any, code: any, key: any): any;
|
||||
function runWorker(runtime: any, code: any, xworker: any): any;
|
||||
function runWorkerAsync(runtime: any, code: any, xworker: any): any;
|
||||
function writeFile(
|
||||
{
|
||||
cmodule: {
|
||||
module: { FS },
|
||||
},
|
||||
}: {
|
||||
cmodule: {
|
||||
module: {
|
||||
FS: any;
|
||||
};
|
||||
};
|
||||
},
|
||||
path: any,
|
||||
buffer: any,
|
||||
): any;
|
||||
}
|
||||
export default _default;
|
||||
9
pyscript.core/types/runtimes.d.ts
vendored
Normal file
9
pyscript.core/types/runtimes.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/** @type {Map<string, object>} */
|
||||
export const registry: Map<string, object>;
|
||||
/** @type {Map<string, object>} */
|
||||
export const configs: Map<string, object>;
|
||||
/** @type {string[]} */
|
||||
export const selectors: string[];
|
||||
/** @type {string[]} */
|
||||
export const prefixes: string[];
|
||||
export const runtime: Map<any, any>;
|
||||
9
pyscript.core/types/script-handler.d.ts
vendored
Normal file
9
pyscript.core/types/script-handler.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
export const runtimes: Map<any, any>;
|
||||
export function getDetails(
|
||||
type: any,
|
||||
id: any,
|
||||
name: any,
|
||||
version: any,
|
||||
config: any,
|
||||
): any;
|
||||
export function handle(script: HTMLScriptElement): Promise<void>;
|
||||
1
pyscript.core/types/toml.d.ts
vendored
Normal file
1
pyscript.core/types/toml.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export function parse(text: string): object;
|
||||
37
pyscript.core/types/utils.d.ts
vendored
Normal file
37
pyscript.core/types/utils.d.ts
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
export const isArray: (arg: any) => arg is any[];
|
||||
export const assign: {
|
||||
<T extends {}, U>(target: T, source: U): T & U;
|
||||
<T_1 extends {}, U_1, V>(
|
||||
target: T_1,
|
||||
source1: U_1,
|
||||
source2: V,
|
||||
): T_1 & U_1 & V;
|
||||
<T_2 extends {}, U_2, V_1, W>(
|
||||
target: T_2,
|
||||
source1: U_2,
|
||||
source2: V_1,
|
||||
source3: W,
|
||||
): T_2 & U_2 & V_1 & W;
|
||||
(target: object, ...sources: any[]): any;
|
||||
};
|
||||
export const create: {
|
||||
(o: object): any;
|
||||
(o: object, properties: PropertyDescriptorMap & ThisType<any>): any;
|
||||
};
|
||||
export const defineProperty: <T>(
|
||||
o: T,
|
||||
p: PropertyKey,
|
||||
attributes: PropertyDescriptor & ThisType<any>,
|
||||
) => T;
|
||||
export const all: {
|
||||
<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>[]>;
|
||||
<T_1 extends [] | readonly unknown[]>(
|
||||
values: T_1,
|
||||
): Promise<{ -readonly [P in keyof T_1]: Awaited<T_1[P]> }>;
|
||||
};
|
||||
export const resolve: {
|
||||
(): Promise<void>;
|
||||
<T>(value: T): Promise<Awaited<T>>;
|
||||
<T_1>(value: T_1 | PromiseLike<T_1>): Promise<Awaited<T_1>>;
|
||||
};
|
||||
export function absoluteURL(path: any, base?: string): string;
|
||||
21
pyscript.core/types/worker/class.d.ts
vendored
Normal file
21
pyscript.core/types/worker/class.d.ts
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
declare function _default(
|
||||
...args: any[]
|
||||
): (url: string, options?: WorkerOptions) => Worker;
|
||||
export default _default;
|
||||
/**
|
||||
* plugin configuration
|
||||
*/
|
||||
export type WorkerOptions = {
|
||||
/**
|
||||
* the runtime/interpreter type to use
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* the optional runtime version to use
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
* the optional config to use within such runtime
|
||||
*/
|
||||
config?: string;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user