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

Components that require an external top level await during compilation fail in svelte 5 #13015

Closed
dsgallups opened this issue Nov 16, 2024 · 8 comments
Labels

Comments

@dsgallups
Copy link

dsgallups commented Nov 16, 2024

Describe the bug

On import of any function binding from WebAssembly will return an error not found when running a vite development build, due to usage of vite-plugin-top-level-await. This is a Sveltekit issue that appears upon usage of Svelte 5 over Svelte 4.

Discovered behavior using the cloudflare adapter. svelte-bug-wasm-2 reproduces the behavior with the auto adapter.

Uses: wasm-pack: 0.13.1, vite-plugin-wasm: 3.3.0 for wasm bindings

Reproduction

https://github.com/dsgallups/svelte-bug-wasm
https://github.com/dsgallups/svelte-bug-wasm-2

Branch with working compilation on svelte 4: https://github.com/dsgallups/svelte-bug-wasm-2/tree/svelte-4-working

Logs

TypeError: Pyramid_0 is not a function
    at Root (file:///Users/danielgallups/repo/svelte-bug-wasm-2/.svelte-kit/output/server/chunks/internal.js:510:5)
    at render (file:///Users/danielgallups/repo/svelte-bug-wasm-2/.svelte-kit/output/server/chunks/index3.js:812:3)
    at Function._render [as render] (file:///Users/danielgallups/repo/svelte-bug-wasm-2/.svelte-kit/output/server/chunks/internal.js:472:20)
    at render_response (file:///Users/danielgallups/repo/svelte-bug-wasm-2/.svelte-kit/output/server/index.js:1238:34)
    at async render_page (file:///Users/danielgallups/repo/svelte-bug-wasm-2/.svelte-kit/output/server/index.js:2066:12)
    at async resolve2 (file:///Users/danielgallups/repo/svelte-bug-wasm-2/.svelte-kit/output/server/index.js:2700:24)
    at async respond (file:///Users/danielgallups/repo/svelte-bug-wasm-2/.svelte-kit/output/server/index.js:2592:22)
    at async visit (file:///Users/danielgallups/repo/svelte-bug-wasm-2/node_modules/@sveltejs/kit/src/core/postbuild/prerender.js:208:20)

node:internal/event_target:1094
  process.nextTick(() => { throw err; });
                           ^
Error: 500 /
To suppress or handle this error, implement `handleHttpError` in https://svelte.dev/docs/kit/configuration#prerender
    at file:///Users/danielgallups/repo/svelte-bug-wasm-2/node_modules/@sveltejs/kit/src/core/config/options.js:203:13
    at file:///Users/danielgallups/repo/svelte-bug-wasm-2/node_modules/@sveltejs/kit/src/core/postbuild/prerender.js:71:25
    at save (file:///Users/danielgallups/repo/svelte-bug-wasm-2/node_modules/@sveltejs/kit/src/core/postbuild/prerender.js:419:4)
    at visit (file:///Users/danielgallups/repo/svelte-bug-wasm-2/node_modules/@sveltejs/kit/src/core/postbuild/prerender.js:242:3)
Emitted 'error' event on Worker instance at:
    at [kOnErrorMessage] (node:internal/worker:326:10)
    at [kOnMessage] (node:internal/worker:337:37)
    at MessagePort.<anonymous> (node:internal/worker:232:57)
    at [nodejs.internal.kHybridDispatch] (node:internal/event_target:820:20)
    at MessagePort.<anonymous> (node:internal/per_context/messageport:23:28)

Node.js v20.15.0

System Info

System:
    OS: macOS 15.1
    CPU: (16) arm64 Apple M3 Max
    Memory: 22.27 GB / 48.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.15.0 - ~/.nvm/versions/node/v20.15.0/bin/node
    npm: 10.7.0 - ~/.nvm/versions/node/v20.15.0/bin/npm
  Browsers:
    Chrome: 130.0.6723.117
    Chrome Canary: 133.0.6839.1
    Safari: 18.1
  npmPackages:
    svelte: 5.2 => 5.2.0
    vite: ^5.4.10 => 5.4.11

Severity

blocking an upgrade

Additional Information

In .svelte-kit/output/server/entries/pages/_layout.svelte.js the following code is generated in svelte 4:

import { c as create_ssr_component } from "../../chunks/ssr.js";
import { e as escape } from "../../chunks/escape.js";
let Layout;
let __tla = (async () => {
   ...vite-plugin-wasm instantiation code...
 Layout = create_ssr_component(($$result, $$props, $$bindings, slots) => {
    let mountState = "mounting";
    return `<div class="app"><main><div>${escape(mountState)}</div> <div>${slots.default ? slots.default({}) : ``}</div></main></div>`;
  });
})();
export {
  __tla,
  Layout as default
};

Now, svelte-5 will generate:

import { e as escape_html } from "../../chunks/escaping.js";
import { S as pop, Q as push } from "../../chunks/index.js";
let _layout;
let __tla = (async () => {
   ...vite-plugin-wasm instantiation code...
  _layout = function($$payload, $$props) {
    push();
    let { children } = $$props;
    let mountState = "mounting";
    $$payload.out += `<div class="app"><main><div>${escape_html(mountState)}</div> <div>`;
    children($$payload);
    $$payload.out += `<!----></div></main></div>`;
    pop();
  };
})();
export {
  __tla,
  _layout as default
};

The issue is that __tla doesn't complete before _layout is called by render:

_layout is initially undefined. If called before __tla resolves, component (_layout) in 0.js will return undefined leading all the way to Pyramid[0] being the component constructor for the layout page to be undefined. This is why it would sometimes work, because something would eventually process the topLevelAwait.

Why does this work in Svelte 4?

in internal.js the Root function will validate that constructors[0] and constructors[1] have been properly instantiated using validate_component in ssr.js. Svelte 4 has a fallback for this, so things work:

$$rendered = `  ${constructors[1] ? `${validate_component(constructors[0] || missing_component, "svelte:component").$$render(
      $$result,
      { data: data_0, this: components[0] },
      {
        this: ($$value) => {
          components[0] = $$value;
          $$settled = false;
        }
      },
      {
        default: () => {
          return `${validate_component(constructors[1] || missing_component, "svelte:component").$$render(
            $$result,
            { data: data_1, form, this: components[1] },
            {
              this: ($$value) => {
                components[1] = $$value;
                $$settled = false;
              }
            },
            {}
          )}`;
        }
      }
    )}` : `${validate_component(constructors[0] || missing_component, "svelte:component").$$render(
      $$result,
      { data: data_0, form, this: components[0] },
      {
        this: ($$value) => {
          components[0] = $$value;
          $$settled = false;
        }
      },
      {}
    )}`} ${``}`;
  } while (!$$settled);

Compared to the Root function from root.svelte in .svelte-kit/output/server/chunks/internal.js:~516:

  const Pyramid_1 = constructors[1];
  if (constructors[1]) {
    $$payload.out += "<!--[-->";
    const Pyramid_0 = constructors[0];  // <-- There is no guarantee that constructors[0] exists.
    $$payload.out += `<!---->`;
    Pyramid_0($$payload, {
      data: data_0,
      form,
      children: ($$payload2) => {
        $$payload2.out += `<!---->`;
        Pyramid_1($$payload2, { data: data_1, form });
        $$payload2.out += `<!---->`;
      },
      $$slots: { default: true }
    });
    $$payload.out += `<!---->`;
  } else {
    $$payload.out += "<!--[!-->";
    const Pyramid_0 = constructors[0];
    $$payload.out += `<!---->`;
    Pyramid_0($$payload, { data: data_0, form });
    $$payload.out += `<!---->`;
  }

Since these constructors are not validated in Svelte 5, that leads to the error.

This is why I believe wasm won't work: Top level await does not set the component before the component's render() function is called.

Why is this Important?

Because the ES Module Integration proposal for WASM is not supported, the vite docs recommend usage of the vite-plugin-wasm dep. Since Svelte cannot properly compile components that utilize this plugin, the wasm-pack toolchain becomes effectively useless more difficult to use. Examples of a workaround are not available A half-workaround is to use a dynamic import. There are some cases, however, where wasm memory should be loaded alongside the constructor outside of an onMount callback.

Idea for solution

I'd like to readd the validate_component/fallback functionality back to sveltekit in some form. However, I have no clue why it was removed. My hypothesis is that the simplest approach is to adjust write_root with an if check on constructor[0], but not sure what a reasonable fallback would be. Some guidance on this would be super helpful!

@dsgallups dsgallups changed the title Some components, WASM will yield error on running build Calling into WASM will yield error on running build target for sveltekit on svelte 5 Nov 16, 2024
@dsgallups
Copy link
Author

dsgallups commented Nov 17, 2024

This is quite an unusual bug. I noticed that in the output you will find

<!-- This file is generated by @sveltejs/kit — do not edit it! -->
<svelte:options runes={true} />
<script>
	import { setContext, onMount, tick } from 'svelte';
	import { browser } from '$app/environment';

	// stores
...

I have provided this output to the example.

It's actually all over the place, sometimes working when revisiting the page during preview of prod build, but not during others. Fails completely when hosted by a cloudflare worker. Scratching my head on this one.

Edit: The first example randomly just started working, so I have posted a new example that should be more dependable. This uses adapter-auto and fails during the prerendering phase. Notably, the wasm generated files + executable are missing from the compilation output.

@eltigerchino
Copy link
Member

I'm not able to reproduce the same error logs when running build on the reproduction. Can you provide another minimal reproduction and a set of steps to run?
These are the build logs I get:

x Build failed in 1.16s
error during build:
[vite:load-fallback] Could not load /home/chewteeming/github/svelte-bug-wasm-2/src/lib/wasm/pkg/frontend_wasm (imported by src/routes/+layout.svelte): ENOENT: no such file or directory, open '/home/chewteeming/github/svelte-bug-wasm-2/src/lib/wasm/pkg/frontend_wasm'
Error: Could not load /home/chewteeming/github/svelte-bug-wasm-2/src/lib/wasm/pkg/frontend_wasm (imported by src/routes/+layout.svelte): ENOENT: no such file or directory, open '/home/chewteeming/github/svelte-bug-wasm-2/src/lib/wasm/pkg/frontend_wasm'
 ELIFECYCLE  Command failed with exit code 1.

@dsgallups
Copy link
Author

I'm not able to reproduce the same error logs when running build on the reproduction. Can you provide another minimal reproduction and a set of steps to run?

Absolutely! Apologies, had not added the README.md from the first example. For compilation, you'll want to install rust, but what I'll do is upload the wasm pkg file for this

@dsgallups
Copy link
Author

dsgallups commented Nov 18, 2024

Alright, should be available now. You should be able to reproduce this error just by running the build command (no rust compilation required). Thank you!

Edit:

I have also updated the readme with steps if you would like to compile the wasm binary directly. This requires the rust toolchain and the wasm host target. This can be added by running rustup target add wasm32-unknown-unknown after installing the toolchain. Finally, run npm run build-wasm, which will compile your binary + bindings out to $lib/pkg/.

When running dev mode, it should run correctly.

When using adapter-auto, it seems like it will prerender the page, causing compilation to fail during the build step. using a host adapter, like cloudflare, it will succeed in compilation, but will render a 500 server error on page access on the first request. On occasion, the page may improperly load after the initial load, but not when running on cloudflare workers. I'm unsure about the other hosts, but will be doing more research today.

@dsgallups
Copy link
Author

dsgallups commented Nov 18, 2024

FOUND IT!! Did a lot of digging.

Edit: moved most of this comment to issue details

@dsgallups dsgallups changed the title Calling into WASM will yield error on running build target for sveltekit on svelte 5 Any component that requires a top level await is prone to failing on running build target for sveltekit on svelte 5 Nov 18, 2024
@dsgallups dsgallups changed the title Any component that requires a top level await is prone to failing on running build target for sveltekit on svelte 5 Any component that requires an external top level await during compilation is prone to failing on running build target for sveltekit on svelte 5 Nov 18, 2024
@dsgallups dsgallups changed the title Any component that requires an external top level await during compilation is prone to failing on running build target for sveltekit on svelte 5 Components that require an external top level await during compilation fail in svelte 5 Nov 19, 2024
@dsgallups
Copy link
Author

Last time I'm changing the title, wanted to make it more concise.

@benmccann
Copy link
Member

Changing SvelteKit sort of feels like the wrong solution to this as it works fine with the code that is ordinarily generated. If a plugin is generating different code it seems like that plugin's responsibility to make the generated code work.

Perhaps you could use a different import method instead of using vite-plugin-wasm? vitejs/vite#4551 (comment)

Another option would be to write a preprocessor to change root.svelte to use your patch

Fixing the underlying Rollup issue could be another way to proceed and probably the best long-term solution: rollup/rollup#4708

@dsgallups
Copy link
Author

Thank you Ben and everyone else! That totally makes sense. Seems like this isn't an issue with Sveltekit at all then, just that this plugin unintentionally worked as a consequence of the svelte 4 implementation. I will proceed with your recommendations. Much appreciated!

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

Successfully merging a pull request may close this issue.

3 participants