-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Update either the compiler or runtime to allow independently built components to share runtime functions #3671
Comments
Edit: A proof of concept implementation can be found in this comment: #3671 (comment) Another approach to this that would be much simpler to implement would be for the functions in the After that the main problem is the I'm guessing this is purely to avoid the error that's thrown in Those runtime files could then export either the existing I realise that this probably isn't the solution some will want because I used the word "global", but perhaps it could be an option that's turned on by a switch/env var/whatever, like I also don't see the problem with a global store for runtime functions since the goal of this is for stand-alone components to all use the same functions in the end. It does mean that stand-alone components can't choose to be built without internals, but the issue here isn't file size, (otherwise I'd be building everything in the same project at great expense to flexibility), rather the issue is individual components not using the same runtime functions. Having said that, there could be an option for "plugin" style components that just define runtime functions as whatever is on I've altered most of the runtime files in a local build of Svelte and it's working great so far without any obvious problems. It solves literally every problem I've had so far with pre-built standalone components, including a few that there are open issues about, like components erroring on There is one error about not being able to tree shake Could anyone let me know of any downsides to this approach that don't unnecessarily bikeshed on globals or unused Svelte internals (that's a trade off I was making anyway) ? |
I would also appreciate this being done. Currently making an application with modular extensions and I have also come across this. |
As an advocate for wanting to expose and use global runtime internals between independently built components I've taken it upon myself to at least prove how easily this can be done and compared final file size in a simple project. You can see the branch in my fork here: https://github.com/jakelucas/svelte/tree/optional-global-runtime-internals (no longer available) And you can see the comparison to the master branch I forked here: https://github.com/jakelucas/svelte/compare/master...jakelucas:optional-global-runtime-internals (no longer available) First of all, some rough measurements in a basic project. Using the current version of plain Svelte my With my changes for optional global runtime internals turned off my project's With my changes for optional global runtime internals turned on my project's The changes I've made make use of the rollup replace plugin to replace This means the global runtime internals are optional and are an opt-in feature so that users who don't care about using independently built components together don't pay the price unnecessarily. This also means that unless a component was built using The main changes to the code base are all in the By checking for a global function before exporting it's possible to make sure that all independently built components use the same runtime functions and therefore the same module level variables that track state. This fixes all of the problems (that I know of) relating to separate copies of the runtime being used. The only downside to exporting global functions optionally is that the export code has become a little messier, but since it doesn't affect too many files/functions I don't think this is actually a problem in the grand scheme of things. The The global object is returned from a new I don't believe it's up to Svelte to support independently built components working together from different versions and it's up to the application developer to make sure versions between components match up. All normal unit tests pass when global internals are both turned on or off. The only test that doesn't pass is the Lastly, I don't know much about TypeScript, so I added the following lines to declare const global: any;
declare const window: any; If there's a better way to do this please let me know. I've been using this patch in my project and it's fixed all of the problems I've run into without any need for change in the actual compiler. Since it's opt-in I believe this is a nice compromise to solving the problem as it doesn't affect users unless they explicitly say they want to use it whilst only making the export code in a few files slightly messier; otherwise Svelte functions as it always has. Using this solution enables applications that don't know what components they will be using until runtime (because the code doesn't exist in the project in the first place, for example) to use pre-built components from the same version of Svelte without having to be recompiled and bundled up again. It's true that a host project importing externally built components will be wasting some bandwidth on Svelte runtime internals that might never be used in the host project, and will never be used in the external components, but that's a trade off those kinds of projects have already decided to make anyway. In my changes I may have even been a little overzealous in checking for globals for functions during export. It's possible that not all of the functions I chose to export as global would actually need to be exported in that manner, but I wanted to try for the worst case. What does everyone think to this solution? |
Could you achieve the same thing by telling rollup of the component library that svelte is an external dependency?, which would leave all the require('svelte/internal') calls in the component library source to be resolved when the main host project is built |
The problem with both of these approaches is that the compiled svelte components are dependent on the API shape of svelte internals which is not public or stable. eg a component library built with svelte 3.7 would not work correctly with a host using svelte 3.12 |
I think the main conflicts happen when a parent component assumes a child component is part of the same runtime instance and calls the shortcut methods (eg $$render instead of .render). Maybe what is needed is for the compiler to emit different construction logic for subcomponents from different svelte instances ( although the cost might not be worth the gains ). |
I'm not sure at all how to configure rollup so I don't know about the first question. The main point of this though is to not need to rebuild the host project in the first place. My use case for this is essentially to have a host project that can pick up built JS file components from a folder and use them at runtime without having to recompile with the knowledge of all possible components. As for depending on the API shape that's why I added the basic version check to emit warnings. That version check is just a proof of concept though and could be strict enough that it requires an exact major and minor version match to not emit a warning. I don't think it's up to Svelte to support this use case between different Svelte component versions and that's up to the application developer to keep in sync. It's also why I think it's best as an opt-in (in addition to the tree-shaking). The reason for this change is because when independent components use the same version of Svelte I don't see why it shouldn't just work, given that they all use the same functions anyway. |
"host project that can pick up built JS file components from a folder and use them at runtime" not sure how this would work?, how can you instantiate them from host svelte project without knowing them? |
In my particular case the host project just walks through all of the files in a set folder. I build both a All I do is import the file using I then store the returned module in an object with a key based on the file path. Those keys are then looked up at runtime based on data saved in a database and are instantiated in both SSR on the server and in the browser at runtime to generate content. The reason I want this is so that I can have multiple host projects set up with completely different component sets. This even allows a client to provide their own pre-built components without needing the host project source as well. |
you might be able to do this without touching the svelte compiler. import * as SvelteInternals from 'svelte/internals'
(window || global).__svelte_internals = SvelteInternals then in the rollup config for your client.js and server.js you would globals: {
'svelte/internal': '__svelte_internals'
} _ |
Thanks for the suggestion; I had no idea you could do that. I've tried it in webpack using this in the external component projects: externals: {
'svelte/internal': 'svelteInternal',
}, And in the host project I added the suggested code to the entry point. I have two entry points, one for client and one for server, so I used: import * as svelteInternal from 'svelte/internal'
window.svelteInternal = svelteInternal and: import * as svelteInternal from 'svelte/internal'
global.svelteInternal = svelteInternal It seems to work just fine and is obviously the better and prefered solution. I'm glad I don't have to argue for the runtime to be altered now 👍 I'll leave this issue open for a few days to see if I run into any issues, but if everything works out I'll close it and stick with your suggested solution. Assuming this solution doesn't bring up any problems perhaps this should be documented? Thanks! |
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: /^svelte/ to mark every Svelte related import in my external projects as an external. Then in the host project I provide everything on import * as svelte from 'svelte'
import * as svelteAnimate from 'svelte/animate'
import * as svelteEasing from 'svelte/easing'
import * as svelteInternal from 'svelte/internal'
import * as svelteMotion from 'svelte/motion'
import * as svelteStore from 'svelte/store'
import * as svelteTransition from 'svelte/transition'
const g = typeof window !== 'undefined' ? window : global
g.svelte = svelte
g['svelte/animate'] = svelteAnimate
g['svelte/easing'] = svelteEasing
g['svelte/internal'] = svelteInternal
g['svelte/motion'] = svelteMotion
g['svelte/store'] = svelteStore
g['svelte/transition'] = svelteTransition I'll close this issue now since this is a much nicer solution than trying to change the internals. |
If I'm understanding correctly - is the suggestion that any time an app wants to use 3rd party svelte components the dev would need to manually add the boilerplate above:
in addition to the library looking for and assuming these globals? If so, this still seems pretty fragile and feels like the issue is unresolved and should remain open. |
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
|
Thanks for the detailed writeup and explanation @jakelucas 👍 |
Relying on the compiled JavaScript of the JSON Tree component caused runtime errors. The best explanation I’ve read is here: <sveltejs/svelte#3671 (comment)>
* fix(debug): Import JSON tree Svelte component to fix runtime error Relying on the compiled JavaScript of the JSON Tree component caused runtime errors. The best explanation I’ve read is here: <sveltejs/svelte#3671 (comment)> * test(debug): Mock `svelte-json-tree-auto` in tests `svelte-json-tree-auto` causes errors in tests (see #781). I think this might have something to do with the fact that the tree has circular dependencies, which perhaps the Jest transformer can’t handle. As it’s a third-party library, we can probably do without it during tests, and this change replaces it with an empty Svelte component for tests. * test(debug): Add interactivity test for Debug panel
@jakelucas (whoever you are?), Thanks for this writeup. Really opened up my options. Here is the way I handled it: Create shared Svelte RuntimeSo essentially, I wanted to maintain the paths to the export * from 'svelte'; Also, note that I included each file as an entry point so that Vite would output a file for each entry point: lib: {
entry: [
'src/svelte.js',
'src/animate.js',
'src/easing.js',
'src/internal.js',
'src/motion.js',
'src/store.js',
'src/transition.js'
],
...
} Now my
Plus, whatever other files the bundler produces that are shared between these files. Use the
|
Edit: A proof of concept implementation can be found in this comment: #3671 (comment)
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 inpackage.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.
The text was updated successfully, but these errors were encountered: