Skip to content
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

RFC: Use <slot> instead of {{yield}} #763

Closed
Rich-Harris opened this issue Aug 13, 2017 · 11 comments
Closed

RFC: Use <slot> instead of {{yield}} #763

Rich-Harris opened this issue Aug 13, 2017 · 11 comments

Comments

@Rich-Harris
Copy link
Member

Something I've been wondering about recently — whether we should replace {{yield}} (an Ember idiom Ractive stole that we then copied) with <slot></slot>, which is the 'platform' equivalent.

A quick primer on <slot>

In Svelte, you might do something like this:

<!-- App.html -->
<MyComponent>
  hello!
</MyComponent>

<!-- MyComponent.html -->
some yielded content: {{yield}}

The web components equivalent would be this:

<template id='my-component'>
  some slotted content: <slot></slot>
</template>

<script>
  class MyComponent extends HTMLElement {
    constructor() {
      super();
      const template = document.querySelector('#my-component').content;
      const shadowRoot = this.attachShadow({mode: 'open'})
        .appendChild(template.cloneNode(true));
    }
  }

  customElements.define('my-component', MyComponent);
</script>

<my-component>
  hello!
</my-component>

Web components also have named slots, and fallback content for slots, meaning you can do this:

<template id='my-component'>
  <h1>my component</h1>
  <slot>unnamed fallback</slot>

  <h2>named slot foo</h2>
  <slot name='foo'>foo fallback</slot>

  <h2>named slot bar</h2>
  <slot name='bar'>bar fallback</slot>
</template>

<script>/* gross setup code goes here */

<my-component>
  some content for the unnamed slot
  <span slot='foo'>the foo content</span>
</my-component>

This results in something like the following:

screen shot 2017-08-13 at 10 50 59 am

Pros and cons

