Repository: pyscript/pyscript
File: pyscriptjs/src/main.ts (TypeScript)

Code before:
        console.log(`importing ${modulename}`);
        // TODO: This is very specific to Pyodide API and will not work for other interpreters,
        //       when we add support for other interpreters we will need to move this to the
        //       interpreter API level and allow each one to implement it in its own way
        const module = interpreter._remote.interface.pyimport(modulename);
        if (typeof module.plugin !== 'undefined') {
            const py_plugin = module.plugin as PyProxy & { init(app: PyScriptApp): void };
            py_plugin.init(this);
            this.plugins.addPythonPlugin(py_plugin);
        } else {
            logger.error(`Cannot find plugin on Python module ${modulename}! Python plugins \
modules must contain a "plugin" attribute. For more information check the plugins documentation.`);
        }
    }

    /**
     * Fetch url from src attribute of py-repl tags and
     * preload the code from fetch response into the Corresponding py-repl tag,
     * but please note that they will not be pre-run unless you click the runbotton.
     */
    async loadReplSrc() {
        logger.info('loading code into the repl');
        const pyrepl_tags = document.getElementsByTagName('py-repl');
        const loadError = [];
        await Promise.all(
            Array.from(pyrepl_tags).map(pyrepl_tag => {
                const pyrepl_src = pyrepl_tag.getAttribute('src');
                return fetch(pyrepl_src)
                    .then(response => (response.ok ? response.text() : undefined))
                    .then(content => {
                        const code = (content + '').replace(/[<&]/g, match => (match === '<' ? '&lt;' : '&amp;'));
                        content ? (pyrepl_tag.innerHTML = code) : loadError.push(pyrepl_src);
                    });
            }),
        )
            .then(() => {
                if (loadError.length != 0) {
                    logger.error(
                        'Flied to fetch and load the code from',
                        loadError.toString(),
                        'to repl. Are your filename and path correct?',
                    );
                }
            })
            .then(() => logger.info('load code into the repl completely'));
    }

    // lifecycle (7)
    executeScripts(interpreter: InterpreterClient) {
        // make_PyScript takes an interpreter and a PyScriptApp as arguments
        this.PyScript = make_PyScript(interpreter, this);

Review comment (suggestion):
FYI `Array.from` [already accepts a callback](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#parameters) as second argument so that while creating the array you get to work with its data ... here it's twice the loop so it can be `Array.from(pyrepl_tags, pyrepl_tag => { ... })` instead.

Code after:
     * export in the module (the plugin class) and add it to the plugins
     * list with `new importedPlugin()`.
     *
     * @param filePath - URL of the javascript file to fetch.
     */
    async fetchJSPlugin(filePath: string) {
        const pluginBlob = await (await robustFetch(filePath)).blob();
        const blobFile = new File([pluginBlob], 'plugin.js', { type: 'text/javascript' });
        const fileUrl = URL.createObjectURL(blobFile);

        const module = (await import(fileUrl)) as { default: { new (): Plugin } };
        // Note: We have to put module.default in a variable
        // because we have seen weird behaviour when doing
        // new module.default() directly.
        const importedPlugin = module.default;

        // If the imported plugin doesn't have a default export
        // it will be undefined, so we throw a user error, so
        // an alter banner will be created.
        if (importedPlugin === undefined) {
            throw new UserError(
                ErrorCode.NO_DEFAULT_EXPORT,
                `Unable to load plugin from '${filePath}'. ` + `Plugins need to contain a default export.`,
            );
        } else {
            this.plugins.add(new importedPlugin());
        }
    }

    /**
     * Fetch python plugins from a filePath, saves it on the FS and import
     * it as a module, executing any plugin define the module scope.
     *
     * @param interpreter - the interpreter that will execute the plugins
     * @param filePath - path to the python file to fetch
     */
    async fetchPythonPlugin(interpreter: InterpreterClient, filePath: string) {
        const pathArr = filePath.split('/');
        const filename = pathArr.pop();
        // TODO: Would be probably be better to store plugins somewhere like /plugins/python/ or similar
        await interpreter._remote.loadFileFromURL(filename, filePath);

        //refresh module cache before trying to import module files into interpreter
        await interpreter._remote.invalidate_module_path_cache();

        const modulename = filePath.replace(/^.*[\\/]/, '').replace('.py', '');

        console.log(`importing ${modulename}`);
        // TODO: This is very specific to Pyodide API and will not work for other interpreters,
        //       when we add support for other interpreters we will need to move this to the
        //       interpreter API level and allow each one to implement it in its own way