plihelix / wails-template-sveltekit

Wails.io template using SvelteKit - trades SSR for golang within the app
https://github.com/plihelix/wails-sveltekit
40 stars 1 forks source link

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

Open mrsafalpiya opened 1 year ago

mrsafalpiya commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

plihelix commented 1 year ago

Situationally, yes. One of my projects needs it in the +layout and the other doesn't. I've gone back and forth on including it.

On Sun, Jul 30, 2023, 4:27 PM Isaac Andrade @.***> wrote:

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

— Reply to this email directly, view it on GitHub https://github.com/plihelix/wails-template-sveltekit/issues/5#issuecomment-1657280687, or unsubscribe https://github.com/notifications/unsubscribe-auth/ARUJFUP7LQFVA7EDKWE4SFLXS3NVVANCNFSM6AAAAAAUINWE5M . You are receiving this because you commented.Message ID: @.***>