There are several advantages to using <slot> over {{yield}}:

  • Transferrable knowledge. If people already know web components, they have a headstart with Svelte component composition. Likewise, if they learn <slot> in a Svelte context, they will be able to hit the ground running with web components.
  • Alignment with The Platform™. While a lot of us have been somewhat sceptical about web components generally, there are situations where they make a lot of sense, and there's no point in doing things differently (other things being equal). More on this later
  • Named slots are a nice feature. (Ractive has named {{yield foo}} blocks, which we haven't yet implemented)
  • Fallback content is another nice feature
  • It suggests a public API — imagine doing myComponent.slots.foo.appendChild(content). More on this later

There are some possible downsides as well:

  • It's extra markup, if the <slot> is actually rendered to the DOM (rather than simply treated as a placeholder)
  • It means we're locked into the web components way of doing things

Personally I'm more persuaded by the pros than the cons. Are there things I'm missing?

Aligning with web components

We've discussed this previously (#12) without any real resolution — so far, the way to generate web components from Svelte components is to use svelte-custom-elements. There are arguments for generating web components directly from the compiler, aside from mere convenience. In particular, true style encapsulation (not just Svelte-style style leakage prevention, but protection from global styles) is a nice feature, and it's good for performance since the browser can isolate stuff more effectively.

From a DX perspective, compiling directly to custom elements is better than using svelte-custom-elements, because svelte-custom-elements puts everything in shadow DOM. Using light DOM (i.e. <slot>) means you can see your component tree in devtools, for example.

I've talked in the past about how web components are sub-optimal when it comes to dataflow, because the DOM is a terrible API for that. But it occurs to me that Svelte-generated web components could have all the same methods regular Svelte components do, so you could (for example) do this:

document.querySelector('my-component').set({
  foo: 1,
  bar: 2
});

In other words, you can retain the benefits of synchronous, predictable updates, without sacrificing the performance benefits of batching.

Another more cynical reason to align with web components: the web components community. Svelte has an opportunity to be the favoured way of creating web components, since authoring them by hand involves writing some hilariously ugly code, and every other way creates a dependency on a framework. So there's a PR advantage that we shouldn't ignore.

(For the avoidance of doubt: none of this means that web components would be in any way required. It would still be possible to SSR a Svelte app without custom elements, for example. Best of all possible worlds!)

Public API

This issue was prompted in part by this Gitter convo with @m59peacemaker about having a public API for yielded content.

The suggestion there was that yield could be a public initialisation option (as opposed to an internally used _yield):

new Component({
  target,
  yield: [someDiv, someOtherDiv]
});

If we had <slot> elements in the component template, they could be properties of the component, which would allow this sort of thing:

component = new Component({
  target
});

component.slots.foo.appendChild(someDiv);
component.slots.foo.appendChild(someOtherDiv);

We could also have a combination of the two. We would also need a way of targeting an unnamed <slot> — this is kind of ugly but maybe works?

component.slots[''].appendChild(someDiv);
component.slots[''].appendChild(someOtherDiv);

Thanks for reading this far, if anyone did. Let me know your thoughts!

@constgen
Copy link

I have problem currently with yield and ESlint. It reports error, because this word is reserved. So I think its time to refuse yield

@TehShrike
Copy link
Member

I'm excited by the possibility of getting a handle to yielded components with ref, and passing attributes into them.

@morewry
Copy link

morewry commented Aug 23, 2017

I just came across svelte while evaluating various ways to do (as-much-as-possible) framework agnostic UI components--and after reviewing the documentation came here to see if there was any undocumented functionality equivalent to "named slots" or "multi transclude" which I've previously relied on in Polymer and Angular. Equivalent functionality would be acceptable, but I concur with everything you've said about the positives of aligning with the platform.

this is kind of ugly but maybe works?

component.slots[''].appendChild(someDiv);
component.slots[''].appendChild(someOtherDiv);

Potentially you could create an internal alias for that slot that communicates that an unnamed slot is the catch-all or default slot when there are both named slots and an unnamed slot.

@Rich-Harris
Copy link
Member Author

We chatted about this a bit on Gitter and I think the consensus was that this would work:

component.slots.default.appendChild(someDiv);

It has a nice symmetry with ES modules (default imports/exports), and we can verify at compile time that there isn't a slot named 'default'.

@robdodson
Copy link

I'm biased but I think this is a very cool idea 😃

@Crisfole
Copy link
Contributor

My two cents/pence:

I like svelte for its similarity to Ractive which I'm already familiar with. I'm willing to bet a large portion of svelte users have a similar background.

Transferrable knowledge. If people already know web components, they have a headstart with Svelte component composition. Likewise, if they learn in a Svelte context, they will be able to hit the ground running with web components

The same applies but in reverse "If people already know Ember/Ractive, they have a headstart..."

The "named slot" pro has a solution: what Ember and Ractive do.

On the other hand I do like the public API "pro", although I struggle to think of a situation I'd need it in...

@Rich-Harris Rich-Harris mentioned this issue Aug 26, 2017
7 tasks
@Rich-Harris
Copy link
Member Author

@Crisfole yeah, I hear you. The fact remains though (much as I hate to admit it!) that the userbase that's familiar with <slot> (either via WCs/Polymer or Vue) is probably much larger than the Ember/Ractive userbase, certainly when you factor in the growth that will happen around WCs as other browser vendors get round to implementing them — and while they may not be Svelte's current users, hopefully they will be one day 😀

Anyway, PR is done, if anyone is interested --> #787

@Crisfole
Copy link
Contributor

No doubt! I was just offering my dissenting opinion since it hadn't been stated yet.

m59peacemaker added a commit to m59peacemaker/svelte that referenced this issue Aug 27, 2017
m59peacemaker added a commit to m59peacemaker/svelte that referenced this issue Aug 27, 2017
Rich-Harris added a commit that referenced this issue Aug 28, 2017
@Rich-Harris
Copy link
Member Author

Closing — implemented in 1.33

@mrkishi
Copy link
Member

mrkishi commented Sep 10, 2017

Sorry to bump this issue, but I couldn't quite figure this out:

Is it possible to check if a slot's been filled? For instance, how could one completely remove the <h2> in this example when the name slot is empty?

@m59peacemaker
Copy link
Contributor

@mrkishi You can access slots on the component's options property. I don't know if there is a better way. Demo here.

{{#if slots.name}}
    <h2><slot name='name'></slot></h2>
{{/if}}
export default {
  data () {
    return { slots: {} }
  },
  oncreate () {
    this.set ({ slots: this.options.slots })
  }
}

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

No branches or pull requests

8 participants