Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions docs/live-demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ This demo is using two of the most popular plugins: `sp-repo-review` and

<div id="root">Loading...</div>

<script
src="https://cdn.jsdelivr.net/pyodide/v0.29.3/full/pyodide.js"
crossorigin
></script>

<!-- Fonts to support Material Design -->
<link
rel="stylesheet"
Expand Down
26 changes: 8 additions & 18 deletions docs/webapp.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ If you copy the webapp into your page, use this header (with the link to where
you extract the webapp):

```html
<script
src="https://cdn.jsdelivr.net/pyodide/v0.29.3/full/pyodide.js"
crossorigin
></script>
<!-- Fonts to support Material Design -->
<link
rel="stylesheet"
Expand Down Expand Up @@ -59,28 +55,22 @@ And then after that, call the script with whatever dependencies you want:

### Bundler notes

When bundling the app for the web, Pyodide is NOT bundled via an npm package.
The webapp expects `loadPyodide()` to be available at runtime (for example by
including Pyodide from the official CDN or otherwise providing it on the host
page). Running `bun run build` writes a bundled ESM file to
`docs/_static/scripts/repo-review-app.min.js`, which the Live Demo imports as a module.
The webapp loads Pyodide automatically from the jsDelivr CDN
(`https://cdn.jsdelivr.net/pyodide/`) at runtime; no extra `<script>` tag is
required. The version loaded matches the `pyodide` npm package version used at
build time. Running `bun run build` writes a bundled ESM file to
`docs/_static/scripts/repo-review-app.min.js`, which the Live Demo imports as a
module.

## Custom app

If you prefer to write a custom integration, ensure Pyodide is loaded on the
page and then call `loadPyodide()` as the demo does. For example, load Pyodide
from the CDN and then mount the app (or import the ESM bundle):

Global (script) example:
To embed the app, simply import the ESM bundle and call `mountApp()`:

```html
<script src="https://cdn.jsdelivr.net/pyodide/v0.29.3/full/pyodide.js"></script>
<script type="module">
import { mountApp } from "./_static/scripts/repo-review-app.min.js";
await loadPyodide();
mountApp({ header: false, deps: ["repo-review"] });
</script>
```

The webapp code expects a callable `loadPyodide()` and will use `micropip` to
install any requested Python packages.
The webapp loads Pyodide and uses `micropip` to install any requested Python packages.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
"eslint": "^10.1.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react": "^7.37.5",
"pyodide": "^0.29.3",
"typescript": "^6.0.2",
"typescript-eslint": "^8.58.0"
},
"dependencies": {
"pyodide": "^0.29.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"@mui/material": "^7.3.9",
Expand Down
4 changes: 0 additions & 4 deletions src/repo-review-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@
name="viewport"
content="initial-scale=1, width=device-width"
/>
<script
src="https://cdn.jsdelivr.net/pyodide/v0.29.3/full/pyodide.js"
crossorigin
></script>
<!-- Fonts to support Material Design -->
<link
rel="stylesheet"
Expand Down
2 changes: 2 additions & 0 deletions src/repo-review-app/repo-review-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ interface Option {
interface AppProps {
deps: string[];
header?: boolean;
pyodideBaseUrl?: string;
}

interface AppState {
Expand Down Expand Up @@ -414,6 +415,7 @@ class App extends React.Component<AppProps, AppState> {
componentDidMount() {
this.pyodide_promise = prepare_pyodide(
this.props.deps,
this.props.pyodideBaseUrl,
(p: number, m?: string) =>
this.setState({
pyodideProgress: p,
Expand Down
22 changes: 13 additions & 9 deletions src/repo-review-app/utils/pyodide.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
import type { PyodideInterface } from "pyodide";
import type { PyProxy } from "pyodide/ffi";
import pyodidePackage from "pyodide/package.json";

declare global {
interface Window {
loadPyodide?: () => Promise<PyodideInterface>;
}
}
// Version resolved from the npm package at build time; runtime files loaded from CDN
const DEFAULT_PYODIDE_BASE_URL = `https://cdn.jsdelivr.net/pyodide/v${pyodidePackage.version}/full`;

export async function prepare_pyodide(
deps: string[],
pyodideBaseUrl?: string,
onProgress?: (p: number, m?: string) => void,
): Promise<PyodideInterface> {
const deps_str = deps.map((i) => `\"${i}\"`).join(", ");
try {
if (onProgress) onProgress(5, "Initializing Pyodide runtime");
// loadPyodide is provided by the Pyodide script at runtime
const pyodide: PyodideInterface = await (window as Window).loadPyodide!();
const baseUrl = pyodideBaseUrl ?? DEFAULT_PYODIDE_BASE_URL;
const { loadPyodide } = (await import(`${baseUrl}/pyodide.mjs`)) as {
loadPyodide: () => Promise<PyodideInterface>;
};
const pyodide: PyodideInterface = await loadPyodide();
if (onProgress) onProgress(50, "Core Pyodide loaded");

if (onProgress) onProgress(65, "Loading micropip");
await pyodide.loadPackage("micropip");
if (onProgress) onProgress(80, "Installing Python packages");

// Pass deps via globals instead of string interpolation for safety
pyodide.globals.set("_rr_deps_to_install", deps);
await pyodide.runPythonAsync(`
import micropip
await micropip.install([${deps_str}])
await micropip.install(list(_rr_deps_to_install))
`);
pyodide.globals.delete("_rr_deps_to_install");

if (onProgress) onProgress(100, "Ready");
return pyodide;
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"forceConsistentCasingInFileNames": true,
"outDir": "dist",
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"lib": ["DOM", "DOM.Iterable", "ES2022"]
},
"include": ["src/**/*"]
Expand Down
Loading