| 73 | |
| 74 | cache.set(file, new Proxy(create(null), { |
| 75 | get(_, field) { |
| 76 | if (!exports.has(field)) { |
| 77 | // create an async callback once and always return the same later on |
| 78 | exports.set(field, async (...args) => { |
| 79 | // the third cache is about reaching lazily the code only once |
| 80 | // augmenting its content with exports once and drop it on done |
| 81 | if (!python) { |
| 82 | // do not await or multiple calls will fetch multiple times |
| 83 | // just assign the fetch `Promise` once and return it |
| 84 | python = fetch(file).then(async response => { |
| 85 | const code = await response.text(); |
| 86 | // create a unique identifier for the Python context |
| 87 | const identifier = pathname.replace(/[^a-zA-Z0-9_]/g, ''); |
| 88 | const name = `__pyscript_${identifier}${Date.now()}`; |
| 89 | // create a Python dictionary with all accessed fields |
| 90 | const detail = `{"detail":${dictionary(exports.keys())}}`; |
| 91 | // create the arguments for the `dispatchEvent` call |
| 92 | const eventArgs = `${stringify(name)},${name}to_ts(${detail})`; |
| 93 | // bootstrap the script element type and its attributes |
| 94 | const script = el('script', { type, textContent: [ |
| 95 | '\n', code, '\n', |
| 96 | // this is to avoid local scope name clashing |
| 97 | `from pyscript import window as ${name}`, |
| 98 | `from pyscript.ffi import to_js as ${name}to_ts`, |
| 99 | `${name}.dispatchEvent(${name}.CustomEvent.new(${eventArgs}))`, |
| 100 | // remove these references even if non-clashing to keep |
| 101 | // the local scope clean from undesired entries |
| 102 | `del ${name}`, |
| 103 | `del ${name}to_ts`, |
| 104 | ].join('\n') }); |
| 105 | |
| 106 | // if config is provided it needs to be a worker to avoid |
| 107 | // conflicting with main config on the main thread (just like always) |
| 108 | script.toggleAttribute('worker', !!config || !!worker); |
| 109 | if (config) { |
| 110 | const attribute = await normalize(config, file); |
| 111 | script.setAttribute('config', attribute); |
| 112 | } |
| 113 | |
| 114 | if (env) script.setAttribute('env', env); |
| 115 | if (serviceWorker) script.setAttribute('service-worker', serviceWorker); |
| 116 | |
| 117 | // let PyScript resolve and execute this script |
| 118 | document.body.appendChild(script); |
| 119 | |
| 120 | // intercept once the unique event identifier with all exports |
| 121 | globalThis.addEventListener( |
| 122 | name, |
| 123 | event => { |
| 124 | resolve(event.detail); |
| 125 | script.remove(); |
| 126 | }, |
| 127 | { once: true } |
| 128 | ); |
| 129 | |
| 130 | // return a promise that will resolve only once the event |
| 131 | // has been emitted and the interpreter evaluated the code |
| 132 | const { promise, resolve } = Promise.withResolvers(); |