Update either the compiler or runtime to allow independently built components to share runtime functions
See original GitHub issueEdit: A proof of concept implementation can be found in this comment: https://github.com/sveltejs/svelte/issues/3671#issuecomment-541271339
Is your feature request related to a problem? Please describe. The lack of this feature leads to bugs in stand-alone pre-built components which are being imported into another Svelte code base.
Bugs are always related to runtime internals because a stand-alone component is always built with its own version of the internals.
For example, it’s impossible to use the context API in a pre-built stand-alone component when it has been included in another Svelte code base. Another example would be bind:this
.
I understand that the "svelte"
key in package.json
exists to allow you to import components from other projects at build time and have them packaged up to use the same internals.
Unfortunately there are some applications that can’t know what components they will import until runtime and can’t build and package up any old Svelte component they find on the fly to use the same internals, so this is a separate issue.
Describe the solution you’d like Edit: An easier to implement solution based on just altering runtime exports is described in my next comment in this issue.
First of all, I understand this isn’t a trivial change, but that being said, I feel like this use case is common enough that it would be worth adding an option to the compiler to compile a component without any internals and for the component to expect to be given an internals
object, or something of that kind, which it can then use in place of having its own copy of the internals.
In other words, I’d like to have a compiler option like plugin: true
, or something like that, which signals that this component must be used within a project that has some internals to pass on to it.
I haven’t looked at all of the code in the runtime, but I don’t see why this shouldn’t be possible, since most of the internals that cause the problems are things like module level array or object references that get imported into various files to push functions etc. onto.
If as an architectural decision Svelte is ok with module level references being imported all over the place, then I don’t see why we shouldn’t just allow a component to take a reference to those same objects if they are optionally passed into a component.
The main problem I see with this is that as Svelte versions move on the “plugin” component and the main code base may drift apart to the point where they are no longer compatible, but this seems like a detail that application developers should be keeping in mind. I think it’s ok for Svelte to simply provide the option to pass internals on and after that’s happened it’s the application developer’s responsibility to keep things in sync.
I can also see there being a problem with dead code removal in the final build of the main application, since it would have to pass through all of the internals. Given that this is useful for applications that don’t know what components they’ll be using as runtime that’s just one of the trade offs, but perhaps that could just be an option as well?
Describe alternatives you’ve considered
I’ve considered just avoiding the issues by not using features like bind:this
or the context API in “plugin” components, but I can’t shake the feeling that this should just work, given that I can’t see any good reason it shouldn’t (admittedly in the limited time I’ve spent looking at the Svelte code base).
Other than that I’m not sure of other ways that internals could actually be shared. I understand that this could be a change that makes the code base too complex, and if that’s the case I’m fine just not using the features that aren’t supported, but I wanted to at least throw the idea out there.
How important is this feature to you? It’s extremely important to me, as someone who builds applications that can’t know in advance what components it will be using.
I can work around it by just not using those features, but it’s frustrating knowing that I could use those features if Svelte would just (optionally) share its internals a little more.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:3
- Comments:15 (5 by maintainers)
@bestguy
No, this isn’t the case, and the use case described in this issue isn’t a common one (or shouldn’t be at least).
There are 3 use cases for components as far as I can see.
For one there should be no problems at all, for another there should be no problems with one caveat, and for the last one (and the caveat) all problems are solved by sharing internals as global (provided you can guarantee all components are built with the same Svelte version).
Svelte project
import
s another Svelte project (.svelte
files)In this case the library just adds a
"svelte"
key to theirpackage.json
file and the host project bundles everything up as required. Because everything is bundled up in the same project all Svelte components share a single copy of the internals and everything works as it should do.Non-Svelte project loads pre-compiled JS component files
In this case the host project just loads the pre-compiled JS component file and uses it as a normal JS module. Pre-compiled JS components from Svelte get bundled with their own copy of the internals so everything works out of the box.
There is a caveat to this which I’ll get to soon.
Svelte project loads pre-compiled JS component files
In this case the host project is using Svelte, so it’s bundled with a copy of the internals, as it should be, but this time, for whatever reason, the library’s
.svelte
files are not beingimport
ed and are being loaded via the pre-compiled JS files.This is fine until the host and the library need to interact.
Because the host has its own internals and the library was understandably compiled with its own internals as well, when it comes time for the host Svelte components to do things like mounting/unmounting the library components communication between them breaks down. This is because there are two copies of the internals that both have their own module level tracking variables that are updated as things happen on the page. If a library sets an
onMount
function in its own internals then the internals from the host project won’t be able to see the function and won’t know to run it. This kind of communication breakdown between runtime internal copies also manifests as errors such asoutros.c
not being defined and things like that.The solution to this is the one provided above where the host project provides internals as a global and the library just expects them to exist so it can use the same ones. This use case is quite niche since if you have a host Svelte project you would normally just
import
the library and be done with it, and Svelte projects that need to import pre-compiled components (like my project) are set up in such a way that special logic is required to make everything work anyway, so making globals available isn’t exactly a big deal and it’s a trade off those kinds of projects just have to make.Non-Svelte caveat
Now, onto the caveat for the second use case, a non-svelte project using pre-compiled Svelte components as JS files.
If a non-svelte project is using a pre-compiled Svelte library everything will work fine because the Svelte library will have its own runtime internals copy to use. That is, until you try to mix multiple libraries together. If you take Svelte library A and Svelte library B which have both been pre-compiled separately from each other then they will have separate internals. As soon as you try to pass a component from one library into the other there will be a communication breakdown between the internals again and the same errors as a Svelte project loading JS components will appear. This is because it’s essentially the same thing even if the main host project isn’t Svelte; it’s still just one Svelte component trying to use another independently built component with two or more copies of the internals floating around.
There is also of course the more obvious problem of two independently built components using different version of Svelte as well.
As long as library A and B in that scenario never mix then there will never be a problem.
In other words, problems only occur once you start trying to mix two or more copies of the runtime internals.
It looks like you’ve been directed here by @telemmaite from an issue here: https://github.com/bestguy/sveltestrap/issues/71
They describe their problem here: https://github.com/sveltejs/svelte/issues/3448#issuecomment-530331354
I tested out SvelteStrap on my machine and I couldn’t reproduce the errors they were getting, because as expected you’re correctly setting a
"svelte"
key in yourpackage.json
.But as you can see from this comment (https://github.com/sveltejs/svelte/issues/3448#issuecomment-541391289) it is possible that even if the main host project is a Svelte one and a library is being
import
ed as recommended, if the main project isn’t set up correctly then two or more copies of the internals may still be bundled by mistake, which is the issue I assume they’re having.I thought it may be related to your recommendation to use
npm --save
rather thannpm --save-dev
, because there’s no need to save as a normal dependency with Svelte since everything is bundled up at compile time, but even using--save
in webpack it still worked fine.Since your pre-compiled version of SvelteStrap seems to be a single JS file where everything is bundled as a single project it means all of your components should be sharing internals as well, so a non-svelte project I assume should have no problem using your library (unless they try to pass other pre-compiled Svelte components into it).
Because of all of this I’m more inclined to believe that if @telemmaite’s project is a Svelte one, and they are
import
ing components as they should do, then something is probably wrong with their project set up that it’s bundling two or more separate copies of the internals which leads to their errors they described in the issue linked earlier.Why this issue is closed
I don’t think there’s a need to reopen this issue because the use case described is quite niche and there’s only so much support Svelte would be able to provide for it anyway.
One of the reasons for the way the Svelte project has been written, as far as I understand, is to allow for as much tree-shaking as possible to cut down on bundle size.
Officially supporting the use case described here would mean having to always build with the entire runtime bundled and available as a global all the time, since the host project can’t know which functions will be required by any random components being loaded. This would go completely against the grain of the runtime etc. being built in a way where tree-shaking is as easy to provide as possible and would mean users that don’t need all of the runtime will have to load it anyway.
Because of that my first suggestion was to be able to turn globals on at build time with an environment variable, but then @halfnelson suggested using the
externals
orglobals
keys in webpack/rollup and just attaching to the global object manually instead.Since a project using components as described by this issue would have to explicitly turn on globals in the first place, the suggestion to just do it manually is much better since it’s barely any different as far as setup/boilerplate goes and avoids needing to change the project’s internals just to facilitate it.
In addition to that problems will come up if two separately built component’s versions differ too much. If one component expects to be able to provide something for another component where the versions differ a lot then it’s possible that things will break anyway due to both components possibly not expecting the same behaviour.
Because of all this I can see why it’s not an officially supported use case and why the
"svelte"
key for Svelte projects exists.Well after using the method suggested by @halfnelson it’s clear to me that it will work just fine, so I’ll close this issue now rather than waiting.
In my case I’ve taken it to the extreme at the cost of only a couple of kilobytes in the browser.
Basically I’ve change the webpack
externals
config to:to mark every Svelte related import in my external projects as an external.
Then in the host project I provide everything on
window
orglobal
like this:I’ll close this issue now since this is a much nicer solution than trying to change the internals.