Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Discussion options

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/svelte@4.2.18";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 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>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 asnew 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:

replacenew component withcmp = createClassComponent({component:Component, target }); andcmp = 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.sveltein Widget.sveltein GameBoard.sveltein +page.sveltein layout.sveltein +layout.sveltein 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

You must be logged in to vote

done. solution is in the repo.

Replies: 9 comments 11 replies

Comment options

Please provide a reproduction repository

You must be logged in to vote
0 replies
Comment options

of the system that worked or the one that doesnt?
gonna take me till tmrw

You must be logged in to vote
0 replies
Comment options

@dummdidumm
https://github.com/vivalence/Svelte-Widget-Demo

You must be logged in to vote
0 replies
Comment options

This the version that doesnt work.
with svelte 4 and just a few adjustments this setup worked like a charm.

I am pretty sure the problem is somewhere on the client with how the component is instantiated and mounted.
I think that because thats the thing that changed most and the :server/bundle/Test.svelte component's console.log() statement actually executes and prints to the client console.

only once the component tries to attach to the dom, some problem arrises.

also the bundling is successfull, and from what i can tell, looks exactly like before.
you can view the bundle by visitinghttp://localhost:3000/bundle/Test.svelte once the server is running.

You must be logged in to vote
0 replies
Comment options

The problem is that your setup does not deduplicate the Svelte runtime, it exists multiple times: once for your main app, once for your each of your bundles. If you can externalize them somehow that would be the ideal outcome, since it means you also save on bundle size. If that's not possible for some reason, you have to make sure to call themount method of your bundled component, not the one of your main app. In other words, you likely need a wrapper that does the mounting inside the bundle.

I quickly tried that like this

<scriptmodule>importTestfrom'./Test.svelte'import {mountas_mount }from"svelte";exportfunctionmount(target,props) {return_mount(Test, { target, props})    }</script><script>let {...data }=$props();console.log("Hello World from Test.svelte");console.log("Check these amazing props", data);</script><h1class="text-palette-white">My Test Heading</h1>

(and adjusting the place where the component is instantiated to use the exported mount method)

...but it still produces two versions of the Svelte runtime inside theTest.svelte bundle. I don't know why that is, but if you fix that then that would work (but as I said above ideally you would externalize the whole Svelte runtime).

Converting this to a discussion because this is not a bug in Svelte.

You must be logged in to vote
0 replies
Comment options

for reference:https://stackoverflow.com/questions/65484019/how-can-i-manually-compile-a-svelte-component-down-to-the-final-javascript-and-c
this is where i got most of the implementation that worked in 4 from.

the author adresses the issue of duplicate runtimes headon towards the end:

The problem of duplicated Svelte copies only occurs when the conflicting components are part of the same components tree. That is, when you let Svelte create and manage the component instances, like above. The problem doesn't exist when you create and manage the component instances yourself.

const foo = new Foo({ target: document.querySelector('#foo') })const bar = new Bar({ target: document.querySelector('#bar') })

Here foo and bar will be entirely independent component trees, as far as Svelte is concerned. Code like this will always work, irrelevantly of how and when (and with which Svelte version, etc.) Foo and Bar were compiled and bundled.

Is there any way to maintain this approach with svelte5?

You must be logged in to vote
0 replies
Comment options

@dummdidumm I am chewing my teeth out on this.

I can easily remove the second svelte runtime from the component bundle and get the component down to:

import "svelte/internal/disclose-version";import * as $ from "svelte/internal/client";var root = $.template(`<h1>My Test Heading</h1>`);function Test($$anchor, $$props) {  let data = $.rest_props($$props, ["$$slots", "$$events", "$$legacy"]);  console.log("Hello World from Test.svelte");  console.log("Check these amazing props", data);  var h1 = root();  $.append($$anchor, h1);}export {Test as default};

by adding theexternal: ["svelte", "svelte/*"], to the esbuild config and removing the cache plugin.
but this bundle isnt linked and the imports dont resolve.

Uncaught (in promise) TypeError: Failed to resolve module specifier "svelte/internal/disclose-version". Relative references must start with either "/", "./", or "../".

