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

Appearance settings

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

Merged
merged 34 commits into from
Nov 3, 2018
Merged

RFC: Reactive assignments #1

merged 34 commits into from
Nov 3, 2018

Conversation

Rich-Harris
Copy link
Member

@Rich-Harris Rich-Harris commented Nov 2, 2018

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

text/0000-reactive-assignments.md Outdated Show resolved Hide resolved
@Rich-Harris Rich-Harris changed the title [WIP] RFC: Reactive assignments RFC: Reactive assignments Nov 2, 2018

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.
Copy link
Member

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

Copy link
Member Author

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

Copy link
Member

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?

Copy link
Member Author

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

Copy link
Member

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.

Copy link
Member Author

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

Copy link
Member

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.

Copy link
Member

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?

@tomcon
Copy link

tomcon commented Nov 2, 2018

Will equivalents to component.root & component.options be available?


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.
Copy link
Member

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'.

Copy link
Member Author

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

Copy link
Member

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);

Copy link
Member

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.

@Rich-Harris
Copy link
Member Author

Will equivalents to component.root & component.options be available?

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.

text/0000-reactive-assignments.md Outdated Show resolved Hide resolved
text/0000-reactive-assignments.md Outdated Show resolved Hide resolved
@TehShrike
Copy link
Member

The svelte:meta element seems like a great way to get the component's state as an object, or even just get the props that were passed in:

<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 ref instead of bind, but bind seems better. @Conduitry noted that already uses the bind directive for one-way binding, so it doesn't seem too weird here.

@Rich-Harris
Copy link
Member Author

Yes, I like the <svelte:meta> idea. Idiomatic, easy to compile.

I don't think we'd want it to make those names magically available in <script> though — I think we'd want to handle it the same way this RFC proposes we handle refs:

<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 state. props makes sense, as does root. Not entirely sold on options — it'd be nice if we were free to use a more optimal private API for inline components, rather than the public API (which would also prevent us from doing certain optimisations like component folding).

@TehShrike
Copy link
Member

Oh, I'd somehow missed that bit about needing to define the variable with let for refs.

Yeah, that would make TypeScript type waaaaaay simpler.

text/0000-reactive-assignments.md Outdated Show resolved Hide resolved
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">
Copy link
Member

@lukeed lukeed Nov 2, 2018

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>

Copy link
Member

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.

Copy link
Member Author

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 @Conduitrypreload is an awkward anomaly. The less we have those special cases the more stable a platform for innovation the framework becomes

Copy link
Member

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 😅

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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

Copy link
Member

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 Show resolved Hide resolved

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.
Copy link
Member

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);

Copy link
Member

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).

Copy link
Member

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.

Copy link

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.

Copy link
Member

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.

Copy link

@tomcon tomcon Nov 4, 2018

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strong agree w/ @tivac and @tomcon on this one.

<Bar {...subset()}/>
```

This is inelegant, but I believe the inelegance would affect a small minority of components.
Copy link
Member

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>

Copy link
Member

@Conduitry Conduitry Nov 2, 2018

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.

@Rich-Harris Rich-Harris merged commit 543f42d into master Nov 3, 2018
@Rich-Harris Rich-Harris deleted the reactive-assignments branch November 3, 2018 14:11
@Rich-Harris Rich-Harris mentioned this pull request Nov 3, 2018
@Rich-Harris
Copy link
Member Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.