mirror of
https://github.com/pyscript/pyscript.git
synced 2026-02-18 13:00:39 -05:00
Refactor py-config and the general initialization logic of the page (#806)
This PR is the first step to improve and rationalize the life-cycle of a pyscript app along the lines of what I described in #763 . It is not a complete solution, more PRs will follow. Highlights: - py-config is no longer a web component: the old code relied on PyConfig.connectedCallback to do some logic, but then if no <py-config> tag was present, we had to introduce a dummy one with the sole goal of activating the callback. Now the logic is much more linear. - the new pyconfig.ts only contains the code which is needed to parse the config; I also moved some relevant code from utils.ts because it didn't really belong to it - the old PyConfig class did much more than dealing with the config: in particular, it contained the code to initialize the env and the runtime. Now this logic has been moved directly into main.ts, inside the new PyScriptApp class. I plan to refactor the initialization code in further PRs - the current code relies too much on global state and global variables, they are everywhere. This PR is a first step to solve the problem by introducing a PyScriptApp class, which will hold all the mutable state of the page. Currently only config is stored there, but eventually I will migrate more state to it, until we will have only one global singleton, globalApp - thanks to what I described above, I could kill the appConfig svelte store: one less store to kill :).
This commit is contained in:
@@ -1,130 +0,0 @@
|
||||
import { BaseEvalElement } from './base';
|
||||
import { appConfig, addInitializer, runtimeLoaded } from '../stores';
|
||||
import type { AppConfig, Runtime } from '../runtime';
|
||||
import { version } from '../runtime';
|
||||
import { PyodideRuntime } from '../pyodide';
|
||||
import { getLogger } from '../logger';
|
||||
import { readTextFromPath, handleFetchError, mergeConfig, validateConfig, defaultConfig } from '../utils'
|
||||
|
||||
// Subscriber used to connect to the first available runtime (can be pyodide or others)
|
||||
let runtimeSpec: Runtime;
|
||||
runtimeLoaded.subscribe(value => {
|
||||
runtimeSpec = value;
|
||||
});
|
||||
|
||||
let appConfig_: AppConfig;
|
||||
appConfig.subscribe(value => {
|
||||
appConfig_ = value;
|
||||
});
|
||||
|
||||
const logger = getLogger('py-config');
|
||||
|
||||
/**
|
||||
* Configures general metadata about the PyScript application such
|
||||
* as a list of runtimes, name, version, closing the loader
|
||||
* automatically, etc.
|
||||
*
|
||||
* Also initializes the different runtimes passed. If no runtime is passed,
|
||||
* the default runtime based on Pyodide is used.
|
||||
*/
|
||||
|
||||
export class PyConfig extends BaseEvalElement {
|
||||
widths: Array<string>;
|
||||
label: string;
|
||||
mount_name: string;
|
||||
details: HTMLElement;
|
||||
operation: HTMLElement;
|
||||
values: AppConfig;
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
extractFromSrc(configType: string) {
|
||||
if (this.hasAttribute('src'))
|
||||
{
|
||||
logger.info('config set from src attribute');
|
||||
return validateConfig(readTextFromPath(this.getAttribute('src')), configType);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
extractFromInline(configType: string) {
|
||||
if (this.innerHTML!=='')
|
||||
{
|
||||
this.code = this.innerHTML;
|
||||
this.innerHTML = '';
|
||||
logger.info('config set from inline');
|
||||
return validateConfig(this.code, configType);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
injectMetadata() {
|
||||
this.values.pyscript = {
|
||||
"version": version,
|
||||
"time": new Date().toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
const configType: string = this.hasAttribute("type") ? this.getAttribute("type") : "toml";
|
||||
let srcConfig = this.extractFromSrc(configType);
|
||||
const inlineConfig = this.extractFromInline(configType);
|
||||
// first make config from src whole if it is partial
|
||||
srcConfig = mergeConfig(srcConfig, defaultConfig);
|
||||
// then merge inline config and config from src
|
||||
this.values = mergeConfig(inlineConfig, srcConfig);
|
||||
this.injectMetadata();
|
||||
|
||||
appConfig.set(this.values);
|
||||
logger.info('config set:', this.values);
|
||||
|
||||
addInitializer(this.loadPackages);
|
||||
addInitializer(this.loadPaths);
|
||||
this.loadRuntimes();
|
||||
}
|
||||
|
||||
log(msg: string) {
|
||||
const newLog = document.createElement('p');
|
||||
newLog.innerText = msg;
|
||||
this.details.appendChild(newLog);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.remove();
|
||||
}
|
||||
|
||||
loadPackages = async () => {
|
||||
const env = appConfig_.packages;
|
||||
logger.info("Loading env: ", env);
|
||||
await runtimeSpec.installPackage(env);
|
||||
}
|
||||
|
||||
loadPaths = async () => {
|
||||
const paths = appConfig_.paths;
|
||||
logger.info("Paths to load: ", paths)
|
||||
for (const singleFile of paths) {
|
||||
logger.info(` loading path: ${singleFile}`);
|
||||
try {
|
||||
await runtimeSpec.loadFromFile(singleFile);
|
||||
} catch (e) {
|
||||
//Should we still export full error contents to console?
|
||||
handleFetchError(<Error>e, singleFile);
|
||||
}
|
||||
}
|
||||
logger.info("All paths loaded");
|
||||
}
|
||||
|
||||
loadRuntimes() {
|
||||
logger.info('Initializing runtimes');
|
||||
for (const runtime of this.values.runtimes) {
|
||||
const runtimeObj: Runtime = new PyodideRuntime(runtime.src, runtime.name, runtime.lang);
|
||||
const script = document.createElement('script'); // create a script DOM node
|
||||
script.src = runtimeObj.src; // set its src to the provided URL
|
||||
script.addEventListener('load', () => {
|
||||
void runtimeObj.initialize();
|
||||
});
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user