rvanasa / vite-react-motoko

Starter project for Vite + React + TypeScript + Motoko
https://gitpod.io/#https://github.com/rvanasa/vite-react-motoko
39 stars 23 forks source link

Using webworkers with global window caused undefined reference error #3

Closed atengberg closed 1 year ago

atengberg commented 1 year ago

Setting up a project using a webworker I ran into an issue that the window wasn't defined for global when the webworker was created.

To resolve, I dropped the global global: 'window', from define and instead used:

  define: {
-   global: window,
    'process.env.DFX_NETWORK': JSON.stringify(process.env.DFX_NETWORK),
    // Expose canister IDs provided by `dfx deploy`
    ...Object.fromEntries(
      Object.entries(canisterIds).map(([name, ids]) => [
        `process.env.${name.toUpperCase()}_CANISTER_ID`,
        JSON.stringify(ids[network] || ids[localNetwork]),
      ]),
    ),
  },
+ optimizeDeps: {
+  esbuildOptions: {
+     // Node.js global to browser globalThis
+    // Required for worker to work.
+   define: {
+      global: 'globalThis',
+  },
+ },
  },

as part of the vite config.

rvanasa commented 1 year ago

Thanks for pointing this out! I updated the starter project based on your suggestion.

atengberg commented 1 year ago

After some research and testing, seems like what's necessary is:

1)

<!DOCTYPE html>
 ...
  <body>
    <div id="root"></div>
    <script type="module" src="/src/frontend/src/main.jsx"></script>
+    <!--Script polyfills global.-->
+    <script>window.global ||= window</script>
  </body>
</html>

in index.html (or otherwise as its own file import in main or App might make it more clear). This will polyfill global everywhere dfx's type declarations rely on them. It also makes (at least in Chrome) web worker's have access to self (otherwise they'll fail to load). Though for web workers that have (ES6) imports, they also need the optimizeDeps... entry. Looks like there's still no best solution: https://github.com/vitejs/vite/issues/4796 (last comment is interesting).

In case you hadn't seen, from @kPeacock AuthClient demo looks like all the code involved in the define entries mapping canister ids/dfx state can be more simply added using the vite-plugin-enviroment if "output_env_file": ".env", is added to dfx.json (which seems to be added by default now) (here's a super minimal vite.config.js working "out of the box" with dfx new and the output_env_file set).

import  { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import EnvironmentPlugin from 'vite-plugin-environment'

export default defineConfig({
  plugins: [
    react(),
    // Adds all envars prefixed with CANISTER_ from ./.env  as values on process.env.*
    EnvironmentPlugin("all", { prefix: "CANISTER_" }),
    EnvironmentPlugin("all", { prefix: "DFX_" }),
  ],
 optimizeDeps: {
   esbuildOptions: {
     // Node.js global to browser globalThis
     // Required for worker to work.
     define: {
       global: 'globalThis',
     },
  },
  server: {
    // Local IC replica proxy.
    proxy: {
      '/api': {
        // Default port used. 
        target: 'http://127.0.0.1:4943',
      }
    }
  }
})

Now dfx just needs to add the port to the env listing.

Also thanks for making and sharing the repo, getting up to speed with tailwind couldn't have been easier!

rvanasa commented 1 year ago

Awesome; thanks for the follow-up!

I played around with the AuthClient demo, and it seems necessary to manually include the environment variables in a .env file to get the same behavior when running the dev server via npm start. Does that sound consistent with your experience? It would be great to find a way to use vite-environment-plugin if there ends up being feasible to get this working with the Vite dev server.

krpeacock commented 1 year ago

If you're on dfx 0.14.1 and you configure "output_env_file": ".env", in dfx.json it should all be fine!

rvanasa commented 1 year ago

Very cool! Works perfectly for this use case.

atengberg commented 11 months ago

One thing of note is that when I setup the testing in a different directory and imported the config, it did not carry over the environmental variables (just to note).

Also, the other thing that may be worth looking into is I found while testing jsdom did not handle authenticated actor calls well (though this may not be so much an issue for unit tests, but for integration...).