Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uncaught TypeError: Cannot read properties of undefined (reading 'main') when calling golang functions in subpages. #5

Open
mrsafalpiya opened this issue Jan 27, 2023 · 6 comments

Comments

@mrsafalpiya
Copy link

Description

Calling golang functions in subpages such as localhost:34115/foo produces following error:

image

But if we are to goto /foo from the homepage (/) using an anchor tag as <a href="/foo">Goto foo</a> the function call works as expected but as soon as we refresh the page, it breaks again.

To Reproduce

$ wails init -n subpage_golang_call -t https://github.com/plihelix/wails-template-sveltekit
$ cd subpage_golang_call/
$ mkdir frontend/src/routes/foo
$ cp frontend/src/routes/{+page.svelte,foo}
$ wails dev

Now browse http://localhost:34115/foo, enter a name and click on Greet:

image

Using anchor tag from homepage

$ echo "<a href='/foo'>Goto /foo</a>" >> frontend/src/routes/+page.svelte

Browse the home page, scroll to bottom and click on Goto /foo

image

Scroll to bottom, enter a name and click on Greet:

image

This works as expected. But if we refresh the page and again enter a name and click on Greet:

image

Expected behaviour

image

System Details

$ wails doctor
Wails CLI v2.3.1

Scanning system - Please wait (this may take a long time)...Done.

# System

OS           | Arch Linux
Version      | Unknown
ID           | arch
Go Version   | go1.19.5
Platform     | linux
Architecture | amd64

# Wails

Version         | v2.3.1
Package Manager | pacman

# Dependencies

Dependency | Package Name | Status    | Version
*docker    | docker       | Installed | 1:20.10.23-1
gcc        | gcc          | Installed | 12.2.1-1
libgtk-3   | gtk3         | Installed | 1:3.24.36-1
libwebkit  | webkit2gtk   | Installed | 2.38.3-1
npm        | npm          | Installed | 8.19.2-1
pkg-config | pkgconf      | Installed | 1.8.0-1
* - Optional Dependency

# Diagnosis

Your system is ready for Wails development!

Additional message

I have tried this only on linux.

@plihelix
Copy link
Owner

plihelix commented Jan 28, 2023

First, I've got to compliment the thoroughness of your post. It perfectly replicated the issue. What was perhaps most interesting was that I was able to use /foo within the app via the link, but not within a browser at localhost:34115/foo even when navigating (if the app didn't navigate there first).

Root Issue:

This is a result of the wails runtime not being injected into the generated localhost:34115/foo/index.html as it is in the localhost:34115/index.html.

If you inspect the / endpoint you will see the following two scripts injected into the head of the document.
head

What's happening:

These two scripts are injected by the wails app in order to link to the golang backend. But, when you navigate to /foo within a browser, Wails doesn't re-inject these scripts into the new page unless it is loaded within the running Wails app.

Solution # 1: Wails Runtime: Start at /

The first solution is a breakable one, adding even a bare-bones +layout.svelte like:
sol1
allows navigation within the browser from / to /foo to carry the injection as a user routes through the site since the layout is replacing the routes/+page.svelte with routes/foo/+page.svelte without changing the header.

Problem:

If a user navigates to localhost:34115/foo without having started at localhost:34115 the layout will not be loaded, so the required scripts aren't injected.

Solution # 2: Served Web-App: Start anywhere.

Adding these scripts to the head in these endpoints within the /foo/+page.svelte allows a browser to navigate directly to an endpoint while retaining the capability to communicate with the backend.
sol2

Problem:

This fails to negate a +layout.svelte that carries these injections when a user moves from the root to /foo resulting in doubled inclusion as seen below.
sol2-prob

Notes:

I have confirmed that this problem of double injection remains within the Wails app using solution # 2 with Svelte-kit even without the +layout.svelte to carry the injection. Currently, Wails is not intended as a served web-app for an external browser to navigate. While this doesn't appear to cause any conflicts, repeated injection can easily consume more resources than necessary as a user continues to navigate.

@mrsafalpiya
Copy link
Author

mrsafalpiya commented Jan 28, 2023

Great observation!

Based on your second solution I have came up with this solution which is to be placed on every route's +page.svelte or +layout.svelte file:

>> NOTE: The following code has few problems. Please refer to the updated code two comments down. <<

import { browser } from "$app/environment";

