Pyodide (Python Interpreter) Initialization Error in Obsidian Plugin

I’m getting an error when using the Pyodide library within my Obsidian plugin class. Below is the code I’m working with:

import { Plugin } from 'obsidian';
import { loadPyodide } from 'pyodide';


export default class MyPlugin extends Plugin {
	async onload() {
		console.log('My CUSTOM plugin');
		process.browser = "obsidian";
		const pyodide = await loadPyodide(
			{
				indexURL : "https://cdn.jsdelivr.net/pyodide/v0.26.4/full/"
			}
		);
		await pyodide.loadPackage("micropip");
		await pyodide.loadPackage("numpy");
		console.log(pyodide);
		console.log(pyodide.runPython('print("CUSTOM PRINT"); 1+1'));

		this.registerMarkdownCodeBlockProcessor('pyodide', async (source, el, ctx) => {
			pyodide.runPython(source);
		});
	}
}

When I start Obsidian, I get the following error:

Interestingly, if I disable and then re-enable the plugin from the list of plugins, it works as expected:

Why am I encountering this error when starting Obsidian, and how can I resolve it so that the plugin works correctly on the initial load without needing to disable and re-enable it?

Extra details

I added the following external node packages to the default esbuild.config.js to avoid compilation errors:

  • node:path
  • node:fs
  • node:vm
  • node:promises
  • node:url
  • node:crypto
  • node:child_process

I’m new to javascript and bundles :slight_smile:

Well, I am not sure how to solve this CORS issue in a node bundler, It’s easy to solve in python projects. You might have to, somehow, add the app://obsidian.md in CORS.

I would suggest, create a beta release of your plugin and try installing it through BRAT to see if it loads correctly, and most important than that, whether it builds correctly on GitHub actions, before proceeding further with your development to avoid any end moment frustration for using long packages.

Also, try to build the plugin and loading, similar to other plugins. If that works, then the issue might be only for development environment.

Thank you, @Tu2_atmanand! I wasn’t aware of BRAT, and I will definitely give it a try. I will also try GitHub actions.

Could you explain the differences between testing the plugin locally and publishing and installing it? Is there any difference in the loading process? As far as I know, all plugins are essentially a main.js file that is loaded from the plugins directory, so I do not see how the installation method could make a difference.

Thanks again!

@guiferviz that’s an interesting problem, difficult to debug

If we observe the compiled js

  1. From Pyodide sources
var g = typeof process == "object" && typeof process.versions == "object" && typeof process.versions.node == "string" && !process.browser;

Here g will be true on the first run because process.browser is undefined

  1. You have in your code
process.browser = "obsidian";

but this code is executed AFTER the code I mentioned above, so the variable g is still true there

  1. From Pyodide sources
async function T() {
  if (!g || (V = (await import("node:url")).default, H = await import("node:fs"), D = await import("node:fs/promises"), z = (await import("node:vm")).default, L = await import("node:path"), U = L.sep, typeof O < "u"))
    return;
  let e = H, t = await import("node:crypto"), o = await Promise.resolve().then(() => __toESM(require_browser(), 1)), r = await import("node:child_process"), a = { fs: e, crypto: t, ws: o, child_process: r };
  globalThis.require = function(n) {
    return a[n];
  };
}

As your g in (1) is true, the code tries to get import("node:url") and other node modules. And that’s causes your error.

However the subsequent times you try to load the plugin, your process.browser is already set in (2), and therefore g is false in (1) and the error in (3) doesn’t occur.

One of the ways to solve your issue is to patch the compiled main.js such that process.browser is set before the problematic code executes.

E.g. in esbuild.config.mjs

const banner =
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/

process.browser = "obsidian"; // it's a dirty hack but it works
`;
2 Likes

Hi @mnaoumov. Thank you very much for your answer, it has indeed solved my problem! It was a tricky one. For some reason, I thought that the check you mentioned in point (1) was done within loadPyodide, but it’s obvious that it wasn’t.

Thank you for pointing out that I need to include that line in the banner of esbuild.config.mjs. I didn’t understand why it wasn’t enough to move process.browser to the beginning of the main.ts file (before the imports), but I’ve seen that the generated main.js includes the pyodide code before, no matter where the imports are located.

Thanks again, I really appreciate it! I learned a lot from this!

2 Likes

Right the bundled plugin is in dev environment. If you are building a prod version of it and then loading in Obsidian. That means it will work just fine after publishing.

Well, I dont know the exact difference incase of dev and prod loading process, Obsidian team might able to mention something on this. But, there is obviously a difference as you might know.

The reason for mention this is because, in past I have faced issues, where the plugin was working just fine in dev environment, but when I build it, it was failing to load, or giving errors while building. It was long back, can give specific example.

Also, now when you will be publishing your plugin, it will be ran on the GitHub Actions and your main.js will be build on GitHub, from where you will release it, in the release section of you repo. And since this is a unique error you had, and a unique plugin development, of using python libraries in node. So, you might get unexpected things in production environment, hence thought of mentioning this, although its rare. But great to know the solution, learned new stuff.

1 Like