Skip to content

Migrating Remotely Bundled Component setup from Svelte 4 to 5 #14293

@CrackedBeefcake

Description

@CrackedBeefcake

Describe the bug

I have a server on port 8000 that bundles and hosts svelte components, which are imported into a sveltekit app running on port 5173. This setup used to work fine on svelte 4, but the migration isnt as straight forward as i'd hoped.
Let me show first how the old setup used to work server and client side, and then what ive tried so far.

The server had a esbuild bundler, which had its outputFiles hosted on a http get request.

export default async function bundle (entry) {
  const build = await esbuild.build({
    entryPoints: [entry],
    mainFields: ["svelte", "browser", "module", "main"],
    conditions: ["svelte", "browser"],
    target: "es6",
    format: "esm",
    write: false,
    treeShaking: true,
    sourcemap: config.isDev ? "inline" : false,
    minify: true,
    bundle: true,
    outdir: dirname(entry),
    outExtension: { ".js": ".svelte" },
    plugins: [
      cache(svelteImportMap),
      sveltePlugin({ },
        compilerOptions: {
          filename: basename(entry),
          css: "injected",
        },
      }),
    ],
  });
  return build.outputFiles;
}
const svelte = "https://esm.sh/[email protected]";

const svelteImportMap = {
  importmap: {
    imports: {
      svelte,
      "@vivalence/ui": `../../../../packages/ui/mod.js`,
      "svelte/store": `${svelte}/store`,
      "svelte/motion": `${svelte}/motion`,
      "svelte/internal": `${svelte}/internal`,
      "svelte/internal/disclose-version": `${svelte}/internal/disclose-version`,
    },
  },
};

....
  // serve:
  bundler.serve = () => async (ctx) => {
    const path = join(dirname(input.path), ctx.params.filename);
    const bundle = await bundler(path);
    if (bundle) {
      ctx.response.body = bundle;
      ctx.response.type = "application/javascript";
    }
  };

This was consumed by the client in two steps.
The Widget functioned as the Sveltekits Universal interface/ loader.

<script>
  import Component from "./Component.svelte";
  import { onMount } from "svelte";

  export let bundle;
  export let data;

  let component = null;

  async function fetchAndCompileAST() {
    const response = await locals.call.raw(bundle, null, { method: "GET" });
    const text = await response.text();
    const blob = new Blob([text], { type: "application/javascript" });
    const url = URL.createObjectURL(blob);
    const { default: Widget } = await import(/* @vite-ignore */ url);
    component = Widget;
  }

  onMount(() => {
    fetchAndCompileAST();
  });
</script>

{#if Component}
  <Component this="{component}" {...data}  />
{:else}
  <p>Loading component...</p>
{/if}

The referenced Component:

<script>
  import { onDestroy } from 'svelte'

  let component
  export { component as this }

  let target
  let cmp

  const create = () => {
    cmp = new component({
      target,
      props: $$restProps,
    })
  }

  const cleanup = () => {
    if (!cmp) return
    cmp.$destroy()
    cmp = null
  }

  $: if (component && target) {
    cleanup()
    create()
  }

  $: if (cmp) {
    cmp.$set($$restProps)
  }

  onDestroy(cleanup)
</script>

<div id="game-container" bind:this={target} />

The component thats gettings built by the server is currently an empty demo.

<script>
  console.log("Hello World from Component");
</script>

<h1 class="text-palette-white">My Heading</h1>

this setup worked like a CHARM! given, its a bit much, but once i had figured it out, it never had any hickups.

but as you can see, it relied on instantiating the components as new component classes.

now ive updated the build svelte dependency to 5.1.9 which is the same my main sveltekit app uses.
on the client ive tried a few different approaches like:

replace new component with cmp = createClassComponent({component:Component, target }); and cmp = mount(Component, { target, props: payload,});

but nothing works.
I get various error messages like:

Uncaught TypeError: Cannot read properties of undefined (reading 'call')

	in Component.svelte
	in Widget.svelte
	in GameBoard.svelte
	in +page.svelte
	in layout.svelte
	in +layout.svelte
	in root.svelte

    at get_first_child (operations.js:77:28)
    at template.js:48:50
    at Flashcards (Flashcards.svelte:3:44)
    at render.js:228:16
    at update_reaction (runtime.js:317:53)
    at update_effect (runtime.js:443:18)
    at create_effect (effects.js:125:4)
    at branch (effects.js:346:9)
    at render.js:210:3
    at update_reaction (:5173/.vite/deps/chunk-6CDLSX2F.js?v=6ab23a08:1714:23)TypeError: Cannot read properties of undefined (reading 'call')
    at get_first_child (operations.js:77:28)
    at template.js:48:50
    at Flashcards (Flashcards.svelte:3:44)
    at render.js:228:16
    at update_reaction (runtime.js:317:53)
    at update_effect (runtime.js:443:18)
    at create_effect (effects.js:125:4)
    at branch (effects.js:346:9)
    at render.js:210:3
    at update_reaction (runtime.js:317:53)

any help would be welcome. i am very stuck and have no clue what levers to try next.

Reproduction

above

Logs

No response

System Info

System:
    OS: macOS 14.5
    CPU: (8) arm64 Apple M1 Pro
    Memory: 161.06 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.17.0 - ~/.nvm/versions/node/v20.17.0/bin/node
    Yarn: 4.5.0 - ~/.nvm/versions/node/v20.17.0/bin/yarn
    npm: 10.8.2 - ~/.nvm/versions/node/v20.17.0/bin/npm
    pnpm: 9.11.0 - ~/Library/pnpm/pnpm
  Browsers:
    Chrome: 130.0.6723.117

Severity

blocking an upgrade

Metadata

Metadata

Assignees

No one assigned

    Labels

    awaiting submitterneeds a reproduction, or clarification

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions