···11/**
22- * This file has been copied from pyodide source and modified to allow
33- * pyodide to be used in a web worker within this
22+ * Pyodide Web Worker
33+ *
44+ * Load order:
55+ * 1. Pyodide runtime (served as a static asset at /pyodide/).
66+ * 2. Binary packages bundled with Pyodide (numpy, scipy, matplotlib, pandas).
77+ * 3. MNE-Python and its pure-Python deps, installed offline from local
88+ * wheel files that were pre-downloaded by `npm run install-mne-wheels`.
99+ * The manifest at /packages/manifest.json maps package names → filenames.
410 */
51166-// pyodide is served as a static asset at /pyodide/ (via Vite publicDir).
1212+// Pyodide is served as a static asset at /pyodide/ (via Vite publicDir).
713// An absolute path is required so importScripts resolves correctly regardless
814// of where the worker script itself is served from.
915importScripts('/pyodide/pyodide.js');
10161117async function loadPyodideAndPackages() {
1218 self.pyodide = await loadPyodide({ indexURL: '/pyodide/' });
1313- await self.pyodide.loadPackage(['matplotlib', 'mne', 'pandas']);
1919+2020+ // Load binary packages that are bundled with the Pyodide npm package and
2121+ // therefore available locally without any network request.
2222+ await self.pyodide.loadPackage(['numpy', 'scipy', 'matplotlib', 'pandas']);
2323+2424+ // Load MNE and its pure-Python dependencies from pre-downloaded wheel files.
2525+ // The manifest was written by `node internals/scripts/InstallMNE.mjs`.
2626+ let manifest = {};
2727+ try {
2828+ const response = await fetch('/packages/manifest.json');
2929+ if (response.ok) {
3030+ manifest = await response.json();
3131+ } else {
3232+ console.warn('[pyodide worker] manifest.json not found — MNE will not be available');
3333+ }
3434+ } catch (err) {
3535+ console.warn('[pyodide worker] Could not fetch manifest.json:', err);
3636+ }
3737+3838+ const wheelUrls = Object.values(manifest)
3939+ .map((entry) => `/packages/${entry.filename}`);
4040+4141+ if (wheelUrls.length > 0) {
4242+ await self.pyodide.loadPackage('micropip');
4343+ const micropip = self.pyodide.pyimport('micropip');
4444+ // micropip resolves relative URLs against the worker's base URL.
4545+ // Pass absolute URLs so it works regardless of worker location.
4646+ const absoluteUrls = wheelUrls.map(
4747+ (u) => new URL(u, self.location.origin).href
4848+ );
4949+ await micropip.install(absoluteUrls);
5050+ } else {
5151+ console.warn('[pyodide worker] No MNE wheels found in manifest.');
5252+ }
1453}
5454+1555let pyodideReadyPromise = loadPyodideAndPackages();
16561757self.onmessage = async (event) => {
1818- // make sure loading is done
1958 await pyodideReadyPromise;
2020- // Don't bother yet with this line, suppose our API is built in such a way:
5959+2160 const { data, ...context } = event.data;
2222- // The worker copies the context in its own "memory" (an object mapping name to values)
2361 for (const key of Object.keys(context)) {
2462 self[key] = context[key];
2563 }
2626- // Now is the easy part, the one that is similar to working in the main thread:
6464+2765 try {
2866 self.postMessage({
2967 results: await self.pyodide.runPythonAsync(data),