Added py:all-done event (#1756)

This commit is contained in:
Andrea Giammarchi
2023-09-26 15:56:50 +02:00
committed by GitHub
parent b9a1227e47
commit 3ac2ac0982
11 changed files with 239 additions and 74 deletions

View File

@@ -1,17 +1,17 @@
{ {
"name": "@pyscript/core", "name": "@pyscript/core",
"version": "0.2.3", "version": "0.2.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@pyscript/core", "name": "@pyscript/core",
"version": "0.2.3", "version": "0.2.4",
"license": "APACHE-2.0", "license": "APACHE-2.0",
"dependencies": { "dependencies": {
"@ungap/with-resolvers": "^0.1.0", "@ungap/with-resolvers": "^0.1.0",
"basic-devtools": "^0.1.6", "basic-devtools": "^0.1.6",
"polyscript": "^0.4.2" "polyscript": "^0.4.6"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.1", "@rollup/plugin-node-resolve": "^15.2.1",
@@ -49,9 +49,9 @@
} }
}, },
"node_modules/@eslint-community/regexpp": { "node_modules/@eslint-community/regexpp": {
"version": "4.8.1", "version": "4.8.2",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.2.tgz",
"integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", "integrity": "sha512-0MGxAVt1m/ZK+LTJp/j0qF7Hz97D9O/FH9Ms3ltnyIdDD57cbb1ACIQTkbHvNXtWDv5TPq7w5Kq56+cNukbo7g==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0" "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
@@ -851,9 +851,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.528", "version": "1.4.529",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.528.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.529.tgz",
"integrity": "sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA==", "integrity": "sha512-6uyPyXTo8lkv8SWAmjKFbG42U073TXlzD4R8rW3EzuznhFS2olCIAfjjQtV2dV2ar/vRF55KUd3zQYnCB0dd3A==",
"dev": true "dev": true
}, },
"node_modules/entities": { "node_modules/entities": {
@@ -1278,15 +1278,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/import-fresh/node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/import-from": { "node_modules/import-from": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz",
@@ -1299,6 +1290,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/import-from/node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/imurmurhash": { "node_modules/imurmurhash": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -1776,9 +1776,9 @@
"integrity": "sha512-yyVAOFKTAElc7KdLt2+UKGExNYwYb/Y/WE9i+1ezCQsJE8gbKSjewfpRqK2nQgZ4d4hhAAGgDCOcIZVilqE5UA==" "integrity": "sha512-yyVAOFKTAElc7KdLt2+UKGExNYwYb/Y/WE9i+1ezCQsJE8gbKSjewfpRqK2nQgZ4d4hhAAGgDCOcIZVilqE5UA=="
}, },
"node_modules/polyscript": { "node_modules/polyscript": {
"version": "0.4.2", "version": "0.4.6",
"resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.4.2.tgz", "resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.4.6.tgz",
"integrity": "sha512-3mM5Y/DdpYND8/INAUmgF5VL4InVc04xADB+of129t8RjXi3eZK4xoGRPZdTYzW+wM56WNptnC8fC9Zt7jKLoA==", "integrity": "sha512-yRL8iwa8NHCWYIkYIRZ7Ujwd69WaDKAoeFxhQRLkTmcdlKKFxoFJStwyb5PONWZUl+mb+oXGkrPPsRaAJHHipQ==",
"dependencies": { "dependencies": {
"@ungap/structured-clone": "^1.2.0", "@ungap/structured-clone": "^1.2.0",
"@ungap/with-resolvers": "^0.1.0", "@ungap/with-resolvers": "^0.1.0",
@@ -2421,12 +2421,12 @@
} }
}, },
"node_modules/resolve-from": { "node_modules/resolve-from": {
"version": "5.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=4"
} }
}, },
"node_modules/reusify": { "node_modules/reusify": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@pyscript/core", "name": "@pyscript/core",
"version": "0.2.3", "version": "0.2.4",
"type": "module", "type": "module",
"description": "PyScript", "description": "PyScript",
"module": "./index.js", "module": "./index.js",
@@ -33,7 +33,7 @@
"dependencies": { "dependencies": {
"@ungap/with-resolvers": "^0.1.0", "@ungap/with-resolvers": "^0.1.0",
"basic-devtools": "^0.1.6", "basic-devtools": "^0.1.6",
"polyscript": "^0.4.2" "polyscript": "^0.4.6"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.1", "@rollup/plugin-node-resolve": "^15.2.1",

View File

@@ -0,0 +1,62 @@
import TYPES from "./types.js";
import hooks from "./hooks.js";
const DONE = "py:all-done";
const {
onAfterRun,
onAfterRunAsync,
codeAfterRunWorker,
codeAfterRunWorkerAsync,
} = hooks;
const waitForIt = [];
const codes = [];
const codeFor = (element) => {
const isAsync = element.hasAttribute("async");
const { promise, resolve } = Promise.withResolvers();
const type = `${DONE}:${waitForIt.push(promise)}`;
// resolve each promise once notified
addEventListener(type, resolve, { once: true });
if (element.hasAttribute("worker")) {
const code = `
from pyscript import window as _w
_w.dispatchEvent(_w.Event.new("${type}"))
`;
if (isAsync) codeAfterRunWorkerAsync.add(code);
else codeAfterRunWorker.add(code);
return code;
}
// dispatch only once the ready element is the same
const code = (_, el) => {
if (el === element) dispatchEvent(new Event(type));
};
if (isAsync) onAfterRunAsync.add(code);
else onAfterRun.add(code);
return code;
};
const selector = [];
for (const [TYPE] of TYPES)
selector.push(`script[type="${TYPE}"]`, `${TYPE}-script`);
// loop over all known scripts and elements
for (const element of document.querySelectorAll(selector.join(",")))
codes.push(codeFor(element));
// wait for all the things then cleanup
Promise.all(waitForIt).then(() => {
// cleanup unnecessary hooks
for (const code of codes) {
onAfterRun.delete(code);
onAfterRunAsync.delete(code);
codeAfterRunWorker.delete(code);
codeAfterRunWorkerAsync.delete(code);
}
dispatchEvent(new Event(DONE));
});

View File

@@ -1,16 +1,21 @@
/*! (c) PyScript Development Team */ /*! (c) PyScript Development Team */
import "@ungap/with-resolvers"; import "@ungap/with-resolvers";
import { INVALID_CONTENT, define, XWorker } from "polyscript";
// TODO: this is not strictly polyscript related but handy ... not sure // These imports can hook more than usual and help debugging possible polyscript issues
// we should factor this utility out a part but this works anyway. import {
INVALID_CONTENT,
define,
XWorker,
} from "../node_modules/polyscript/esm/index.js";
import { queryTarget } from "../node_modules/polyscript/esm/script-handler.js"; import { queryTarget } from "../node_modules/polyscript/esm/script-handler.js";
import { dedent, dispatch } from "../node_modules/polyscript/esm/utils.js"; import { dedent, dispatch } from "../node_modules/polyscript/esm/utils.js";
import { Hook } from "../node_modules/polyscript/esm/worker/hooks.js"; import { Hook } from "../node_modules/polyscript/esm/worker/hooks.js";
import "./all-done.js";
import TYPES from "./types.js"; import TYPES from "./types.js";
import configs from "./config.js"; import configs from "./config.js";
import hooks from "./hooks.js";
import sync from "./sync.js"; import sync from "./sync.js";
import stdlib from "./stdlib.js"; import stdlib from "./stdlib.js";
import { ErrorCode } from "./exceptions.js"; import { ErrorCode } from "./exceptions.js";
@@ -66,28 +71,6 @@ const registerModule = ({ XWorker: $XWorker, interpreter, io }) => {
interpreter.runPython(stdlib, { globals: interpreter.runPython("{}") }); interpreter.runPython(stdlib, { globals: interpreter.runPython("{}") });
}; };
export const hooks = {
/** @type {Set<function>} */
onBeforeRun: new Set(),
/** @type {Set<function>} */
onBeforeRunAsync: new Set(),
/** @type {Set<function>} */
onAfterRun: new Set(),
/** @type {Set<function>} */
onAfterRunAsync: new Set(),
/** @type {Set<function>} */
onInterpreterReady: new Set(),
/** @type {Set<string>} */
codeBeforeRunWorker: new Set(),
/** @type {Set<string>} */
codeBeforeRunWorkerAsync: new Set(),
/** @type {Set<string>} */
codeAfterRunWorker: new Set(),
/** @type {Set<string>} */
codeAfterRunWorkerAsync: new Set(),
};
const workerHooks = { const workerHooks = {
codeBeforeRunWorker: () => codeBeforeRunWorker: () =>
[stdlib, ...hooks.codeBeforeRunWorker].map(dedent).join("\n"), [stdlib, ...hooks.codeBeforeRunWorker].map(dedent).join("\n"),
@@ -100,7 +83,7 @@ const workerHooks = {
}; };
const exportedConfig = {}; const exportedConfig = {};
export { exportedConfig as config }; export { exportedConfig as config, hooks };
for (const [TYPE, interpreter] of TYPES) { for (const [TYPE, interpreter] of TYPES) {
const { config, plugins, error } = configs.get(TYPE); const { config, plugins, error } = configs.get(TYPE);

View File

@@ -0,0 +1,21 @@
export default {
/** @type {Set<function>} */
onBeforeRun: new Set(),
/** @type {Set<function>} */
onBeforeRunAsync: new Set(),
/** @type {Set<function>} */
onAfterRun: new Set(),
/** @type {Set<function>} */
onAfterRunAsync: new Set(),
/** @type {Set<function>} */
onInterpreterReady: new Set(),
/** @type {Set<string>} */
codeBeforeRunWorker: new Set(),
/** @type {Set<string>} */
codeBeforeRunWorkerAsync: new Set(),
/** @type {Set<string>} */
codeAfterRunWorker: new Set(),
/** @type {Set<string>} */
codeAfterRunWorkerAsync: new Set(),
};

View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../dist/core.css">
<script type="module">
import '../dist/core.js';
document.body.append('loading ...', document.createElement('br'));
addEventListener(
'py:all-done',
() => {
document.body.append('all executed');
},
{ once: true }
);
</script>
</head>
<body>
<script type="py">
print(1)
</script>
<py-script>
print(2)
</py-script>
<py-script async>
print(3)
</py-script>
<script type="py" worker>
print(4)
</script>
<py-script async worker>
print(5)
</py-script>
</body>
</html>

View File

@@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PyScript Next</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
<script type="module">
const loader = document.querySelector('#loader');
loader.showModal();
addEventListener(
'py:all-done',
() => {
loader.close();
},
{ once: true }
);
</script>
<script type="py">
from pyscript import document
document.body.textContent = "PyScript Ready";
</script>
</head>
<body>
<dialog id="loader">
Loading PyScript ...
</dialog>
</body>
</html>

View File

@@ -20,8 +20,10 @@ class TestBasic(PyScriptTest):
</py-script> </py-script>
""" """
) )
assert self.console.log.lines == ["hello from script py", assert self.console.log.lines == [
"hello from py-script"] "hello from script py",
"hello from py-script",
]
def test_execution_thread(self): def test_execution_thread(self):
self.pyscript_run( self.pyscript_run(
@@ -162,10 +164,12 @@ class TestBasic(PyScriptTest):
""" """
) )
assert self.console.log.lines[-4:] == ["A true false", assert self.console.log.lines[-4:] == [
"B <div></div>", "A true false",
"C true false", "B <div></div>",
"D <div></div>"] "C true false",
"D <div></div>",
]
def test_packages(self): def test_packages(self):
self.pyscript_run( self.pyscript_run(
@@ -346,10 +350,15 @@ class TestBasic(PyScriptTest):
) )
pyscript_tag = self.page.locator("py-script") pyscript_tag = self.page.locator("py-script")
assert pyscript_tag.inner_html() == "" assert pyscript_tag.inner_html() == ""
assert pyscript_tag.evaluate("node => node.srcCode") == 'print("hello from py-script")' assert (
pyscript_tag.evaluate("node => node.srcCode")
== 'print("hello from py-script")'
)
script_py_tag = self.page.locator('script[type="py"]') script_py_tag = self.page.locator('script[type="py"]')
assert script_py_tag.evaluate("node => node.srcCode") == 'print("hello from script py")' assert (
script_py_tag.evaluate("node => node.srcCode")
== 'print("hello from script py")'
)
def test_py_attribute_without_id(self): def test_py_attribute_without_id(self):
self.pyscript_run( self.pyscript_run(
@@ -366,3 +375,20 @@ class TestBasic(PyScriptTest):
self.wait_for_console("hello world!") self.wait_for_console("hello world!")
assert self.console.log.lines[-1] == "hello world!" assert self.console.log.lines[-1] == "hello world!"
assert self.console.error.lines == [] assert self.console.error.lines == []
def test_py_all_done_event(self):
self.pyscript_run(
"""
<script>
addEventListener("py:all-done", () => console.log("2"))
</script>
<script type="py">
print("1")
</script>
"""
)
btn = self.page.wait_for_selector("button")
btn.click()
self.wait_for_console("1")
assert self.console.log.lines[-1] == "2"
assert self.console.error.lines == []

1
pyscript.core/types/all-done.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

View File

@@ -10,17 +10,7 @@ export function PyWorker(file: string, options?: {
}): Worker & { }): Worker & {
sync: ProxyHandler<object>; sync: ProxyHandler<object>;
}; };
export namespace hooks {
let onBeforeRun: Set<Function>;
let onBeforeRunAsync: Set<Function>;
let onAfterRun: Set<Function>;
let onAfterRunAsync: Set<Function>;
let onInterpreterReady: Set<Function>;
let codeBeforeRunWorker: Set<string>;
let codeBeforeRunWorkerAsync: Set<string>;
let codeAfterRunWorker: Set<string>;
let codeAfterRunWorkerAsync: Set<string>;
}
export { exportedConfig as config };
import sync from "./sync.js"; import sync from "./sync.js";
declare const exportedConfig: {}; declare const exportedConfig: {};
import hooks from "./hooks.js";
export { exportedConfig as config, hooks };

12
pyscript.core/types/hooks.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
declare namespace _default {
let onBeforeRun: Set<Function>;
let onBeforeRunAsync: Set<Function>;
let onAfterRun: Set<Function>;
let onAfterRunAsync: Set<Function>;
let onInterpreterReady: Set<Function>;
let codeBeforeRunWorker: Set<string>;
let codeBeforeRunWorkerAsync: Set<string>;
let codeAfterRunWorker: Set<string>;
let codeAfterRunWorkerAsync: Set<string>;
}
export default _default;