brython-dev / brython

Brython (Browser Python) is an implementation of Python 3 running in the browser
BSD 3-Clause "New" or "Revised" License
6.31k stars 513 forks source link

[Project] Produce standalone ES6 modules. #2292

Open denis-migdal opened 8 months ago

denis-migdal commented 8 months ago

Goals

Step 1: Produce semi-standalone JS

// at the start of the generated JS
const $B = __BRYTHON__
const declareModule = await $B.waitMyTurn();

// do stuff here

// at the end of the file.
declareModule(some_args);
// at the start of brython.js
const $B = {}
globalThis.__BRYTHON__ = $B; // put __BRYTHON__ into the global scope

const waitingList = [];
$B.waitMyTurn = function() { // no need for async as it returns a Promise

  let start;
  let resolver;

  let waitMyTurn = new Promise( (r,err) => { start    = r });
  let declareMod = new Promise( (r,err) => { resolver = r });

  waitingList.push({start, resolver, declareMod});

  return waitMyTurn;   
}

// when Brython is ready :
for(let i = 0; i < waitingList.length; ++i) {
    let module = waitingList[i];
  module.start(module.resolver); // waitMyTurn is resolved.
  let some_args = await module.declareMod;
  // add the module to the list of Brython intialized modules.
}

Step 2 : Async imports

Step 3: Beta feature: ES6 module format

Step 4: Compatibility with Web Dev tool with 0 adaptation.

Step 5: Fully standalone ES6 modules

@PierreQuentel What do you think ?

PierreQuentel commented 8 months ago

I don't have yet an opinion on all these topics, but marking non-module-level imports as incorrect is not possible, because the Python language supports them and by design Brython is as compliant as possible with Python.

In the Brython standard library only, these scripts have such imports:

['_codecs.py', '_dummy_thread.py', '_frozen_importlib.py', '_pydatetime.py', '_pydecimal.py', '_socket.py', '_sre.py', '_thread.py', '_typing.py', 'argparse.py', 'ast.py', 'base64.py', 'bdb.py', 'calendar.py', 'cmd.py', 'code.py', 'codecs.py', 'collections\init.py', 'concurrent\futures\process.py', 'copy.py', 'copyreg.py', 'difflib.py', 'doctest.py', 'email\utils.py', 'encodings\init.py', 'encodings\rot_13.py', 'enum.py', 'functools.py', 'getopt.py', 'getpass.py', 'gettext.py', 'gzip.py', 'heapq.py', 'hmac.py', 'http\client.py', 'imp.py', 'importlib\init.py', 'importlib\_bootstrap.py', 'importlib\_bootstrap_external.py', 'importlib\abc.py', 'inspect.py', 'ipaddress.py', 'json\init.py', 'locale.py', 'logging\init.py', 'logging\config.py', 'logging\handlers.py', 'mimetypes.py', 'multiprocessing\util.py', 'nntplib.py', 'ntpath.py', 'os.py', 'pathlib.py', 'pdb.py', 'pickle.py', 'pkgutil.py', 'posixpath.py', 'profile.py', 'py_compile.py', 'pydoc.py', 'quopri.py', 're1.py', 'select.py', 'shutil.py', 'site-packages\simpleaio\helpers.py', 'site.py', 'socket.py', 'subprocess.py', 'symtable.py', 'sysconfig.py', 'tabnanny.py', 'tarfile.py', 'threading.py', 'time.py', 'timeit.py', 'tokenize.py', 'traceback.py', 'types.py', 'typing.py', 'unittest\init.py', 'unittest\main.py', 'unittest\loader.py', 'unittest\mock.py', 'urllib\parse.py', 'uu.py', 'uuid.py', 'warnings.py', 'weakref.py', 'zipfile.py']

denis-migdal commented 8 months ago

I don't have yet an opinion on all these topics,

I can isolate some "sub-steps", that can still be interesting to do regardless of the whole global scheme.

but marking non-module-level imports as incorrect is not possible, because the Python language supports them and by design Brython is as compliant as possible with Python.

It is not exactly marking them as "incorrect", but notifying users that it might not be supported in the future due to the evolution of Web standards, independently of Python/Brython.

But yeah I understand your point.

denis-migdal commented 8 months ago

Okay, some stuff that can be interesting regardless :

  1. Being able to test the current dev version in the Editor (either by duplicating the page, or through a <select> to choose a Brython version).
  2. Enabling to import ES6 JS files through normal Python import instead of having to use javascript.import_modules() (?).
  3. Having a brython-cli build $SRC [$DST] command generating a js file from a python source file (1 input file = 1 output file). a. With this command enabling to select an output format through an option : -> JS files, like the result of __BRYTHON__.pythonToJS() or javascript.py2js() (enables easy AOT in Brython). -> Python source code inside JS files, like the format of what brython-cli make_dist is producing (aka the Brython module format), but without dependencies resolutions. b. Adding a --watch option to this brython-cli build command. c. Enabling this command to take a directory as a source (then all elements in the directory would recursively be processed).

This would enable some level of integration with current WebDev tools... Well, won't be as performant as an ES6 output for tree shaking, but still, can be an easy step to be able to use some nice features (e.g. advanced Bundling).

Modules would then be loaded in Brython thanks to javascript.load() or javascript.import_js() I think. I also noticed that in JS we can import Brython module thanks to getPythonModule().

Still, could be nice to be able to just include them as <script type="text/javascript>. Then it'd require some work on brython.js so that it'd be immediately "ready" once the file is synchronously executed (would also solves some timing issues when working with JS). I'd suggest to have at least a __BRYTHON__.whenReady() returning a promise that'd be resolved once Brython is ready. This function could then be added at the start of all generated JS (the top-level function could be marked as async to allow top level await ?).

Could be the firsts steps in this project, then the only remaining tasks would be :

@PierreQuentel What do you think now ?

EDIT: To enable better bundling and module dependencies resolution during build time :

  1. Replace the (function(){})() by :
    
    let singleton = null;

function X(){ // the original (function(){}) here: // ... }

export default function FOO() { if( singleton !== null) return singleton return singleton = X(); }

if( BRYTHON.__IN_IMPORT__ !== true ) // if included in a Githubissues.

  • Githubissues is a development platform for aggregating issues.