sveltejs / kit

web development, streamlined
https://kit.svelte.dev
MIT License
17.89k stars 1.8k forks source link

SvelteKit + Electron = 💔 #11997

Open jerrygreen opened 2 months ago

jerrygreen commented 2 months ago

Describe the bug

There's an issue when working from within ElectronJS, as there are non-web routes involved: they're file-based.

For starters, there's one issue inside generated index.html file, there's a line like:

__sveltekit_wgqb1w = {
  base: new URL(".", location).pathname.slice(1, -1)
};

On Windows, it returns url something like that:

'/C:/Users/Jerry/projects/electron-svelte/out/app.asar/.vite'

So the fact that it starts from /C:/ is already an issue since it then return an error:

app.qFRTHcTk.js:7 Error: Not found: /C:/Users/Jerry/projects/electron-svelte/out/app.asar/.vite/index.html

I tried to intervene into Svelte building pipeline, so when building I make corresponding changes to index.html:

-new URL(".", location).pathname.slice(0, -1)
+new URL(".", location).pathname.slice(1, -1)

Now the url is formed properly, but now I'm stuck into some infinite loop by some reason:

Throttling navigation to prevent the browser from hanging. See https://crbug.com/1038223. Command line switch --disable-ipc-flooding-protection can be used to bypass the protection

It tries to load some initial code it seems, but I'm not sure where and why it breaks – since no error occurs. Only a warning:

image

There's a lot of blinking involved, so clearly: the thing is looped. It's broken. And it doesn't render any html as you can see.

But if I'm not running from statics, instead from web version like this:

mainWindow.loadURL('http://localhost:5173')

Then it works perfectly fine:

image

So, the issue only seems related to when running from local static files like this:

mainWindow.loadFile(path.resolve(app.getAppPath(), '.vite/index.html'))

Reproduction

Largely I use this minimal electron-forge repo that explains how to use electron with esm:

https://github.com/jdms754/electron-forge-vite-esm-test

It works perfectly for dev and production: i.e. both from mainWindow.loadUrl and mainWindow.loadFile.

So I cloned it, added Svelte, and it works... But only in dev environment (mainWindow.loadUrl).

When it gets to building an executable with statics, I need to load statics like this:

mainWindow.loadFile(path.resolve(app.getAppPath(), '.vite/index.html'))

Then here, the problem occurs. All as described above: first with wrong base path, then with infinite loop.

Logs

No response

System Info

Windows 10 (22H2)
Node: 20.11.1
Electron: 29.1.4
@sveltejs/adapter-auto: ^3.0.0 => 3.1.1
@sveltejs/adapter-static: 3.0.1 => 3.0.1
@sveltejs/kit: ^2.0.0 => 2.5.3
@sveltejs/vite-plugin-svelte: ^3.0.0 => 3.0.2
svelte: 5.0.0-next.80 => 5.0.0-next.80
vite: ^5.0.3 => 5.1.5

Severity

blocking all usage of SvelteKit

Additional Information

Questions to approach to a resolution: • At which point the infinite loop happens? • Can we make changes to resolve that?

And please don't suggest to NOT use SvelteKit: I might see this suggestion coming. Because in Electron if you don't have url section so you basically don't need routing right? Just use basic Svelte? But if I want to support both web-version and electron-version of my app, I still would like to use single codebase with SvelteKit – so in web version I can have this cool routing system that SvelteKit provides, and electron-version: it just works. Plus, even without ability to change url in electron-version I still prefer the routing system of SvelteKit rather than pure Svelte – it helps to organize code so much better!

jerrygreen commented 2 months ago

My guess, something is wrong here, starting at if (hydrate) ...:

image

It seems it constantly does goto(location.href... and that's the reason of infinite loop.

I'm not too much into the codebase, I don't know all details, but on a glance I can't even imagine how it even works in other use-cases, other than file:// protocol. It almost seems like it should go into infinite loop in every single use-case because it always passes exactly two arguments to kit.start(app, element), without hydrate argument, where at this point it's looped:

image

There's obviously some magic happening that I don't see, because it's not infinitely looped in other cases than file:// protocol.


I'm sorry for pinging but it seems quite important one, @Rich-Harris can you give some feedback? Like your thoughts of how/where it might go wrong that it goes into infinite loop? Or even more broadly: if you ever plan/want SvelteKit to support when used in conjunction with Electron? Or how the hydration works here. Any feedback appreciated, really. Thanks.

khromov commented 2 months ago

As far as I know SvelteKit apps have never worked when you run them from the disk, you always need a web server of some sort. Have you checked any of the available templates such as https://github.com/FractalHQ/sveltekit-electron ?

jerrygreen commented 2 months ago

For bundling, they use electron-serve, which is basically a file server.

Still, I'd prefer to run statics without any http wrapper.

I am not sure how viable this idea of running statics as is. Maybe it's not viable at all. I'm sorry if I'm proposing some dumb idea that would take eternity to refactor SvelteKit just to this need.

And, actually, thank you for feedback. I will consider their approach as workaround, maybe it's not that bad as I think.

khromov commented 2 months ago

I think this limitation is mostly around the fact that JS modules (which SvelteKit uses heavily) can't be loaded from file:// urls:

You need to pay attention to local testing — if you try to load the HTML file locally (i.e. with a file:// URL), you'll run into CORS errors due to JavaScript module security requirements. You need to do your testing through a server.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#other_differences_between_modules_and_standard_scripts

There is a Vite plugin called vite-plugin-singefile that is able to wrap all the CSS and JS into a single file, this can run from a single index.html file. You can use this in Svelte, but I haven't seen anyone have luck with this in SvelteKit (probably due to the complicated nature of SK bundling)

jerrygreen commented 2 months ago

It’s no surprise file:// protocol protected by cors, it wouldn’t be safe for browsers to allow requesting file:// urls from other files: a lot of nasty stuff could be done just from opening a local html page, or worse – from a web page. Web security standards is on high stakes.

But electron is another story. It isn't just a web app, it's an executable app only then a web app, – and for executables it's already assumed they can do much more than simple html file, including file access. Thus, security standards are different. Chrome engine allows to pass --allow-file-access-from-files which negates cors issue for file access. And in Electron, it seems to be enabled by default. So, from an Electron app, I can fetch via file:// protocol no problem.

Here's my little experiment in devtools, inside of electron app:

image

As you can see, I was able to access index2.html from index1.html both from file:// protocol.

It works both for relative paths, and for absolute paths like:

await fetch('file:///C:/Users/Jerry/projects/electron-template/.vite/renderer/index2.html')
await fetch('index2.html') // equivalent to this

So, this shouldn't be a problem to JS modules. Might require some code changes here and there in SvelteKit, still. Because currently SvelteKit fully assumes it is run from http. But it doesn't have to. I'd love to have Electron as first-class citized in SvelteKit.

mhkeller commented 3 weeks ago

@jerrygreen try setting ssr = true in your layout config: https://github.com/mhkeller/sveltekit-electron/