the author of the stackoverflow above cites this issue in his explanation for why the second svelte instance is needed.

the problems seems to be that we changed from classes to functions. ie, i can no longer instantiate a svelte component withnew.
does that mean i can never update to 5?

You must be logged in to vote
6 replies
@CrackedBeefcake
Comment options

i might have it.

@CrackedBeefcake
Comment options

done. solution is in the repo.

Answer selected bydummdidumm
@dummdidumm
Comment options

glad you found a fix! Curious why you did separate out the mount into a different file? Did mounting within the same file via<script module> not work?

@CrackedBeefcake
Comment options

it worked fine. purely a matter of taste. this pattern fits my overall application architecture way better.

@CrackedBeefcake
Comment options

i'll leave the repo up. the stackoverflow thread was fairly popular and people will want to know at some point.

@negue
Comment options

I too looking for a way to import components lazy remotely (like an external components plugin way), the solution definitely seems to be working (tested the repo, thanks for that example! ❤️ ) - too bad there is no built-in way 😅

Did you maybe figured out how to prevent the additional svelte runtime in those remote component bundles? The current example component bundle is about 24kbs big - sure might not be that huge, but with a couple of remote components this might add up a bit 😬

maybe it would be possible to replace thesvelte/internal/disclose-version imports with somekind of global Property but I don't know how to access those internals, since .. they are internals in first place 😬

Alternative: Otherwise I might need to look into building the Svelte as WebComponents and then import/render them instead (but that would have the full svelte runtime in it as well... so probably not an alternative at all xD)

Comment options

@dummdidumm something popped up.
there is this import$app/environment
if i use it inside a bundled widget I get the following error on build:

✘ [ERROR] Could not resolve "$app/environment"    packages/interfaces/display/components/input/Input.svelte:3:24:      3 │ import { browser } from "$app/environment";        ╵                         ~~~~~~~~~~~~~~~~~~  You can mark the path "$app/environment" as external to exclude it  from the bundle, which will remove this error and leave the unresolved  path in the bundle.

and If i addexternal: ["$app/environment"] to esbuild then i get the following error on the client:
Uncaught (in promise) TypeError: Failed to resolve module specifier "$app/environment". Relative references must start with either "/", "./", or "../".

any idea how to handle this?

You must be logged in to vote
5 replies
@dummdidumm
Comment options

Did this work before or are you only adding this import now?
It's tricky because this is an import that SvelteKit resolves. If you're bundling and make it external (which IMO is the correct first step) then you need to somehow make sure that this code is also looked at by SvelteKit in your main app.

@CrackedBeefcake
Comment options

Its a new import. I've not yet used the$app/environment inside the Widget setup. But it is somewhat important and it would be nice to get that setup.

How do i make sure that SvelteKit looks at code that imported viaawait import(bundleUrl).default(htmlRef, props)?
sounds ... challenging.

of course the Plan B would be to just pass the info as{...props, '$app':...}.fix

but howcould i do it? do you have any idea?

@dummdidumm
Comment options

I think if you want to keep it such that you can dynamically import widgets which have no idea about in which environment they're loaded specifically then youhave to pass it at runtime via props or context somehow.

@CrackedBeefcake
Comment options

the environment is allways sveltekit. but yes, ill just pass required props.

still something to look out for. would be interesting to know what a possible solution looks like.

@dummdidumm
Comment options

there isn't really another solution other than passing it through via context or props, because what$app/environment resolves to isn't known to your components you precompile at that point

Comment options

I've been wanting to solve this problem for over two years and I finally got to the bottom of it 🥳

There are plenty of gotchas one has to overcome to get this realized. I now agree,esbuild is what's up!

I threw up an end-to-end working demo athttps://github.com/mateothegreat/svelte-dynamic-component-engine. Hope it helps everybody!

  • Matthew
You must be logged in to vote
0 replies
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Category
Q&A
Labels
None yet
4 participants
@CrackedBeefcake@negue@dummdidumm@mateothegreat
Converted from issue

This discussion was converted from issue #14293 on November 14, 2024 12:32.


[8]ページ先頭

©2009-2025 Movatter.jp