[next] Bootstrap plugins directly (#1698)

This commit is contained in:
Andrea Giammarchi
2023-09-14 11:11:44 +02:00
committed by GitHub
parent 7994207c78
commit 3aef5a99dc
8 changed files with 70 additions and 47 deletions

View File

@@ -10,7 +10,11 @@ for (const file of readdirSync(join(__dirname, "..", "src", "plugins"))) {
? name ? name
: `[${JSON.stringify(name)}]`; : `[${JSON.stringify(name)}]`;
const value = JSON.stringify(`./plugins/${file}`); const value = JSON.stringify(`./plugins/${file}`);
plugins.push(` ${key}: () => import(${value}),`); plugins.push(
// this comment is needed to avoid bundlers eagerly embedding lazy
// dependencies, causing all sort of issues once in production
` ${key}: () => import(/* webpackIgnore: true */ ${value}),`,
);
} }
} }

View File

@@ -0,0 +1,44 @@
/**
* This file parses a generic <py-config> or config attribute
* to use as base config for all py-script elements, importing
* also a queue of plugins *before* the interpreter (if any) resolves.
*/
import { $ } from "basic-devtools";
import allPlugins from "./plugins.js";
import { robustFetch as fetch, getText } from "./fetch.js";
// TODO: this is not strictly polyscript related but handy ... not sure
// we should factor this utility out a part but this works anyway.
import { parse } from "../node_modules/polyscript/esm/toml.js";
// find the shared config for all py-script elements
let config, plugins, parsed;
let pyConfig = $("py-config");
if (pyConfig) config = pyConfig.getAttribute("src") || pyConfig.textContent;
else {
pyConfig = $('script[type="py"][config]');
if (pyConfig) config = pyConfig.getAttribute("config");
}
// load its content if remote
if (/^https?:\/\//.test(config)) config = await fetch(config).then(getText);
// parse config only if not empty
if (config?.trim()) {
try {
parsed = JSON.parse(config);
} catch (_) {
parsed = await parse(config);
}
}
// parse all plugins and optionally ignore only
// those flagged as "undesired" via `!` prefix
const toBeAwaited = [];
for (const [key, value] of Object.entries(allPlugins)) {
if (!parsed?.plugins?.includes(`!${key}`)) toBeAwaited.push(value());
}
if (toBeAwaited.length) plugins = Promise.all(toBeAwaited);
export { config, plugins };

View File

@@ -1,12 +1,7 @@
/*! (c) PyScript Development Team */ /*! (c) PyScript Development Team */
import "@ungap/with-resolvers"; import "@ungap/with-resolvers";
import { $ } from "basic-devtools";
import { define, XWorker } from "polyscript"; import { define, XWorker } from "polyscript";
import sync from "./sync.js";
import stdlib from "./stdlib.js";
import plugins from "./plugins.js";
// TODO: this is not strictly polyscript related but handy ... not sure // TODO: this is not strictly polyscript related but handy ... not sure
// we should factor this utility out a part but this works anyway. // we should factor this utility out a part but this works anyway.
@@ -14,29 +9,21 @@ 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 { robustFetch as fetch } from "./fetch.js"; import sync from "./sync.js";
import stdlib from "./stdlib.js";
import { config, plugins } from "./config.js";
import { robustFetch as fetch, getText } from "./fetch.js";
const { assign, defineProperty, entries } = Object; const { assign, defineProperty, entries } = Object;
const getText = (body) => body.text(); const TYPE = "py";
// allows lazy element features on code evaluation // allows lazy element features on code evaluation
let currentElement; let currentElement;
// create a unique identifier when/if needed // create a unique identifier when/if needed
let id = 0; let id = 0;
const getID = (prefix = "py") => `${prefix}-${id++}`; const getID = (prefix = TYPE) => `${prefix}-${id++}`;
// find the shared config for all py-script elements
let config;
let pyConfig = $("py-config");
if (pyConfig) config = pyConfig.getAttribute("src") || pyConfig.textContent;
else {
pyConfig = $('script[type="py"]');
config = pyConfig?.getAttribute("config");
}
if (/^https?:\/\//.test(config)) config = await fetch(config).then(getText);
// generic helper to disambiguate between custom element and script // generic helper to disambiguate between custom element and script
const isScript = ({ tagName }) => tagName === "SCRIPT"; const isScript = ({ tagName }) => tagName === "SCRIPT";
@@ -70,7 +57,7 @@ const fetchSource = async (tag, io, asText) => {
if (asText) return dedent(tag.textContent); if (asText) return dedent(tag.textContent);
console.warn( console.warn(
'Deprecated: use <script type="py"> for an always safe content parsing:\n', `Deprecated: use <script type="${TYPE}"> for an always safe content parsing:\n`,
tag.innerHTML, tag.innerHTML,
); );
@@ -140,14 +127,10 @@ const workerHooks = {
[...hooks.codeAfterRunWorkerAsync].map(dedent).join("\n"), [...hooks.codeAfterRunWorkerAsync].map(dedent).join("\n"),
}; };
// avoid running further script if the previous one had
// some import that would inevitably delay its execution
let queuePlugins;
// define the module as both `<script type="py">` and `<py-script>` // define the module as both `<script type="py">` and `<py-script>`
define("py", { define(TYPE, {
config, config,
env: "py-script", env: `${TYPE}-script`,
interpreter: "pyodide", interpreter: "pyodide",
...workerHooks, ...workerHooks,
onWorkerReady(_, xworker) { onWorkerReady(_, xworker) {
@@ -173,21 +156,8 @@ define("py", {
registerModule(pyodide); registerModule(pyodide);
} }
// load plugins unless specified otherwise // ensure plugins are bootstrapped already
const toBeAwaited = []; if (plugins) await plugins;
for (const [key, value] of entries(plugins)) {
if (!pyodide.config?.plugins?.includes(`!${key}`))
toBeAwaited.push(value());
}
// this grants queued results when first script/tag has plugins
// and the second one *might* rely on first tag execution
if (toBeAwaited.length) {
const all = Promise.all(toBeAwaited);
queuePlugins = queuePlugins ? queuePlugins.then(() => all) : all;
}
if (queuePlugins) await queuePlugins;
// allows plugins to do whatever they want with the element // allows plugins to do whatever they want with the element
// before regular stuff happens in here // before regular stuff happens in here
@@ -215,7 +185,7 @@ define("py", {
defineProperty(element, "target", { value: show }); defineProperty(element, "target", { value: show });
// notify before the code runs // notify before the code runs
dispatch(element, "py"); dispatch(element, TYPE);
pyodide[`run${isAsync ? "Async" : ""}`]( pyodide[`run${isAsync ? "Async" : ""}`](
await fetchSource(element, pyodide.io, true), await fetchSource(element, pyodide.io, true),
); );
@@ -249,7 +219,7 @@ class PyScriptElement extends HTMLElement {
this.srcCode = await fetchSource(this, io, !this.childElementCount); this.srcCode = await fetchSource(this, io, !this.childElementCount);
this.replaceChildren(); this.replaceChildren();
// notify before the code runs // notify before the code runs
dispatch(this, "py"); dispatch(this, TYPE);
runner(this.srcCode); runner(this.srcCode);
this.style.display = "block"; this.style.display = "block";
} }

View File

@@ -1,4 +1,7 @@
import { FetchError, ErrorCode } from "./exceptions.js"; import { FetchError, ErrorCode } from "./exceptions.js";
import { getText } from "../node_modules/polyscript/esm/fetch-utils.js";
export { getText };
/** /**
* This is a fetch wrapper that handles any non 200 responses and throws a * This is a fetch wrapper that handles any non 200 responses and throws a

View File

@@ -1,4 +1,4 @@
// ⚠️ This file is an artifact: DO NOT MODIFY // ⚠️ This file is an artifact: DO NOT MODIFY
export default { export default {
error: () => import("./plugins/error.js"), error: () => import(/* webpackIgnore: true */ "./plugins/error.js"),
}; };

2
pyscript.core/types/config.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
export let config: any;
export let plugins: any;

View File

@@ -21,6 +21,5 @@ export namespace hooks {
let codeAfterRunWorker: Set<string>; let codeAfterRunWorker: Set<string>;
let codeAfterRunWorkerAsync: Set<string>; let codeAfterRunWorkerAsync: Set<string>;
} }
declare let config: any; import { config } from "./config.js";
import sync from "./sync.js"; import sync from "./sync.js";
export {};

View File

@@ -8,3 +8,4 @@
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export function robustFetch(url: string, options?: Request): Promise<Response>; export function robustFetch(url: string, options?: Request): Promise<Response>;
export { getText };