How to get Pyodide to work in a React component? - javascript

I am trying to make my own Python fundamentals tutorial that's similar to the interactive editor that codecademy.com has. However, I'm struggling to make Pyodide work with React.
Using the very basic getting started example, I can make the following work in index.html of my create-react-app app:
<!-- PYODIDE -->
<script src="https://cdn.jsdelivr.net/pyodide/dev/full/pyodide.js"></script>
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="text/javascript">
async function main() {
let pyodide = await loadPyodide();
console.log(pyodide.runPython(`
import sys
sys.version
`));
pyodide.runPython("print(1 + 2)");
}
main();
</script>
.....
</body>
However, when I try to move the same code into a separate component, it says 'loadPyodide' is not defined
import React from 'react'
const Task1 = ({ replNum, task }) => {
async function main() {
let pyodide = await loadPyodide();
console.log(pyodide.runPython(`
import sys
sys.version
`));
pyodide.runPython("print(1 + 2)");
}
main();
return (
SOME CODE
)
Why is this?

Try using window.loadPyodide instead of loadPyodide. React uses modules where 'use strict' is set, so undeclared global variables are not allowed.

Related

How to tell babel loaded as CDN script to use ES6 modules?

I am testing a React environment without a build process by loading React and Babel scripts directly from CDN.
When I try to add ES6 modules I run into a problem: babel converts import back down to require, which is not working in the browser.
Since there is no babel config file how can I tell babel not to do this? In other words, how can I load React with script tags from CDN and still use ES6 modules?
HTML
<head>
<script src="https://unpkg.com/react#18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<!-- babel for JSX and datatype to use modules -->
<script defer type="text/babel" data-type="module" src="main.js"></script>
</head>
<body>
<div id="main"></div>
</body>
MAIN.JS (works except for the import)
import { Drawing } from "./drawing"
const App = () => {
return <div>
<h1>Just a test</h1>
<Drawing />
</div>
}
const root = ReactDOM.createRoot(document.querySelector('#main'))
root.render(<App />)
PS I realise this setup is not optimised for production, but it is ideal for quickly sketching out ideas (no bundle process, no node modules folder with 30.000 packages).
Babel standalone added support for browser-native modules in v7.10.0 (including the data-type="module" attribute), while you're using v6. Removing the version number in the CDN or defining a specific one >= 7.10.0 fixes the transform-to-require issue.
However, you have two other issues.
The first one is trivial: ES6 native modules don't automatically resolve the file extension, so you need to do import { Drawing } from "./drawing.js" instead of import { Drawing } from "./drawing".
Secondly though, Babel won't transform the files that you import, so that JSX would be considered invalid. This leaves you with two options.
Create your own import function, and manually import like this (working example)
async function imp(path, requestOrigin) {
const { href } = new URL(path,
requestOrigin
? requestOrigin.slice(0, requestOrigin.lastIndexOf("/"))
: location.href
); // so the paths won't be messed up
const res = await fetch(href);
const text = await res.text();
const { code } = Babel.transform(text, { presets: ["react"] });
return await import(`data:application/javascript,${encodeURIComponent(code)}`);
}
Usage:
const { Drawing } = await imp("./drawing.js");
// if you're in a "nested" route, e.g. "/components/Button.js", you need to provide the file
const { OtherComponent } = await imp("./hello.js", import.meta.url);
okay, okay, WHY though wouldn't you use a bundler?? This is how the world works and always will (for the forseeable furture, i.e.)... I mean, just install parcel globally and you're like, done in two seconds with just a single command

Problem using import/export in Javascript "Uncaught SyntaxError: Cannot use import statement outside a module"

I'm learning Javascript and I have a problem using import/export to modularize my code. I tried to learn this by reading this and this in MDN Web Docs (Mozilla), but failed. I know this has already been asked here, but I couldn't fix the problem.
The error I get in the web browser terminal is: Uncaught SyntaxError: Cannot use import statement outside a module
I'll insert a small example of how I tried to use import/export:
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
</head>
<body>
<h1 class="title">Example</h1>
<button class="title-button">Change title</button>
<!--
I tried adding this line but nothing has changed:
<script type="module" src="module.js"></script>
--->
<script src='main.js'></script>
</body>
</html>
module.js:
const changeTitle = newTitle => {
const title = document.querySelector(".title");
title.innerHTML = newTitle;
}
export { changeTitle };
main.js:
import { changeTitle } from "./module";
const titleButton = document.querySelector(".title-button");
titleButton.addEventListener("click", () => {
let newTitle = prompt("Enter new title:");
changeTitle(newTitle);
});
Note: all three files are in the same folder.
Thank you for your time. Sorry if I made a mistake in this post.
The type="module" attribute must be added in the script that uses the import statement (in this case, main.js). My mistake was trying to add type="module" in module.js instead of main.js

How do I import modules in a project using pyodide without errors?

Whenever I import python modules in a pyodide, it gives this error.
pyodide.js:108 Invalid package name or URI
I am not sure how to properly import modules,
I have tried this which was mentioned in the docs.
pyodide.loadPackage('<module address>')
(this returns a promise on whoes resolution I run this method)
pyodide.runPython('
<python code here>
')
Upon execution, I get the error mentioned above.
Javascript Code:
<html>
<head>
<script type="text/javascript">
// set the pyodide files URL (packages.json, pyodide.asm.data etc)
window.languagePluginUrl = 'https://pyodide-cdn2.iodide.io/v0.15.0/full/';
</script>
<script src="https://pyodide-cdn2.iodide.io/v0.15.0/full/pyodide.js"></script>
</head>
<body>
Pyodide test page <br>
Open your browser console to see pyodide output
<script type="text/javascript">
languagePluginLoader.then(function () {
pyodide.loadPackage('<address>').then(() => {
console.log(pyodide.runPython('
import sys
from <my package> import *
sys.version
'));
console.log(pyodide.runPython('print(1 + 2)'));
});
});
</script>
</body>
</html>
There is a chance that this question might be unclear, but please let me know if you have trouble understanding something.
Also, the string passed in the runPython() method is the python code, just to avoid confusion.
I even tried uploading the module to a server as the docs mentioned a URL using the HTTP protocol, was pretty stupid trying this but I did.
Docs: https://pyodide.readthedocs.io/en/latest/using_pyodide_from_javascript.html#loading-packages
Update: Pyodide v0.21.0
Starting with Pyodide 0.18.0, runPythonAsync does not automatically load packages, so loadPackagesFromImports should be called beforehand.
So, to import a third-party package like numpy we have two options: we can either pre-load required packages manually and then import them in Python
// JS
await pyodide.loadPackage('numpy');
// numpy is now available
pyodide.runPython('import numpy as np')
console.log(pyodide.runPython('np.ones((3, 3)))').toJs())
or we can use the loadPackagesFromImports function that will automatically download all packages that the code snippet imports:
// JS
let python_code = `
import numpy as np
np.ones((3,3))
`
(async () => { // enable await
await pyodide.loadPackagesFromImports(python_code)
let result = await pyodide.runPythonAsync(python_code)
console.log(result.toJs())
})() // call the function immediately
More examples can be found here
(async () => { // enable await
let python_code = `
import numpy as np
np.ones((3,3))
`
let pyodide = await loadPyodide();
await pyodide.loadPackagesFromImports(python_code)
console.log(pyodide.runPython(python_code).toJs())
})() // call the function immediately
<script src="https://pyodide-cdn2.iodide.io/v0.21.0/full/pyodide.js"></script>
Note also, that starting with version 0.17, Pyodide uses JsProxy for non-JS datatypes. So, before printing the results, it has to be converted using toJs.
Old answer (related to Pyodide v0.15.0)
It is not clear what you are passing as <address> in pyodide.loadPackage('<address>'), but it should just be the package name (e.g. numpy).
Also, note that Pyodide currently supports a limited number of packages. Check out this tutorial on it for more details.
If you want to import a third-party package like numpy there are two options: you can either pre-load required packages manually and then import them in Python using pyodide.loadPackage and pyodide.runPython functions:
pyodide.loadPackage('numpy').then(() => {
// numpy is now available
pyodide.runPython('import numpy as np')
console.log(pyodide.runPython('np.ones((3, 3)))'))
})
Or you can use pyodide.runPythonAsync function that will automatically download all packages that the code snippet imports.
Here is the minimal example for pyodide.runPythonAsync
let python_code = `
import numpy as np
np.ones((3,3))
`
// init environment, then run python code
languagePluginLoader.then(() => {
pyodide.runPythonAsync(python_code).then(output => alert(output))
})
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/pyodide/v0.21.0/full/pyodide.js"></script>
</head>
<body>
</body>
</html>

import statement gives a syntax error saying "Cannot use import statement outside a module"

I am new to web development, This is just a simple code i created to test a new text editor I installed, I got this error while trying to import react because I was getting some other errors like; TypeError: Cannot read property 'createElement' of undefined
Please, how can i resolve this? is the import statement wrong?
import React, { Fragment } from 'react';
console.log("Testing");
var document;//made this declaration because I got a document undefined error
document.createElement("p"):
const p = document.createElement("p")
p.innerHTML = "This is to test the javascript";
const body = document.querySelector("body");
body.insertBefore(p,body.childNode[-1]);
<!DOCTYPE html>
<html>
<head>
<title>Testing vs code</title>
<link rel="stylesheet" type="text/css" href="styles.css"/>
</head>
<body>
<h1>Testing the vscode html preview package</h1>
<h2>Hello</h2>
<script type="module" src="js-prac.js"></script>
</body>
</html>
As per https://reactjs.org/docs/add-react-to-a-website.html, you need to add these two lines to your HTML file before importing your script:
<script src="https://unpkg.com/react#16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" crossorigin></script>
I'm not sure that module loading is going to work the way you intend without using something like Create React App. You can remove the import statements and you can still reference React and ReactDOM in your script.
e.g.
'use strict';
const e = React.createElement;
class LikeButton extends React.Component {
constructor(props) {
super(props);
this.state = { liked: false };
}
render() {
if (this.state.liked) {
return 'You liked comment number ' + this.props.commentID;
}
return e(
'button',
{ onClick: () => this.setState({ liked: true }) },
'Like'
);
}
}
// Find all DOM containers, and render Like buttons into them.
document.querySelectorAll('.like_button_container')
.forEach(domContainer => {
// Read the comment ID from a data-* attribute.
const commentID = parseInt(domContainer.dataset.commentid, 10);
ReactDOM.render(
e(LikeButton, { commentID: commentID }),
domContainer
);
});
I Understand you want to create a simple application with React. I would recommend you to read this first https://kentcdodds.com/blog/how-to-react and then this one: https://reactjs.org/tutorial/tutorial.html
A react application can be created by importing script as you have started, but it is not the recommended way to build a react app.
Once you go through the above post, find a good tutorial of your choice on platforms of your choice be it blog or video based. I can name a few like udemy, frontend masters, pluralsight, and there are many more.
Take a look at ReactJS website.
You should create React app using Node package manager
npx create-react-app appName
or should link react scripts to your html
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
Also you can't redefine document object. This references your web page and you can access to element or DOM (Document Object Model) using document object.

Unexpected token import on Electron app

I have built an app using GitHub's Electron. I am using the recommended way of loading modules, the ES6 syntax of:
import os from 'os'
After downloading the boilerplate the app is working fine. I have been able to import scripts in the background.js file without issue. Below is how I am loading my custom module:
import { loadDb } from './assets/scripts/database.js';
However, when I open a new browser window (clipboard.html) within Electron I am then loading a JavaScript file (clipboard.js) which in turn tries to import modules. At this point I am getting an Unexpected token import error.
My clipboard.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Electron Boilerplate</title>
<link href="./stylesheets/main.css" rel="stylesheet" type="text/css">
<script>
window.$ = window.jQuery = require('./assets/scripts/jquery-1.12.1.min.js');
</script>
<script src="./assets/scripts/clipboard.js"></script>
</head>
<body class="clipboard">[...]</body></html>
My clipboard.js file:
import { remote } from 'electron'; // native electron module
import { loadDb } from './assets/scripts/database.js';
const electron = require('electron');
document.addEventListener('DOMContentLoaded', function () {
var db = loadDb();
db.find({ type: 'text/plain' }, function (err, docs) {
var docsjson = JSON.stringify(docs);
console.log(docsjson);
});
});
Just to re-iterate, the same code is used within app.html, which is my app's main window, and this does not error.
It feels like the main window is initialising something that my clipboard.html window isn't (perhaps 'Rollup'?), but there's nothing explicit within my app's code to suggest this.
You need to run clipboard.js through rollup first. Rollup parses the import statements. You have to modify tasks/build/build.js to do that.
var bundleApplication = function () {
return Q.all([
bundle(srcDir.path('background.js'), destDir.path('background.js')),
bundle(srcDir.path('clipboard.js'), destDir.path('clipboard.js')), // Add this line
bundle(srcDir.path('app.js'), destDir.path('app.js')),
]);
};
#user104317 got it right, clipboard.js just didn't get "compiled" by rollup.
Just wanted to add that in your case, it should have been:
var bundleApplication = function () {
return Q.all([
bundle(srcDir.path('background.js'), destDir.path('background.js')),
bundle(srcDir.path('app.js'), destDir.path('app.js')),
bundle(srcDir.path('assets/scripts/clipboard.js'), destDir.path('assets/scripts/clipboard.js')),
]);
};
Then you could have left it at ./assets/scripts/clipboard.js.
If you end up having a lot of independent js files (you shouldn't if you're building a SPA), consider listing them automatically, like done in ./tasks/build/generate_spec_imports.js

Categories