Open webJose opened 1 month ago
My guess is that the microfrontends each come with their own Svelte runtime. That means the effects listen to sources being read inside them but never notice the sources from the other runtime, because those set the corresponding variable in runtime A, not B.
So the solution is to somehow deduplicate the Svelte runtimes. I'm not sure how to do that though, probably some Vite config Fu required.
Thanks for dropping by!
I tried doing what one would do for React: Import Svelte from a CDN and mark it external in Vite. This of course, requires that the project be compiled and a few other adjustments to the repository I shared. The end result is this:
As seen, the class using $state
loaded and ran the initial effect, but the component could not be mounted. If not an imposition, could I ask that you help me understand if Svelte v5 can be de-duped or not?
Ok, I think I successfully set the project up for de-duplication of Svelte using unpkg.com. It is the same repository, but in the branch named JP/SvelteExternalized. The project runs fine in serve mode (npm run dev
), but the end result is the same as the previously shown attempt.
Furthermore, even though I think I successfully de-duped Svelte, the problem in reactivity remains. 😢
Continuing from here. Even though both instances might be technically importing the same module now, they do not share the reference to the same value because of the isolation between processes on different ports...and thus their reactivity graphs (and everything else!) are separate.
Hello, @kwangure and thanks for dropping by. It seems that isolation in browsers is based on the context, and since this is all in the same window, same document, there should be no isolation happening. Or am I misunderstanding how isolation works? For example, there's no isolation going on with my module "@mfe/utils". I have verified that the constructor of the class only runs once. Since both MFE's can get a hold of the class instance, this can only mean it is the same instance, therefore the same module, even when "mfe1" is consuming the module in a different port.
Anyway, by extending the logic, it seems that externalizing the "svelte" module alone is not sufficient to do the trick. At this point I would absolutely loved it if core maintainers could come forward and extern their opinion on what it would take to make this scenario work.
My uneducated alternatives are, in my mind:
For #2, it would be a requirement to be able to reactively work in any number of MFE's, not just "one or the other", or "home graph + 1". For it to be useful, it should work in an arbitrary number of graphs.
That's what I can think about, again, using whatever little knowledge of what's really going on here.
Thank you all in advance for the time you lend me reading this.
Okay. You're right. I assumed that from inspection but single-spa
does some JSFu to scope all JS to root
.
I've gone around removing things binary-search style in your repro. Removing vite-plugin-externalize-dependencies
seems to restore reactivity inside root
. I haven't looked into it further...but I'll leave you with things I'd test just from the top of my head:
vite-plugin-external
and vite-plugin-externalize-deps
dedupe without introducing issuesroot/node_modules/svelte
)Hello again, @kwangure. I hadn't realized that root
had lost reactivity after I introduced externalization. My oversight. Thanks for pointing it out.
The plug-in vite-plugin-externalize-dependencies
(or similar ones) are only needed when running Vite in serve mode. Whether or not my choice of plug-in was incorrect is largely unimportant because building and previewing the projects produce lifecycle_outside_component runtime error. I think we should not focus on the ability to run in serve mode for the time being, because after all, what is needed is that the built product functions. Let's put a pin on this item for later.
For your second point, this is not something I can assert, unfortunately. The condition "satisfactory" in this context can be defined as: Both micro-frontends end up using the same reactive graph. I don't have the skill to assert this condition and is probably the key point where I need help from savvy and experienced developers that know the Svelte code, that know how to assert this condition.
What I can say about your second point is that I performed the same process that is done to de-duplicate React and React-DOM.
Points 3, 4 and 5 all relate to running the projects in serve mode, which is unimportant as long as the built products cannot function properly, so I guess all those have a pin in them as well. ðŸ˜
So, unfortunately and very quickly, I arrived to point 6.
If people are willing to help with this, what we need is to focus on the built product: Can Vite de-duplicate Svelte when building the micro-frontends? If not, why? Once the reasons are known, I suppose the Svelte core team can decide whether or not a potential fix/change/upgrade/refactor is feasible and whether or not they are willing to pursue.
Once the built product works, we can discuss making it work while in serve mode, which probably has a different set of challenges.
My current thinking is that Vite can't properly handle this during dev mode because it transforms the imports to svelte
etc so an import map - which would be solution here - has no possibility of actually kicking in. I think this needs https://github.com/vitejs/vite/issues/6582 implemented. Until then it seems there's some workarounds mentioned in that issue, and/or you can add external
in the rollup plugin options within Vite and rely on pnpm build + pnpm preview
.
Yes, it would be great if that Vite issue could be implemented for sure for development mode. But that leaves us with the lifecycle_outside_component error when the projects are built and run. This should work, I think, since externalization has been a thing in rollup for a long time.
If time permits, can it be explored why the built de-duplicated version throws this error?
@webJose i was just playing around with something similar (not exactly the same) and i think i kinda found a solution. You should externalise the dependencies also on your main project and rely on import maps. However note that the import maps should not be bundled.
<script type="importmap">
{
"imports": {
"svelte": "https://esm.sh/svelte@next?no-bundle",
"svelte/": "https://esm.sh/svelte@next&no-bundle/"
}
}
</script>
this is the import map I'm using and with this i'm able to import a component like this
import "svelte/internal/disclose-version";
import * as $ from "svelte/internal/client";
var on_click = (_, count) => $.update_prop(count);
var root = $.template(`<h1> </h1> <button> </button>`, 1);
export default function _unknown_($$anchor, $$props) {
let count = $.prop($$props, "count", 7);
var fragment = root();
var h1 = $.first_child(fragment);
var text = $.child(h1);
$.reset(h1);
var button = $.sibling(h1, 2);
button.__click = [on_click, count];
var text_1 = $.child(button);
$.reset(button);
$.template_effect(() => {
$.set_text(text, `Hello ${$$props.name ?? ""}`);
$.set_text(text_1, count());
});
$.append($$anchor, fragment);
}
$.delegate(["click"]);
directly in the browser and hydrate an index.html page. Give this a try for your project and let me know.
Hello, @paoloricciuti and thanks for the note. I'll maybe try a thing or two out, but in all honesty, that looks very scary! Hehe. Even if I could mount a component like that, it is not sustainable. This kind of breakthrough is best consumed by core member teams. Maybe you guys can figure out what needs to be done in Svelte itself so it can be externalized while writing normal code, normal components, normal state stores.
Hello, @paoloricciuti and thanks for the note. I'll maybe try a thing or two out, but in all honesty, that looks very scary! Hehe. Even if I could mount a component like that, it is not sustainable. This kind of breakthrough is best consumed by core member teams. Maybe you guys can figure out what needs to be done in Svelte itself so it can be externalized while writing normal code, normal components, normal state stores.
You can also host your own unbundled version of svelte if that's what scares you but the gist is the same. It needs to be the same module that is imported by both projects. And this is also exactly what you tried here
Ok, I think I successfully set the project up for de-duplication of Svelte using unpkg.com
But with a cdn that supports unbundled libraries.
We also opened an issue there to see if they can bundle it correctly in the bundled version.
I'll try to see if there's a decent way to externalize svelte but still build it with your main package so that you can use a single instance in your main project.
Describe the bug
Hello, I describe this in #13224 and in short, a
$state
value doesn't react when it is "foreign" to the project.I made a reproduction repository where one can see that reactivity works within the context of the project that creates the state, while simultaneously not working in the other project.
Reproduction
Example repository
Logs
No response
System Info
Severity
annoyance