mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eaa6711756 | ||
|
|
b528ba67a9 | ||
|
|
71ad1a40cb | ||
|
|
e433275938 | ||
|
|
87256a662b | ||
|
|
7336ae545e | ||
|
|
d68260c0c7 | ||
|
|
14cc05fb80 |
@@ -1,3 +1,4 @@
|
|||||||
ISSUE_TEMPLATE
|
ISSUE_TEMPLATE
|
||||||
*.min.*
|
*.min.*
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
bridge/
|
||||||
|
|||||||
@@ -13,15 +13,15 @@ Using PyScript is as simple as:
|
|||||||
<title>PyScript!</title>
|
<title>PyScript!</title>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://pyscript.net/snapshots/2024.9.2/core.css"
|
href="https://pyscript.net/releases/2025.7.2/core.css"
|
||||||
/>
|
/>
|
||||||
<script
|
<script
|
||||||
type="module"
|
type="module"
|
||||||
src="https://pyscript.net/snapshots/2024.9.2/core.js"
|
src="https://pyscript.net/releases/2025.7.2/core.js"
|
||||||
></script>
|
></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Use MicroPython to evaluate some Python -->
|
<!-- type mpy (MicroPython) or py (Pyodide) to run some Python -->
|
||||||
<script type="mpy" terminal>
|
<script type="mpy" terminal>
|
||||||
print("Hello, world!")
|
print("Hello, world!")
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
57
bridge/README.md
Normal file
57
bridge/README.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# @pyscript/bridge
|
||||||
|
|
||||||
|
Import Python utilities directly in JS
|
||||||
|
|
||||||
|
```js
|
||||||
|
// main thread
|
||||||
|
const { ffi: { func_a, func_b } } = await import('./test.js');
|
||||||
|
|
||||||
|
// test.js
|
||||||
|
import bridge from 'https://esm.run/@pyscript/bridge';
|
||||||
|
export const ffi = bridge(import.meta.url, { type: 'mpy', worker: false });
|
||||||
|
|
||||||
|
// test.py
|
||||||
|
def func_a(value):
|
||||||
|
print(f"hello {value}")
|
||||||
|
|
||||||
|
def func_b():
|
||||||
|
import sys
|
||||||
|
return sys.version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
* **type**: `py` by default to bootstrap *Pyodide*.
|
||||||
|
* **worker**: `true` by default to bootstrap in a *Web Worker*.
|
||||||
|
* **config**: either a *string* or a PyScript compatible config *JS literal* to make it possible to bootstrap files and whatnot. If specified, the `worker` becomes implicitly `true` to avoid multiple configs conflicting on the main thread.
|
||||||
|
* **env**: to share the same environment across multiple modules loaded at different times.
|
||||||
|
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Run `npx mini-coi .` within this folder to then reach out `http://localhost:8080/test/` that will show:
|
||||||
|
|
||||||
|
```
|
||||||
|
PyScript Bridge
|
||||||
|
------------------
|
||||||
|
no config
|
||||||
|
```
|
||||||
|
|
||||||
|
The [test.js](./test/test.js) files uses the following defaults:
|
||||||
|
|
||||||
|
* `type` as `"mpy"`
|
||||||
|
* `worker` as `false`
|
||||||
|
* `config` as `undefined`
|
||||||
|
* `env` as `undefined`
|
||||||
|
|
||||||
|
To test any variant use query string parameters so that `?type=py` will use `py` instead, `worker` will use a worker and `config` will use a basic *config* that brings in another file from the same folder which exposes the version.
|
||||||
|
|
||||||
|
To recap: `http://localhost:8080/test/?type=py&worker&config` will show this instead:
|
||||||
|
|
||||||
|
```
|
||||||
|
PyScript Bridge
|
||||||
|
------------------
|
||||||
|
3.12.7 (main, May 15 2025, 18:47:24) ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note when a *config* is used, the `worker` attribute is always `true`.
|
||||||
150
bridge/index.js
Normal file
150
bridge/index.js
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
/*! (c) PyScript Development Team */
|
||||||
|
|
||||||
|
const { stringify } = JSON;
|
||||||
|
const { create, entries } = Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a list of keys into a Python dictionary.
|
||||||
|
* `['a', 'b']` => `{ "a": a, "b": b }`
|
||||||
|
* @param {Iterable<string>} keys
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
const dictionary = keys => {
|
||||||
|
const fields = [];
|
||||||
|
for (const key of keys)
|
||||||
|
fields.push(`${stringify(key)}: ${key}`);
|
||||||
|
return `{ ${fields.join(',')} }`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve properly config files relative URLs.
|
||||||
|
* @param {string|Object} config - The configuration to normalize.
|
||||||
|
* @param {string} base - The base URL to resolve relative URLs against.
|
||||||
|
* @returns {string} - The JSON serialized config.
|
||||||
|
*/
|
||||||
|
const normalize = async (config, base) => {
|
||||||
|
if (typeof config === 'string') {
|
||||||
|
base = config;
|
||||||
|
config = await fetch(config).then(res => res.json());
|
||||||
|
}
|
||||||
|
if (typeof config.files === 'object') {
|
||||||
|
const files = {};
|
||||||
|
for (const [key, value] of entries(config.files)) {
|
||||||
|
files[key.startsWith('{') ? key : new URL(key, base)] = value;
|
||||||
|
}
|
||||||
|
config.files = files;
|
||||||
|
}
|
||||||
|
return stringify(config);
|
||||||
|
};
|
||||||
|
|
||||||
|
// this logic is based on a 3 levels cache ...
|
||||||
|
const cache = new Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a bridge to a Python module via a `.js` file that has a `.py` alter ego.
|
||||||
|
* @param {string} url - The URL of the JS module that has a Python counterpart.
|
||||||
|
* @param {Object} options - The options for the bridge.
|
||||||
|
* @param {string} [options.type='py'] - The `py` or `mpy` interpreter type, `py` by default.
|
||||||
|
* @param {boolean} [options.worker=true] - Whether to use a worker, `true` by default.
|
||||||
|
* @param {string|Object} [options.config=null] - The configuration for the bridge, `null` by default.
|
||||||
|
* @param {string} [options.env=null] - The optional shared environment to use.
|
||||||
|
* @param {string} [options.serviceWorker=null] - The optional service worker to use as fallback.
|
||||||
|
* @returns {Object} - The bridge to the Python module.
|
||||||
|
*/
|
||||||
|
export default (url, {
|
||||||
|
type = 'py',
|
||||||
|
worker = true,
|
||||||
|
config = null,
|
||||||
|
env = null,
|
||||||
|
serviceWorker = null,
|
||||||
|
} = {}) => {
|
||||||
|
const { protocol, host, pathname } = new URL(url);
|
||||||
|
const py = pathname.replace(/\.m?js(?:\/\+\w+)?$/, '.py');
|
||||||
|
const file = `${protocol}//${host}${py}`;
|
||||||
|
|
||||||
|
// the first cache is about the desired file in the wild ...
|
||||||
|
if (!cache.has(file)) {
|
||||||
|
// the second cache is about all fields one needs to access out there
|
||||||
|
const exports = new Map;
|
||||||
|
let python;
|
||||||
|
|
||||||
|
cache.set(file, new Proxy(create(null), {
|
||||||
|
get(_, field) {
|
||||||
|
if (!exports.has(field)) {
|
||||||
|
// create an async callback once and always return the same later on
|
||||||
|
exports.set(field, async (...args) => {
|
||||||
|
// the third cache is about reaching lazily the code only once
|
||||||
|
// augmenting its content with exports once and drop it on done
|
||||||
|
if (!python) {
|
||||||
|
// do not await or multiple calls will fetch multiple times
|
||||||
|
// just assign the fetch `Promise` once and return it
|
||||||
|
python = fetch(file).then(async response => {
|
||||||
|
const code = await response.text();
|
||||||
|
// create a unique identifier for the Python context
|
||||||
|
const identifier = pathname.replace(/[^a-zA-Z0-9_]/g, '');
|
||||||
|
const name = `__pyscript_${identifier}${Date.now()}`;
|
||||||
|
// create a Python dictionary with all accessed fields
|
||||||
|
const detail = `{"detail":${dictionary(exports.keys())}}`;
|
||||||
|
// create the arguments for the `dispatchEvent` call
|
||||||
|
const eventArgs = `${stringify(name)},${name}to_ts(${detail})`;
|
||||||
|
// bootstrap the script element type and its attributes
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.type = type;
|
||||||
|
|
||||||
|
// if config is provided it needs to be a worker to avoid
|
||||||
|
// conflicting with main config on the main thread (just like always)
|
||||||
|
script.toggleAttribute('worker', !!config || !!worker);
|
||||||
|
if (config) {
|
||||||
|
const attribute = await normalize(config, file);
|
||||||
|
script.setAttribute('config', attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env) script.setAttribute('env', env);
|
||||||
|
if (serviceWorker) script.setAttribute('service-worker', serviceWorker);
|
||||||
|
|
||||||
|
// augment the code with the previously accessed fields at the end
|
||||||
|
script.textContent = [
|
||||||
|
'\n', code, '\n',
|
||||||
|
// this is to avoid local scope name clashing
|
||||||
|
`from pyscript import window as ${name}`,
|
||||||
|
`from pyscript.ffi import to_js as ${name}to_ts`,
|
||||||
|
`${name}.dispatchEvent(${name}.CustomEvent.new(${eventArgs}))`,
|
||||||
|
// remove these references even if non-clashing to keep
|
||||||
|
// the local scope clean from undesired entries
|
||||||
|
`del ${name}`,
|
||||||
|
`del ${name}to_ts`,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
// let PyScript resolve and execute this script
|
||||||
|
document.body.appendChild(script);
|
||||||
|
|
||||||
|
// intercept once the unique event identifier with all exports
|
||||||
|
globalThis.addEventListener(
|
||||||
|
name,
|
||||||
|
event => {
|
||||||
|
resolve(event.detail);
|
||||||
|
script.remove();
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// return a promise that will resolve only once the event
|
||||||
|
// has been emitted and the interpreter evaluated the code
|
||||||
|
const { promise, resolve } = Promise.withResolvers();
|
||||||
|
return promise;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the `Promise` that will after invoke the exported field
|
||||||
|
return python.then(foreign => foreign[field](...args));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the lazily to be resolved once callback to invoke
|
||||||
|
return exports.get(field);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache.get(file);
|
||||||
|
};
|
||||||
27
bridge/package.json
Normal file
27
bridge/package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "@pyscript/bridge",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "A JS based way to use PyScript modules",
|
||||||
|
"type": "module",
|
||||||
|
"module": "./index.js",
|
||||||
|
"unpkg": "./index.js",
|
||||||
|
"jsdelivr": "./jsdelivr.js",
|
||||||
|
"browser": "./index.js",
|
||||||
|
"main": "./index.js",
|
||||||
|
"keywords": [
|
||||||
|
"PyScript",
|
||||||
|
"JS",
|
||||||
|
"Python",
|
||||||
|
"bridge"
|
||||||
|
],
|
||||||
|
"author": "Anaconda Inc.",
|
||||||
|
"license": "APACHE-2.0",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/pyscript/pyscript.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/pyscript/pyscript/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/pyscript/pyscript#readme"
|
||||||
|
}
|
||||||
33
bridge/test/index.html
Normal file
33
bridge/test/index.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>PyScript Bridge</title>
|
||||||
|
<style>body { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; }</style>
|
||||||
|
<link rel="stylesheet" href="https://pyscript.net/releases/2025.5.1/core.css" />
|
||||||
|
<script type="module" src="https://pyscript.net/releases/2025.5.1/core.js"></script>
|
||||||
|
<!-- for local testing purpose only-->
|
||||||
|
<script type="importmap">{"imports":{"https://esm.run/@pyscript/bridge":"../index.js"}}</script>
|
||||||
|
<script type="module">
|
||||||
|
const { ffi: { test_func, test_other, version } } = await import('./test.js');
|
||||||
|
|
||||||
|
console.time("⏱️ first invoke");
|
||||||
|
const result = await test_func("PyScript Bridge");
|
||||||
|
console.timeEnd("⏱️ first invoke");
|
||||||
|
|
||||||
|
document.body.append(
|
||||||
|
Object.assign(
|
||||||
|
document.createElement("h3"),
|
||||||
|
{ textContent: result },
|
||||||
|
),
|
||||||
|
document.createElement("hr"),
|
||||||
|
await version(),
|
||||||
|
);
|
||||||
|
|
||||||
|
console.time("⏱️ other invokes");
|
||||||
|
await test_other("🐍");
|
||||||
|
console.timeEnd("⏱️ other invokes");
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
40
bridge/test/remote/index.html
Normal file
40
bridge/test/remote/index.html
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>PyScript Bridge</title>
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"https://esm.run/@pyscript/bridge": "https://esm.run/@pyscript/bridge@latest",
|
||||||
|
"https://esm.run/@pyscript/bridge/test/test.js": "https://esm.run/@pyscript/bridge@latest/test/test.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>body { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; }</style>
|
||||||
|
<link rel="stylesheet" href="https://pyscript.net/releases/2025.5.1/core.css" />
|
||||||
|
<script type="module" src="https://pyscript.net/releases/2025.5.1/core.js"></script>
|
||||||
|
<script type="module">
|
||||||
|
const cdn_test = 'https://esm.run/@pyscript/bridge/test/test.js';
|
||||||
|
const { ffi: { test_func, test_other, version } } = await import(cdn_test);
|
||||||
|
|
||||||
|
console.time("⏱️ first invoke");
|
||||||
|
const result = await test_func("PyScript Bridge");
|
||||||
|
console.timeEnd("⏱️ first invoke");
|
||||||
|
|
||||||
|
document.body.append(
|
||||||
|
Object.assign(
|
||||||
|
document.createElement("h3"),
|
||||||
|
{ textContent: result },
|
||||||
|
),
|
||||||
|
document.createElement("hr"),
|
||||||
|
await version(),
|
||||||
|
);
|
||||||
|
|
||||||
|
console.time("⏱️ other invokes");
|
||||||
|
await test_other("🐍");
|
||||||
|
console.timeEnd("⏱️ other invokes");
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
5
bridge/test/sys_version.py
Normal file
5
bridge/test/sys_version.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def version():
|
||||||
|
return sys.version
|
||||||
17
bridge/test/test.js
Normal file
17
bridge/test/test.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import bridge from "https://esm.run/@pyscript/bridge";
|
||||||
|
|
||||||
|
// for local testing purpose only
|
||||||
|
const { searchParams } = new URL(location.href);
|
||||||
|
|
||||||
|
// the named (or default) export for test.py
|
||||||
|
export const ffi = bridge(import.meta.url, {
|
||||||
|
env: searchParams.get("env"),
|
||||||
|
type: searchParams.get("type") || "mpy",
|
||||||
|
worker: searchParams.has("worker"),
|
||||||
|
config: searchParams.has("config") ?
|
||||||
|
({
|
||||||
|
files: {
|
||||||
|
"./sys_version.py": "./sys_version.py",
|
||||||
|
},
|
||||||
|
}) : undefined,
|
||||||
|
});
|
||||||
22
bridge/test/test.py
Normal file
22
bridge/test/test.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from pyscript import config, RUNNING_IN_WORKER
|
||||||
|
|
||||||
|
type = config["type"]
|
||||||
|
print(f"{type}-script", RUNNING_IN_WORKER and "worker" or "main")
|
||||||
|
|
||||||
|
|
||||||
|
def test_func(message):
|
||||||
|
print("Python", message)
|
||||||
|
return message
|
||||||
|
|
||||||
|
|
||||||
|
def test_other(message):
|
||||||
|
print("Python", message)
|
||||||
|
return message
|
||||||
|
|
||||||
|
|
||||||
|
def version():
|
||||||
|
try:
|
||||||
|
from sys_version import version
|
||||||
|
except ImportError:
|
||||||
|
version = lambda: "no config"
|
||||||
|
return version()
|
||||||
616
core/package-lock.json
generated
616
core/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@pyscript/core",
|
"name": "@pyscript/core",
|
||||||
"version": "0.6.53",
|
"version": "0.6.65",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "PyScript",
|
"description": "PyScript",
|
||||||
"module": "./index.js",
|
"module": "./index.js",
|
||||||
@@ -67,10 +67,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ungap/with-resolvers": "^0.1.0",
|
"@ungap/with-resolvers": "^0.1.0",
|
||||||
"@webreflection/idb-map": "^0.3.2",
|
"@webreflection/idb-map": "^0.3.2",
|
||||||
"@webreflection/utils": "^0.1.0",
|
"@webreflection/utils": "^0.1.1",
|
||||||
"add-promise-listener": "^0.1.3",
|
"add-promise-listener": "^0.1.3",
|
||||||
"basic-devtools": "^0.1.6",
|
"basic-devtools": "^0.1.6",
|
||||||
"polyscript": "^0.17.20",
|
"polyscript": "^0.18.3",
|
||||||
"sticky-module": "^0.1.1",
|
"sticky-module": "^0.1.1",
|
||||||
"to-json-callback": "^0.1.1",
|
"to-json-callback": "^0.1.1",
|
||||||
"type-checked-collections": "^0.1.7"
|
"type-checked-collections": "^0.1.7"
|
||||||
@@ -78,24 +78,24 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@codemirror/commands": "^6.8.1",
|
"@codemirror/commands": "^6.8.1",
|
||||||
"@codemirror/lang-python": "^6.2.1",
|
"@codemirror/lang-python": "^6.2.1",
|
||||||
"@codemirror/language": "^6.11.0",
|
"@codemirror/language": "^6.11.2",
|
||||||
"@codemirror/state": "^6.5.2",
|
"@codemirror/state": "^6.5.2",
|
||||||
"@codemirror/view": "^6.36.8",
|
"@codemirror/view": "^6.38.0",
|
||||||
"@playwright/test": "^1.52.0",
|
"@playwright/test": "^1.54.0",
|
||||||
"@rollup/plugin-commonjs": "^28.0.3",
|
"@rollup/plugin-commonjs": "^28.0.6",
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@webreflection/toml-j0.4": "^1.1.4",
|
"@webreflection/toml-j0.4": "^1.1.4",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/addon-web-links": "^0.11.0",
|
"@xterm/addon-web-links": "^0.11.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"bun": "^1.2.13",
|
"bun": "^1.2.18",
|
||||||
"chokidar": "^4.0.3",
|
"chokidar": "^4.0.3",
|
||||||
"codedent": "^0.1.2",
|
"codedent": "^0.1.2",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.2",
|
||||||
"eslint": "^9.27.0",
|
"eslint": "^9.30.1",
|
||||||
"flatted": "^3.3.3",
|
"flatted": "^3.3.3",
|
||||||
"rollup": "^4.41.0",
|
"rollup": "^4.44.2",
|
||||||
"rollup-plugin-postcss": "^4.0.2",
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
"rollup-plugin-string": "^3.0.0",
|
"rollup-plugin-string": "^3.0.0",
|
||||||
"static-handler": "^0.5.3",
|
"static-handler": "^0.5.3",
|
||||||
|
|||||||
2
core/src/3rd-party/xterm.css
vendored
2
core/src/3rd-party/xterm.css
vendored
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Minified by jsDelivr using clean-css v5.3.2.
|
* Minified by jsDelivr using clean-css v5.3.3.
|
||||||
* Original file: /npm/@xterm/xterm@5.5.0/css/xterm.css
|
* Original file: /npm/@xterm/xterm@5.5.0/css/xterm.css
|
||||||
*
|
*
|
||||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||||
|
|||||||
@@ -154,6 +154,9 @@ for (const [TYPE] of TYPES) {
|
|||||||
return await Promise.all(toBeAwaited);
|
return await Promise.all(toBeAwaited);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (Number.isSafeInteger(parsed?.experimental_ffi_timeout))
|
||||||
|
globalThis.reflected_ffi_timeout = parsed?.experimental_ffi_timeout;
|
||||||
|
|
||||||
configs.set(TYPE, { config: parsed, configURL, plugins, error });
|
configs.set(TYPE, { config: parsed, configURL, plugins, error });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const getRelatedScript = (target, type) => {
|
|||||||
return editor?.parentNode?.previousElementSibling;
|
return editor?.parentNode?.previousElementSibling;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function execute({ currentTarget }) {
|
async function execute({ currentTarget, script }) {
|
||||||
const { env, pySrc, outDiv } = this;
|
const { env, pySrc, outDiv } = this;
|
||||||
const hasRunButton = !!currentTarget;
|
const hasRunButton = !!currentTarget;
|
||||||
|
|
||||||
@@ -91,14 +91,13 @@ async function execute({ currentTarget }) {
|
|||||||
// creation and destruction of editors on the fly
|
// creation and destruction of editors on the fly
|
||||||
if (hasRunButton) {
|
if (hasRunButton) {
|
||||||
for (const type of TYPES.keys()) {
|
for (const type of TYPES.keys()) {
|
||||||
const script = getRelatedScript(currentTarget, type);
|
script = getRelatedScript(currentTarget, type);
|
||||||
if (script) {
|
if (script) break;
|
||||||
defineProperties(script, { xworker: { value: xworker } });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineProperties(script, { xworker: { value: xworker } });
|
||||||
|
|
||||||
const { sync } = xworker;
|
const { sync } = xworker;
|
||||||
const { promise, resolve } = withResolvers();
|
const { promise, resolve } = withResolvers();
|
||||||
envs.set(env, promise);
|
envs.set(env, promise);
|
||||||
@@ -157,6 +156,20 @@ async function execute({ currentTarget }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const replaceScript = (script, type) => {
|
||||||
|
script.xworker?.terminate();
|
||||||
|
const clone = script.cloneNode(true);
|
||||||
|
clone.type = `${type}-editor`;
|
||||||
|
const editor = editors.get(script);
|
||||||
|
if (editor) {
|
||||||
|
const content = editor.state.doc.toString();
|
||||||
|
clone.textContent = content;
|
||||||
|
editors.delete(script);
|
||||||
|
script.nextElementSibling.remove();
|
||||||
|
}
|
||||||
|
script.replaceWith(clone);
|
||||||
|
};
|
||||||
|
|
||||||
const makeRunButton = (handler, type) => {
|
const makeRunButton = (handler, type) => {
|
||||||
const runButton = document.createElement("button");
|
const runButton = document.createElement("button");
|
||||||
runButton.className = `absolute ${type}-editor-run-button`;
|
runButton.className = `absolute ${type}-editor-run-button`;
|
||||||
@@ -169,15 +182,25 @@ const makeRunButton = (handler, type) => {
|
|||||||
) {
|
) {
|
||||||
const script = getRelatedScript(runButton, type);
|
const script = getRelatedScript(runButton, type);
|
||||||
if (script) {
|
if (script) {
|
||||||
const editor = editors.get(script);
|
const env = script.getAttribute("env");
|
||||||
const content = editor.state.doc.toString();
|
// remove the bootstrapped env which could be one or shared
|
||||||
const clone = script.cloneNode(true);
|
if (env) {
|
||||||
clone.type = `${type}-editor`;
|
for (const [key, value] of TYPES) {
|
||||||
clone.textContent = content;
|
if (key === type) {
|
||||||
script.xworker.terminate();
|
configs.delete(`${value}-${env}`);
|
||||||
script.nextElementSibling.remove();
|
envs.delete(`${value}-${env}`);
|
||||||
script.replaceWith(clone);
|
break;
|
||||||
editors.delete(script);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// lonley script without setup node should be replaced
|
||||||
|
if (script.xworker) replaceScript(script, type);
|
||||||
|
// all scripts sharing the same env should be replaced
|
||||||
|
else {
|
||||||
|
const sel = `script[type^="${type}-editor"][env="${env}"]`;
|
||||||
|
for (const script of document.querySelectorAll(sel))
|
||||||
|
replaceScript(script, type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -365,7 +388,7 @@ const init = async (script, type, interpreter) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isSetup) {
|
if (isSetup) {
|
||||||
await context.handleEvent({ currentTarget: null });
|
await context.handleEvent({ currentTarget: null, script });
|
||||||
notifyEditor();
|
notifyEditor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -16,3 +16,27 @@ except:
|
|||||||
|
|
||||||
create_proxy = _cp
|
create_proxy = _cp
|
||||||
to_js = _tjs
|
to_js = _tjs
|
||||||
|
|
||||||
|
try:
|
||||||
|
from polyscript import ffi as _ffi
|
||||||
|
|
||||||
|
direct = _ffi.direct
|
||||||
|
gather = _ffi.gather
|
||||||
|
query = _ffi.query
|
||||||
|
|
||||||
|
def assign(source, *args):
|
||||||
|
for arg in args:
|
||||||
|
_ffi.assign(source, to_js(arg))
|
||||||
|
return source
|
||||||
|
|
||||||
|
except:
|
||||||
|
import js
|
||||||
|
|
||||||
|
_assign = js.Object.assign
|
||||||
|
|
||||||
|
direct = lambda source: source
|
||||||
|
|
||||||
|
def assign(source, *args):
|
||||||
|
for arg in args:
|
||||||
|
_assign(source, to_js(arg))
|
||||||
|
return source
|
||||||
|
|||||||
@@ -124,6 +124,11 @@ class Element:
|
|||||||
# Element instance via `for_`).
|
# Element instance via `for_`).
|
||||||
if name.endswith("_"):
|
if name.endswith("_"):
|
||||||
name = name[:-1] # noqa: FURB188 No str.removesuffix() in MicroPython.
|
name = name[:-1] # noqa: FURB188 No str.removesuffix() in MicroPython.
|
||||||
|
if name == "for":
|
||||||
|
# The `for` attribute is a special case as it is a keyword in both
|
||||||
|
# Python and JavaScript.
|
||||||
|
# We need to get it from the underlying DOM element as `htmlFor`.
|
||||||
|
name = "htmlFor"
|
||||||
return getattr(self._dom_element, name)
|
return getattr(self._dom_element, name)
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
@@ -142,6 +147,11 @@ class Element:
|
|||||||
# Element instance via `for_`).
|
# Element instance via `for_`).
|
||||||
if name.endswith("_"):
|
if name.endswith("_"):
|
||||||
name = name[:-1] # noqa: FURB188 No str.removesuffix() in MicroPython.
|
name = name[:-1] # noqa: FURB188 No str.removesuffix() in MicroPython.
|
||||||
|
if name == "for":
|
||||||
|
# The `for` attribute is a special case as it is a keyword in both
|
||||||
|
# Python and JavaScript.
|
||||||
|
# We need to set it on the underlying DOM element as `htmlFor`.
|
||||||
|
name = "htmlFor"
|
||||||
|
|
||||||
if name.startswith("on_"):
|
if name.startswith("on_"):
|
||||||
# Ensure on-events are cached in the _on_events dict if the
|
# Ensure on-events are cached in the _on_events dict if the
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
36
core/tests/javascript/worker-symbols.html
Normal file
36
core/tests/javascript/worker-symbols.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>PyScript VS Symbols</title>
|
||||||
|
<script>
|
||||||
|
globalThis.hasSymbol = (symbol, ref) => symbol in ref;
|
||||||
|
globalThis.getSymbol = (symbol, ref) => ref[symbol];
|
||||||
|
|
||||||
|
// some 3rd party JS library might use symbols to brand-check
|
||||||
|
// so it's not about symbols traveling from MicroPython
|
||||||
|
// it's about MicroPython proxies traps not understanding symbols
|
||||||
|
globalThis.hasIterator = ref => Symbol.iterator in ref;
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="../../dist/core.css">
|
||||||
|
<script type="module" src="../../dist/core.js"></script>
|
||||||
|
<script type="mpy">
|
||||||
|
import js
|
||||||
|
|
||||||
|
symbol = js.Symbol.iterator
|
||||||
|
|
||||||
|
if js.getSymbol(symbol, []) and js.hasSymbol(symbol, []) and js.hasIterator([]):
|
||||||
|
js.document.documentElement.classList.add("main")
|
||||||
|
</script>
|
||||||
|
<script type="mpy" worker>
|
||||||
|
from pyscript import window
|
||||||
|
import js
|
||||||
|
|
||||||
|
symbol = js.Symbol.iterator
|
||||||
|
|
||||||
|
if window.getSymbol(symbol, []) and window.hasSymbol(symbol, []) and window.hasIterator([]):
|
||||||
|
window.document.documentElement.classList.add("worker")
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
@@ -59,6 +59,11 @@ test('MicroPython + configURL', async ({ page }) => {
|
|||||||
await page.waitForSelector('html.main.worker');
|
await page.waitForSelector('html.main.worker');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('MicroPython + Symbols', async ({ page }) => {
|
||||||
|
await page.goto('http://localhost:8080/tests/javascript/worker-symbols.html');
|
||||||
|
await page.waitForSelector('html.main.worker');
|
||||||
|
});
|
||||||
|
|
||||||
test('Pyodide + terminal on Main', async ({ page }) => {
|
test('Pyodide + terminal on Main', async ({ page }) => {
|
||||||
await page.goto('http://localhost:8080/tests/javascript/py-terminal-main.html');
|
await page.goto('http://localhost:8080/tests/javascript/py-terminal-main.html');
|
||||||
await page.waitForSelector('html.ok');
|
await page.waitForSelector('html.ok');
|
||||||
|
|||||||
21
core/tests/manual/ffi_timeout/index.html
Normal file
21
core/tests/manual/ffi_timeout/index.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="../../../dist/core.css">
|
||||||
|
<script>
|
||||||
|
window.Worker = class extends Worker {
|
||||||
|
constructor(url, ...rest) {
|
||||||
|
console.log(rest[0]);
|
||||||
|
return super(url, ...rest);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.start = Date.now();
|
||||||
|
</script>
|
||||||
|
<script type="module" src="../../../dist/core.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="py" config="./index.toml" src="index.py" worker></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3
core/tests/manual/ffi_timeout/index.py
Normal file
3
core/tests/manual/ffi_timeout/index.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from pyscript import document, window
|
||||||
|
|
||||||
|
document.body.append(window.Date.now() - window.start)
|
||||||
2
core/tests/manual/ffi_timeout/index.toml
Normal file
2
core/tests/manual/ffi_timeout/index.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
experimental_ffi_timeout = 0
|
||||||
|
package_cache = "passthrough"
|
||||||
@@ -24,5 +24,6 @@
|
|||||||
"./example_js_worker_module.js": "greeting_worker"
|
"./example_js_worker_module.js": "greeting_worker"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages": ["Pillow" ]
|
"packages": ["Pillow" ],
|
||||||
|
"experimental_ffi_timeout": 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -871,7 +871,17 @@ class TestElements:
|
|||||||
self._create_el_and_basic_asserts("kbd", "some text")
|
self._create_el_and_basic_asserts("kbd", "some text")
|
||||||
|
|
||||||
def test_label(self):
|
def test_label(self):
|
||||||
self._create_el_and_basic_asserts("label", "some text")
|
label_text = "Luke, I am your father"
|
||||||
|
label_for = "some-id"
|
||||||
|
# Let's create the element
|
||||||
|
el = web.label(label_text, for_=label_for)
|
||||||
|
# Let's check the element was configured correctly.
|
||||||
|
assert isinstance(el, web.label), "The new element should be a label."
|
||||||
|
assert el.textContent == label_text, "The label text should match."
|
||||||
|
assert el._dom_element.tagName == "LABEL"
|
||||||
|
assert el.for_ == label_for, "The label should have the correct for attribute."
|
||||||
|
# Ensure the label element is rendered with the correct "for" attribute
|
||||||
|
assert f'for="{label_for}"' in el.outerHTML, "The label should have the correct 'for' attribute in its HTML."
|
||||||
|
|
||||||
def test_legend(self):
|
def test_legend(self):
|
||||||
self._create_el_and_basic_asserts("legend", "some text")
|
self._create_el_and_basic_asserts("legend", "some text")
|
||||||
|
|||||||
@@ -137,12 +137,14 @@
|
|||||||
<body>
|
<body>
|
||||||
<h1>Hello world!</h1>
|
<h1>Hello world!</h1>
|
||||||
<p>These are the Python interpreters in PyScript _VERSION_:</p>
|
<p>These are the Python interpreters in PyScript _VERSION_:</p>
|
||||||
<script type="py"> <!-- Pyodide -->
|
<script type="py">
|
||||||
|
# Pyodide
|
||||||
from pyscript import display
|
from pyscript import display
|
||||||
import sys
|
import sys
|
||||||
display(sys.version)
|
display(sys.version)
|
||||||
</script>
|
</script>
|
||||||
<script type="mpy"> <!-- MicroPython -->
|
<script type="mpy">
|
||||||
|
# MicroPython
|
||||||
from pyscript import display
|
from pyscript import display
|
||||||
import sys
|
import sys
|
||||||
display(sys.version)
|
display(sys.version)
|
||||||
|
|||||||
Reference in New Issue
Block a user