-
Notifications
You must be signed in to change notification settings - Fork 509
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEATURE] Suggestions to improve imports #2276
Comments
I'm afraid option A is not possible, because imports are synchronous in Python. They are implemented in Brython with blocking Ajax calls - those have been marked as deprecated for ages, but fortunately are still working. If they are removed some day, I will be in trouble... For option B (or A bis) :
I am reluctant to adding another feature to optimize imports, which would be less efficient than these bundles anyway (both eliminate the need for Ajax calls). More on the other items later. Merci ! |
For "standard" But indeed for some import functions of With WebWorker, it seems we can pause them... the issue is that we can't put Brython in a WebWorker as it doesn't have DOM access... That'd need an active wait it seems... For the A. that should indeed be B. I'll answer later too. |
Okay, found a game changing idea for I have to finish some work, then I'll explain it here ;) |
Here my proposal : Introduce 2 new brython-cli build [OPTIONS...] # execute all brython-cli commands required for your application
brython-cli watch [OPTIONS...] # like brython-cli build, but in watch mode. Watch mode is a very useful thing for large project and is often used in web development tools. It runs in the background, listening to the file changes (e.g. with The first option would be This configuration could be, in development mode, be included in an HTML Tag, either within as a JSON, or with a The file would be a JSON with the following structure:
Ofc, this would need a tutorial to use them, both in development and production mode.
Now remains the issue about import, I'll answer that in my next message. |
Okay, so my idea to handle imports... There are 4 types of imports :
If we could produce ES6 module from Brython files :
So the real issue is for sync dynamics imports, but that should not be quite frequent and would be bad practices. Though we'd offers way to prefetch it to somehow support it. Else, for
Brython then would start executing Brython code found in the page, in an async ways (after |
@PierreQuentel JS has a new live-saving feature that'd save your life :) A life saving future JS feature that'll solve all our issues : Top Level Await - which seems already implemented. AND IT WORKS ! <script type="module">
async function foo() {
return 4
}
await foo();
console.log('ok')
</script> This won't help solving sync dynamic import in sync bloc code, but, hey, that's quite good enough ^^. Another interesting feature worth looking at it : import maps. |
It is quite simple in theory, but problematic to implement in practice. If the module has The strategy you suggest is used by |
I have read about this feature, but I have not found how to take advantage of it to implement If we have this Brython script import some_module
print(some_module.x) where |
If we handle it as a standalone file i.e. not evaluated by brython.js, i.e. pre-converted and saved to a file. // To potentially wait Brython to be ready :
await ___BRYTHON__.whenReady();
// or
await new Promise( (resolve, error) => {
document.addEventListener('BRYTHON_READY', resolve );
});
// or
import * as $B from 'brython.js'; // I think it requires <script type="module"/> or to be imported by another script ?
// or
const $B = await import('brython.js');
let some_module = await __BRYTHON__.fetchModule("some_module") // if "some_module" is Python module (ofc)
// if "from some_module import ...."
let {....} = await __BRYTHON__.fetchModule("some_module");
// do your print and other stuff here.
function foo() {}
// if you want it to handle ES6 export (which could be a good thing) ?
// but not authorized if not imported by a script, or not in <script type=module> ?
export foo; With, in brython.js : // returns null if not found.
async fetchModuleFromIndexDB(name) {
// do the stuff here with lot of awaits.
}
async downloadModule(name) {
let response = await fetch(....);
while(response.status === 404) { // ?
// try another one
response = await fetch(....);
}
// OR for parallel candidate search :
let candidates = [.........].
let answers = await Promise.all(candidates.map( c => fetch(c) ) );
response = .... // the first good answer.
return await response.getText
}
async fetchModule(name) {
if( name in __BRYTHON__.imported )
return __BRYTHON__.imported[name];
let module = await fetchModuleFromIndexDB(name);
if( module === null ) {
let str = await downloadModule('name');
// do the JS conversion
// eval the JS
// put it in indexDB
// put it in __BRYTHON__.imported ?
return {} // an object containing all top-level symbol of the file (functions, variables, etc.)
}
}
window.__BRYTHON__.fetchModule = fetchModule;
// or using ES6 export
export fetchModule I did it from memory, this is more pseudo-code, but the idea is here. |
You could have, when building the package, all dependencies saved at the top of the package ? But with top-level import, that is less an issue I guess (I'll have to develop on that).
Humm... so you'll need like a blacklist ? Another solution, is converting it to ES6 JS files, and let dedicated tool do the tree cutting (e.g. Webpack) ? Why the fuck is
I'll think more about it, with the async in |
Okay, in |
For dynamic synchronous import that are not at top level... just require them to be preloaded by the user (the ones he needs), or ask them to use async ? Maybe enabling it to define a blacklist for some ? Or a white list for some sync dependencies that won't be prefetched ? |
var script_tag = document.createElement('script');
script_tag.type = 'module';
script_tag.text = 'console.log("ok"); async function foo(){ console.log("ddd") }; await foo();'
//script_tag.text = 'alert("hello world")';
document.body.appendChild(script_tag);
console.log('loaded');
Have to check if order is still good if we do an operation like fetch... |
Okay, I'm sad. <script type="text/javascript">
var script_tag = document.createElement('script');
script_tag.type = 'module';
script_tag.text = 'console.log("ok"); async function foo(){ let r = await fetch("toto"); console.log(r.status) }; await foo();'
//script_tag.text = 'alert("hello world")';
document.body.appendChild(script_tag);
console.log('loaded');
</script>
But still, using a script tag instead of And would likely enables to use ES6 module import/export in the produced JS code. EDIT: with an import or a src attribute, "loaded" is printed first... |
One solution :
Not sure if there are other ways around non-top level sync import... |
I'll develop on that. 1. It enables to copy/paste from the Editor.Imagine, at execution time, Brython putting all generated JS code into an ES6-compatible module So e.g. copy-paste the JavaScript generated from the Editor to a file and use it directly, e.g. to test the code in JSPerf, in its own project, with some profiling tools, etc. 2. It enables better JS <=> Brython interactionsYou could import JS ES6 modules in Brython files, and Brython modules in ES6 modules. If it isn't possible, maybe would ask Brython is the module exists, then try to load them in the ES6 way, and lastly do the Brython path search ? EDIT: it is not possible. Then doing, if generated during runtime and ... not known by Brython : let x;
try {
x = await import( '....' ) // try fetching ES6 module
} catch() {
x = await __BRYTHON__.fetchModule(...) // else try other paths.
}
let {......} = x; If generated during runtime or building time, and ... known by Brython as a JS ES6 module file : // + wait for brython to be ready if generated at building time...
import {....} from '.....'; If generated during runtime or building time, and ... known by Brython as a .py module file that will not be converted to an ES6 module : // + wait for brython to be ready if generated at building time...
let {....} = await __BRYTHON__.fetchModule(...); If generated during runtime and ... known by Brython as an already loaded Brython .py file : x = await __BRYTHON__.getModule(...) 3. Without doing anything, it solves the issue of big dependencies (in number of files or size).This means that for big dependencies like When debugging a 4. It enables to use dedicated WebDev tools to generate bundles, do tree shaking, etc.Currently, you can create packages with But it'll becomes harder for a project that'd have many files, with potentially many dependencies. Thus requiring to add features and to maintain this tool. Which is a lot of work. If, when building the production version of your website, your generated script use ES6 imports to import its JS dependencies (likely other generated scripts), you would then use any bundler you like e.g. Parcel, WebPack, etc. Tools that have lot of features, lot of possible configurations, plugins, etc. that you can't expect Therefore, you'll be able to rely on existing WebDev tool, and integrate with them. Moreover if |
I have to admit I am a little lost with all these suggestions ;-) I did a quick try with dynamic insertion of a script of type "module" with top-level awaits, and it actually worked. Making it a replacement for imports is not an easy task, and as you noticed it wouldn't work in Web Workers anyway. I keep it in mind in case blocking Ajax calls are removed some day... The chain that leads from For the other suggestions, could you try to implement one or several ones in branches and submit Pull Requests ? I realize it can be sometimes hard to understand how the current code works and I can help for that. |
I'll try to make a summary of this issue someday. So yeah, I understand that this isn't clear.
It seems that you can declare them like that : Cf https://stackoverflow.com/questions/44118600/web-workers-how-to-import-modules I think top level await import would then be possible inside Workers if they are declared as a module. For Web Workers, you also have
Mmm.... Python is really doing dirty things...
I am not familiar enough with the AST. I can write some mocks on my free time, but touching at a core part of Brython would be a little too hard. Currently, I have 19 opened issues which is a little too much at the same time. First project could be about ES6 module generation for Brython, aiming for better integration with WebDev tools / JS ES6 modules ? I'd then suggest some steps for an alternative code generation (e.g. through an option in |
Web workers is just one of the issues if imports are implemented as If there is an import inside a function, the function must be made asynchronous; the programs that call this function must |
I have a solution for that. I'll be taking my time into writing a document to explain everything steps by steps, while verifying 2-3 things. I'll post a new issue with a |
See #2292 |
Closing this issue in favor of #2292. |
Hi,
Here some suggestions to improve imports.
A. When searching for a module, search for all directories at once
To find a module, Python search them in different directories one by one.
This isn't an issue on Desktop, but on a website, it means sending a query, waiting for an answer, if not found sending another query, etc.
A most efficient way could be to send several async queries to search for the module in different dir at the same time.
B. Allows to define module path aliases
One even more efficient ways would be to define aliases to precise where to find a given module path, e.g. something like :
The behavior would then be :
- e.g. "import brython.browser.console" => "/Brython-3.19/Lib/browser/console.py"
- e.g. "import myapp.browser.console" => "/src/brython/browser.console/index.py"
This would have the side-effect of allowing the generate kind of "maps", listing all modules and their path, we could give to Brython for an efficient look up. Such aliases could also be "scoped" in Brython module by the "init.py" files ?
i.e. they could define their own aliases to apply inside their files (and not outside).
Such paths could be given either by an HTML tag or by a file.
C. Improve support of JS import
Could be nice to be able to import ES6 module like we import Brython modules, i.e. with
from js.myapp import foo
This could be made possible thanks to the previous suggestion, simply by adding an aliases resolving a module to a .js file.
There are 5 types of JS scripts :
load()
function, and that we shouldn't import.<script>
tag has anid=
attribute, to add them as a module. But I'm not sure how it could be made.(function($B){})(__BRYTHON)
) or is this something like 1. we'd use withload()
?I'll advise the following behavior:
let module = await import(path)
to import the module.D. Exporting Brython modules as an ES6 module
Of course, trying to import a Brython module inside a JS ES6 module wouldn't work as JS modules have no idea on how to handle Brython files.
But, we could have a
brython-es6.js
file, to which we could give in its GET parameters brython module e.g.brython-es6.js?module=XXXX
that would do :The text was updated successfully, but these errors were encountered: