0

Using Vue with Pinia, is it possible to load loadPyodide() into Pinia and assign it to a variable, so that components can use runPython()?

For example, in Pinia, I have this code where I am assigning loadPyodide() to the variable pyodide:

In file index.html I add pyodide.js

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <link rel="icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/pyodide/v0.23.0/full/pyodide.js"></script>
    <title>Contabilidade</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

In Pinia:

import { ref } from 'vue'
import { defineStore } from 'pinia'

export const pythonStore = defineStore('python', () => {
  let pyodide = ref(null)

  async function loadPyodideInstance() {
    pyodide = await loadPyodide()
  }

  return {
    pyodide,
    loadPyodideInstance
  }
  
})

In App.vue, I load loadPyodide():

<script setup lang="ts">
    import HomeView from './views/HomeView.vue'
    import { pythonStore } from './stores/python'

    const python = pythonStore()
    python.loadPyodideInstance()
</script>

<template>
    <HomeView/>
</template>

The HomeView view loads the Upload component

<script setup lang="ts">
  import Upload from '../components/Upload.vue'
</script>

<template>
  <main>
    <div class="h-screen flex justify-center items-center">
      <Upload />
    </div>
  </main>
</template>

<style>
</style>

And in the Upload component, I execute runPython().

<template>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { pythonStore } from '../stores/python'
import { storeToRefs } from 'pinia'

export default defineComponent({
    name: 'Testing',

    setup() {
        const python = pythonStore()
        const { pyodide } = storeToRefs(python)
        console.log(
            pyodide.runPython(`
            import sys
            sys.version
          `)
        )

        return {
            pyodide,
        };
    },

});
</script>

<style>
</style>

But it's giving the error: Uncaught TypeError: pyodide.runPython is not a function

How can I make let pyodide = ref(null) available in any component?

Because this way I only load loadPyodide() once.

rafaelcb21
  • 12,422
  • 28
  • 62
  • 86

1 Answers1

0

You need to create a singleton module and actually don't need to use pina here.

First create pyodide.js

// pyodide.js
export default {
  instance: null,
  readyCallbacks: [],
  // initiate the pyodide instance
  async init() {
    // only initiate once
    if (!this.instance) {
      this.instance = await loadPyodide();
      // after initiating success, execute the callback queue
      this.readyCallbacks.forEach((func) => {
        func(this.instance);
      });
      this.readyCallbacks = []
    }
  },
  // use this function to ensure the instance is initiated
  onReady(func) {
    if (!this.instance) {
      this.readyCallbacks.push(func);
    } else {
      func(this.instance);
    }
  },
};

Second, import it to App.vue and call init()

// App.vue
import pyodide from './pyodide.js'
pyodide.init()

Finally, use pyodide in your component:

// SomeComponent.vue
import pyodide from './pyodide.js'
pyodide.onReady(instance) {
  instance.runPython(`
    import sys
    sys.version
  `)
}

Btw, it's recommended to use the npm package instead of the script tag in the Vue project enhance tree-shaking and prioritize script loading

Duannx
  • 7,501
  • 1
  • 26
  • 59
  • the error appears `runtime-core.esm-bundler.js:40 [Vue warn]: Property "ready" was accessed during render but is not defined on instance. ` – rafaelcb21 Apr 08 '23 at 09:28
  • I don't know what the `ready` comes from. As you can see in my answer, there is no variable named `ready`. Anyway, a [mre](https://stackoverflow.com/help/minimal-reproducible-example) should help us easier for debugging – Duannx Apr 10 '23 at 02:39