-
Notifications
You must be signed in to change notification settings - Fork 80
RFC: Reactive assignments #1
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
Conversation
Co-Authored-By: Rich-Harris <richard.a.harris@gmail.com>
text/0000-reactive-assignments.md
Outdated
|
||
It's not clear how this proposal affects standalone components (as opposed to those that use shared helpers). Perhaps it doesn't? | ||
|
||
One consequence of the new way of doing things: Svelte no longer lives in `pkg.devDependencies`, but would instead need to become a `dependency`. Not very On Brand but probably a small price to pay. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I noted in chat, I don't see why this is an issue. It seems like we could just inline these import { whatever } from 'svelte'
statements, and avoid the dependence on svelte
at runtime
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah fair enough. removed. side-note — I was thinking that it probably makes sense to just use Rollup to create the standalone things (with everything except svelte
considered external), rather than the current Heath Robinson nonsense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds way saner than the current mechanisms for this, but I am a bit worried about the compiler's size. Am I going to be forced to download Svelte with a bundled-in version of Rollup? Am I going to be forced to install it as a dependency
even if (for some reason) I don't want it? (Or if I already have a different/newer version of Rollup as a dependency of my app?)
Do we want to make Rollup an optionalDependency, and throw if we try to compile in standalone mode with it unavailable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
definitely a fair concern. also, it would force svelte.compile
to become async. maybe we should cross that bridge later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to drop standalone component compilation as part of svelte.compile
and introduce a separate function that does require Rollup, or maybe some sort of drop-in Rollup plugin or config?
One possibility would be to make this not svelte.compile
's concern at all, and to take care of it in the component-template
rollup.config.js
.
I do see the value of no longer having to worry about this weirdness, but I am concerned that we will be stuck with it until Svelte v4 unless we do something as part of these changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should strongly consider it. I don't think it needs to make it into this RFC (it doesn't need to cover every aspect of v3) but yeah, we should try not to lose track of it. Standalone components are a useful selling point of this framework but they are also a total bloody nuisance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait I just realized, this would also probably let us leave behind all of the non-ESM output modes too. Dang this is sounding real tasty now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh I just remembered the Svelte CLI which people are still using for some reason. I don't know how that fits into this anymore. Do we tell the people who were using it to create standalone IIFEs to get lost?
Will equivalents to component.root & component.options be available? |
text/0000-reactive-assignments.md
Outdated
|
||
This creates consistent behaviour between Svelte components that are compiled to custom elements, and those that are not, while also making it easy to understand the component's contract. | ||
|
||
Of the five **built-in methods** that currently comprise the [component API](https://svelte.technology/guide#component-api) — `get`, `set`, `fire`, `on` and `destroy` — we no longer need the first three. `on` and `destroy` are still necessary. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to have some naming/prefixing convention for built-in methods (or data, like the current options
or root
)? This is a little less easy to type, but it does avoid issues in the future if we want to add something and we need to figure out whether that's a breaking changing change because it might conflict with user-defined items. We can tell users 'don't name anything starting with a $
' but we can't say 'don't name anything that sounds like we might want to use it later potentially'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a bad idea. Vue does $
prefixes. As long as there's no point of confusion with Store, that's probably a good move
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why fire
and on
are removed?
A method's sole purpose may be to emit an event, and rather than pass the Component a props.onFoobar
, something else can listen to Component for that sequence
const greet = new Greeter();
const stutter = str => str.charAt(0).repeat(3) + str;
greet.name = 'Bob';
greet.name = 'George';
greet.on('pretty-girl', stutter);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ My concerns erased. Solved in chat.
There's no way to do that currently in this RFC. We should consider what the use cases are so that we can figure out if it's necessary, or if there's a better way. |
The <svelte:meta bind:state bind:props bind:options bind:root=parent />
<script>
const cool = true
console.log(state.cool)
</script> My original thought was to use |
Yes, I like the I don't think we'd want it to make those names magically available in <script>
import Widget from './Widget.html';
let props;
let options;
let canvas;
onmount(() => {
console.log(props, options, canvas);
});
onprops(() => {
console.log(props);
});
</script>
<svelte:meta bind:props bind:options/>
<canvas ref:canvas></canvas>
<Widget {...props}/> We probably don't want a binding for |
Oh, I'd somehow missed that bit about needing to define the variable with Yeah, that would make TypeScript type waaaaaay simpler. |
text/0000-reactive-assignments.md
Outdated
We can do better, **but it requires something potentially controversial** — a second `<script>` block that, unlike the one we've been using so far, runs a single time rather than upon every instantiation. Let's call it (🐃) `scope="shared"`: | ||
|
||
```html | ||
<script scope="shared"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in chat, I would really like to see this be written as this instead:
<!-- ambiguous "preload" applies to both scopes -->
<script preload>
export function preload({ params }) {
//
}
</script>
<!-- specify "preload" scope -->
<script preload="client|server">
export function preload({ params }) {
//
}
</script>
We already have a "scoped"
vocabulary around styles. Adding another instance of it, for scripts – with different meaning, would be bad IMO.
Another alternative, if one doesn't like the duplicate preload
in such close proximity is to allow default
export since we already know this is a preload
-specific operation.
<!-- ambiguous "preload" == both -->
<script preload>
export default function ({ params }) {}
</script>
<!-- OR, contextual "preload" -->
<script preload=client>
export default function ({ params }) {}
</script>
<!-- OR, ambiguous attribute, specific functions -->
<!-- ... this might be problematic (for resolving dependency imports / tree-shaking?) -->
<script preload>
export function client({ params }) {}
export function server({ params }) {}
</script>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually think the unification of the different types of static stuff that can be attached to a component is nice - and being able to make preload
just be a convention is great. (If we want to have different client/server preloads, could that just be part of Sapper's convention?)
That said, I am really not fond of the term scope='shared'
that's being proposed to indicate this. I don't have a better suggestion currently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm inclined to agree with @Conduitry — preload
is an awkward anomaly. The less we have those special cases the more stable a platform for innovation the framework becomes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH I'm not sure what either of you are saying 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's nothing special about preload
— it doesn't have any defined behaviour. It's just a function that will get attached to both client and server components as a static method, and it gets that privileged treatment because Sapper needs it.
But ideally Svelte shouldn't have features that exist just for the sake of Sapper — someone with different opinions about how to build an app framework with Svelte should be able to do so.
This RFC, among other things, is about stripping Svelte down to its bare minimum — two methods instead of five, one way of making values available to the markup instead of (3? 10? depends which ones you count).
Since we need some way of including code that runs once per app (instead of once per component), we need to have the second <script>
tag. Since we have that second script tag, we may as well use it for preload
rather than it being its own special thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
100% agreed - apologies if something I said pointed to the contrary. I was merely just renaming scope
Having extra script tags also allows new app frameworks to define new boundaries for new behaviors, especially if/when Svelte users are accustomed to having them already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok, I thought you were implying that the second <script>
was purely for preload
. Certainly in the market for alternatives to scope
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but in Sapper apps that would/could be the place for it... Standlone, hoistable preload functions that the Svelte compiler can rip out (safely) per-bundle-type and put those snippets wherever Sapper tells it to put them.
Svelte just needs to be aware of/handle more than 1 script tag
text/0000-reactive-assignments.md
Outdated
|
||
Since there is no longer a `this`, we need to reconsider this approach. At the same time, the `get`/`set`/`on`/`fire` interface (designed to mirror the component API) feels slightly anachronistic in light of the rest of this RFC. A major problem with the store in its current incarnation is its lack of typechecker-friendliness, and the undocumented hoops necessary to jump through to integrate with popular libraries like MobX. | ||
|
||
I'm not yet sure how the store fits into this proposal — this is perhaps the biggest unknown at this point. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if I see the problem?
<h1>Hello, {{name}}!</h1>
<script>
import { ondestroy } from 'svelte';
import { Store } from './my-store';
// setup code
export let name = 'world';
const store = new Store({ name });
const listener = store.on('state', ({ current }) => {
// something changed... changes this component's "name"
name = Math.random().toString(36).slice(7);
// this component now updates
});
// cleanup
ondestroy(listener.cancel);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what that loses is being able to have the store Just Be There without having to manually import it in every component, with the different relative paths that will entail.
Perhaps we could use @TehShrike's <svelte:meta bind:XXX/>
idea for this?
I'm not certain we want to maintain a magic way of referring to store properties in templates (the $
-prefixed identifiers).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I'd like to get rid of magical store presence. Not everything needs to connect to a/the store or have access to it.
Doing it this way also lets a develop to define a custom top-level store (or multiple) and import the ones they want within any given component.
As for $vars
being accessible – I think that can still be achieved if we wanted to. If the compiler can link up foobar
it should be able to link up $foobar
too?
But, personally, I'd prefer to do away with magic $vars
and have the store update the variables I've declared & wired up during setup. For example, the export let name
being updated within store listener.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But, personally, I'd prefer to do away with magic $vars and have the store update the variables I've declared & wired up during setup. For example, the export let name being updated within store listener.
Strong disagree on this sentiment from me. We're using stores heavily and the ability for children to inherit parent stores and transparently reference $vars
is super valuable to our sanity and iteration speed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair 😆 For context, I use store in less than 10% of my components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm with @tivac on this one.
The (relatively) simple & consistent (ie. same api) implementation of Store in v2 won a lot of people over imo - including me! By all means knock yourself out with other stricter store implementations etc but in some parts of the real world it is very useful indeed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
text/0000-reactive-assignments.md
Outdated
<Bar {...subset()}/> | ||
``` | ||
|
||
This is inelegant, but I believe the inelegance would affect a small minority of components. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're using special prefix $
like @Conduitry suggested, this could just be $props
which looks 10x better.
<Foo {...$props} />
<script>
import Foo from './Foo.html';
import Bar from './Bar.html';
const subset = () => {
const { thingIDontWant, ...everythingElse } = $props;
return everythingElse;
};
</script>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My idea with the $
prefix was that it was for externally-visible methods/data on the component instance. For making stuff available to code inside the component, I'm currently favoring something like @TehShrike's <svelte:meta bind:XXX/>
idea over that of magic globals.
Sorry everyone, I did a bad git. I've opened a new PR for this RFC — #4. I think most of the comments in this thread have been addressed in any case; the big TODO is Store, which we can discuss over there. |
Moved to #4
A proposal for simpler, smaller components with a new approach to reactivity.
View formatted RFC
See also the original issue sveltejs/svelte#1826