if (browser && !window.hasOwnProperty("wailsbindings")) {
  // Inject missing /wails/ipc.js and /wails/runtime.js
  let wails_ipc = document.createElement("script");
  wails_ipc.setAttribute("src", "/wails/ipc.js");

  let wails_runtime = document.createElement("script");
  wails_runtime.setAttribute("src", "/wails/runtime.js");

  document.head.appendChild(wails_ipc);
  document.head.appendChild(wails_runtime);
}

This avoids repeated injection of the scripts.

Looks like this solves this issue!

@plihelix
Copy link
Owner

plihelix commented Jan 28, 2023

Thanks for this! I'll double check this solution in wails and browser this weekend and update the repo accordingly. Both Svelte & JS are new to me but I had considered that there may be a way to check and avoid the repeat.

I've shared this issue with the Wails discord due to the targets of v3; such as multi-window applications and local network web-apps. Which are certainly situations where both the user may choose to start at a location other than root and where it doesn't even necessarily make sense to launch from the root.

It may be more beneficial to contain this snippet in the associated +page.js on the route (or add it to the $lib by default) or equivalent in Vue, Angular, etc. This method should at least reduce it to an import and a call. While there's always the option of adding a PR to make a wails (or other) command to generate a new route with the solution, I think working this out might help Wails move towards a more robust and simple development environment. Of course, for simplicity sake, it would be ideal for all frameworks to not need to replicate it or create a variety of custom solutions that are unique to each.

@mrsafalpiya
Copy link
Author

Great observation!

Based on your second solution I have came up with this solution which is to be placed on every route's +page.svelte or +layout.svelte file:

import { browser } from "$app/environment";

if (browser && !window.hasOwnProperty("wailsbindings")) {
  // Inject missing /wails/ipc.js and /wails/runtime.js
  let wails_ipc = document.createElement("script");
  wails_ipc.setAttribute("src", "/wails/ipc.js");

  let wails_runtime = document.createElement("script");
  wails_runtime.setAttribute("src", "/wails/runtime.js");

  document.head.appendChild(wails_ipc);
  document.head.appendChild(wails_runtime);
}

This avoids repeated injection of the scripts.

Looks like this solves this issue!

Update

Looks like a better approach is to have the script injection code on the head tag itself:

<svelte:head>
  <script>
    if (!window.hasOwnProperty("wailsbindings")) {
      let wails_ipc = document.createElement("script");
      wails_ipc.setAttribute("src", "/wails/ipc.js");

      let wails_runtime = document.createElement("script");
      wails_runtime.setAttribute("src", "/wails/runtime.js");

      document.head.appendChild(wails_ipc);
      document.head.appendChild(wails_runtime);
    }
  </script>
</svelte:head>

Why was previous code a problem?

Consider the above code:

+layout.svelte:

<script lang="ts">
  import { browser } from "$app/environment";

  if (browser && !window.hasOwnProperty("wailsbindings")) {
    let wails_ipc = document.createElement("script");
    wails_ipc.setAttribute("src", "/wails/ipc.js");

    let wails_runtime = document.createElement("script");
    wails_runtime.setAttribute("src", "/wails/runtime.js");

    document.head.appendChild(wails_ipc);
    document.head.appendChild(wails_runtime);
  }
</script>

<slot />

+page.svelte:

<script>
  import { Greet } from "$lib/wailsjs/go/main/App.js";
  import { onMount } from "svelte";

  onMount(() => {
    console.log(Greet("foo"));
  })
</script>

The result would be:

image

Looks like the ipc connection gets established after the onMount() hook.

The new code

layout.svelte:

<svelte:head>
  <script>
    if (!window.hasOwnProperty("wailsbindings")) {
      let wails_ipc = document.createElement("script");
      wails_ipc.setAttribute("src", "/wails/ipc.js");

      let wails_runtime = document.createElement("script");
      wails_runtime.setAttribute("src", "/wails/runtime.js");

      document.head.appendChild(wails_ipc);
      document.head.appendChild(wails_runtime);
    }
  </script>
</svelte:head>

<slot />

The result will be:

image

@andradei
Copy link

andradei commented Jul 30, 2023

It's been 6 months. Are you still using the svelte:head solution above to avoid IPC issues?

@plihelix
Copy link
Owner

plihelix commented Jul 31, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants