Code Monkey home page Code Monkey logo

Comments (11)

webJose avatar webJose commented on June 3, 2024 1

Ok, I'm satisfied with my new testing. I panicked over incomplete information. Thank you very much to those that corrected my shortcomings. All hail Svelte!!

from svelte.

webJose avatar webJose commented on June 3, 2024

BTW, the proposed syntax seems to eliminate the need for <svelte:fragment> to "slot" content, so I guess {@const} and probably others would need refactor to be allowed immediately after the pseudo element named after the snippet property.

from svelte.

webJose avatar webJose commented on June 3, 2024

Also, if you downvote, can I ask that you provide a comment explaining the reason? Why you disagree. Am I mistaken? Did I do this incorrectly and it is my own fault that I perceive this as a problem? Or perhaps you are perfectly content with the mess that snippets produce? I guess that's a valid opinion too.

from svelte.

CaptainCodeman avatar CaptainCodeman commented on June 3, 2024

I don't think your examples are fairly representing how the code would look using snippets.

from svelte.

webJose avatar webJose commented on June 3, 2024

Thanks, @CaptainCodeman for your input. Could you provide a more fair example? Or perhaps refactor mine? I just learned about snippets 2 days ago. It would be nice if I were told the best practices around snippets as slots.

from svelte.

webJose avatar webJose commented on June 3, 2024

Real World Example

This issue is really running in circles in my head because I love Svelte so much. I decided to take a simple component from a project from work: A Bootstrap toolbar with merely 11 controls. Here I present the Svelte v4 template:

<ButtonToolbar class="py-1">
    <div class="row flex-fill g-2" style:z-index="5">
        <div class="col-auto flex-fill">
            <div class="d-flex gap-1 justify-content-center justify-content-xxl-start align-items-end">
                <Button
                    size="sm"
                    buttonStyle="secondary"
                    on:click={refreshData}
                >
                    <Icon name="refresh" />
                </Button>
                <UserSelect
                    items={filteredOwners}
                    bind:selectedUser={$currentOwner}
                    text="Search by owner:"
                    on:search={({ detail }) => filteredOwners = filterExistingOwners(detail)}
                    on:clear={() => filteredOwners = []}
                />
                <div class="d-flex flex-row gap-1 align-items-end">
                    <Button
                        size="sm"
                        buttonStyle="danger"
                        outlined
                        disabled={!$currentOwner}
                        on:click={() => dispatch('ownerAction', 'remove')}
                    >
                        Remove Owner
                    </Button>
                </div>
                <UserSelect
                    items={newOwnersResult}
                    placeholder="WWID or <last, first>"
                    minValueLength={4}
                    bind:selectedUser={$newOwner}
                    text="New owner:"
                    on:search={({ detail }) => newOwnersResult = searchForUsers(detail)}
                    on:clear={() => newOwnersResult = Promise.resolve([])}
                />
                <div class="d-flex flex-row align-items-end gap-1">
                    <Button
                        size="sm"
                        buttonStyle="info"
                        outlined
                        disabled={!$currentOwner || !$newOwner}
                        on:click={() => dispatch('ownerAction', 'replace')}
                    >
                        Replace Owner
                    </Button>
                    <Button
                        size="sm"
                        buttonStyle="success"
                        outlined
                        disabled={!$newOwner}
                        on:click={() => dispatch('ownerAction', 'add')}
                    >
                        Add Owner
                    </Button>
                </div>
            </div>
        </div>
        <div class="col-xxl-4">
            <div class="h-100 d-flex align-items-center align-items-xxl-end justify-content-center justify-content-xxl-end align-items-end">
                <div class="d-flex flex-row align-items-baseline gap-1">
                    <ButtonGroup label="Operations to submit" size="sm" class="rounded-3 summary">
                        <BareCheckbox
                            class="mb-0"
                            value="add"
                            bind:group={activeOperations}
                            style="success"
                        >
                            {aggregatedData.add} addition{aggregatedData.add !== 1 ? 's' : ''}
                        </BareCheckbox>
                        <BareCheckbox
                            class="mb-0"
                            value="replace"
                            bind:group={activeOperations}
                            style="info"
                        >
                            {aggregatedData.replace} replacement{aggregatedData.replace !== 1 ? 's' : ''}
                        </BareCheckbox>
                        <BareCheckbox
                            class="mb-0"
                            value="remove"
                            bind:group={activeOperations}
                            style="danger"
                        >
                            {aggregatedData.remove} deletion{aggregatedData.remove !== 1 ? 's' : ''}
                        </BareCheckbox>
                    </ButtonGroup>
                    <Button size="sm" buttonStyle="secondary" disabled={actionButtonDisabled} on:click={undoChangesHandler}>Undo</Button>
                    <Button size="sm" buttonStyle="primary" disabled={actionButtonDisabled}>Submit</Button>
                </div>
            </div>
        </div>
    </div>
</ButtonToolbar>
{#if $showRefreshConfirmation}
    <RefreshConfirmation on:close={({ detail }) => processRefreshConfirmation(detail)}/>
{/if}

The original template is 99 lines. Now the template after migrating slots to snippets:

{#snippet buttonToolbar()}
    <div class="row flex-fill g-2" style:z-index="5">
        <div class="col-auto flex-fill">
            <div class="d-flex gap-1 justify-content-center justify-content-xxl-start align-items-end">
                {#snippet btnRefresh()}
                    <Icon name="refresh" />
                {/snippet}
                <Button
                    size="sm"
                    buttonStyle="secondary"
                    on:click={refreshData}
                    body={btnRefresh}
                />
                <UserSelect
                    items={filteredOwners}
                    bind:selectedUser={$currentOwner}
                    text="Search by owner:"
                    on:search={({ detail }) => filteredOwners = filterExistingOwners(detail)}
                    on:clear={() => filteredOwners = []}
                />
                <div class="d-flex flex-row gap-1 align-items-end">
                    {#snippet btnRemoveOwner()}
                        Remove Owner
                    {/snippet}    
                    <Button
                        size="sm"
                        buttonStyle="danger"
                        outlined
                        disabled={!$currentOwner}
                        on:click={() => dispatch('ownerAction', 'remove')}
                        body={btnRemoveOwner}
                    />
                </div>
                <UserSelect
                    items={newOwnersResult}
                    placeholder="WWID or <last, first>"
                    minValueLength={4}
                    bind:selectedUser={$newOwner}
                    text="New owner:"
                    on:search={({ detail }) => newOwnersResult = searchForUsers(detail)}
                    on:clear={() => newOwnersResult = Promise.resolve([])}
                />
                <div class="d-flex flex-row align-items-end gap-1">
                    {#snippet btnReplaceOwner()}
                        Replace Owner
                    {/snippet}    
                    <Button
                        size="sm"
                        buttonStyle="info"
                        outlined
                        disabled={!$currentOwner || !$newOwner}
                        on:click={() => dispatch('ownerAction', 'replace')}
                        body={btnReplaceOwner}
                    />
                    {#snippet btnAddOwner()}
                        Add Owner
                    {/snippet}
                    <Button
                        size="sm"
                        buttonStyle="success"
                        outlined
                        disabled={!$newOwner}
                        on:click={() => dispatch('ownerAction', 'add')}
                    />
                </div>
            </div>
        </div>
        <div class="col-xxl-4">
            <div class="h-100 d-flex align-items-center align-items-xxl-end justify-content-center justify-content-xxl-end align-items-end">
                <div class="d-flex flex-row align-items-baseline gap-1">
                    {#snippet buttonGroup()}
                        {#snippet additionsCb()}
                            {aggregatedData.add} addition{aggregatedData.add !== 1 ? 's' : ''}
                        {/snippet}    
                        <BareCheckbox
                            class="mb-0"
                            value="add"
                            bind:group={activeOperations}
                            style="success"
                            body={additionsCb}
                        />
                        {#snippet replacementsCb()}
                            {aggregatedData.replace} replacement{aggregatedData.replace !== 1 ? 's' : ''}
                        {/snippet}
                        <BareCheckbox
                            class="mb-0"
                            value="replace"
                            bind:group={activeOperations}
                            style="info"
                            body={replacementsCb}
                        />
                        {#snippet deletionsCb()}
                            {aggregatedData.remove} deletion{aggregatedData.remove !== 1 ? 's' : ''}
                        {/snippet}        
                        <BareCheckbox
                            class="mb-0"
                            value="remove"
                            bind:group={activeOperations}
                            style="danger"
                            body={deletionsCb}
                        />
                    {/snippet}
                    <ButtonGroup label="Operations to submit" size="sm" class="rounded-3 summary" body={buttonGroup}>
                    {#snippet btnUndo()}
                        Undo
                    {/snippet}
                    <Button size="sm" buttonStyle="secondary" disabled={actionButtonDisabled} on:click={undoChangesHandler} body={btnUndo} />
                    {#snippet btnSubmit()}
                        Submit
                    {/snippet}
                    <Button size="sm" buttonStyle="primary" disabled={actionButtonDisabled} body={btnSubmit} />
                </div>
            </div>
        </div>
    </div>
{/snippet}
<ButtonToolbar class="py-1" body={buttonToolbar} />
{#if $showRefreshConfirmation}
    <RefreshConfirmation on:close={({ detail }) => processRefreshConfirmation(detail)} />
{/if}

This is 120 lines. A 20% increase in code when counted by lines, on a real-world example, so let's say we dropped from 60% to 20%. There go the gains in code reduction when compared to React. 😢 (I have calculated in the past some 30% code reduction moving from React to Svelte).

What's Terrible In This

I find particularly terrible to have to add 3 lines of code just to put the word "Undo" inside a button. I went from:

                    <Button size="sm" buttonStyle="secondary" disabled={actionButtonDisabled} on:click={undoChangesHandler}>Undo</Button>

To:

                    {#snippet btnUndo()}
                        Undo
                    {/snippet}
                    <Button size="sm" buttonStyle="secondary" disabled={actionButtonDisabled} on:click={undoChangesHandler} body={btnUndo} />

Is there really no more concise syntax? What happened to the Svelte of days that combatted boilerplate? Let me quote learn.svelte.dev - Auto-subscriptions lesson:

It starts to get a bit boilerplatey though, especially if your component subscribes to multiple stores. Instead, Svelte has a trick up its sleeve — you can reference a store value by prefixing the store name with $:

Then I hate the fact that I have to come up with unique names for snippets. I am the kind of guy that embeds the <input> elements inside the <label> elements just so I don't have to come up with an ID.


I'm not against snippets. I'm against deprecating slots in favor of snippets in their current form. Svelte is a compiler: Let Svelte convert my slots to snippets. I realize I came late to the feedback party, so I ask that you at least don't deprecate slots.

from svelte.

brunnerh avatar brunnerh commented on June 3, 2024

This is a nice trick to avoid consumers to have to import 4 or more "card parts" every time.

It is not a nice trick, it is an abuse of syntax.

If you change it to imports, you just get this with snippets:

<Card class="m-3">
  <Header>
    <Title>The Card's Title</Title>
  </Header>
  <Body>
    <p>
      This is some card content inside the body of the card.
    </p>
  </Body>
</Card>

Contents of components implicitly create a children snippet that can be retrieved from $props.

Also, instead of abusing slot props to save imports, you could also store sub-components as properties of the main component:

<Card class="m-3">
  <Card.Header>
    <Card.Title>The Card's Title</Card.Title>
  </Card.Header>
  <Card.Body>
    <p>
      This is some card content inside the body of the card.
    </p>
  </Card.Body>
</Card>

from svelte.

webJose avatar webJose commented on June 3, 2024

Hello, @brunnerh. Thanks for stopping by.

It is not a nice trick, it is an abuse of syntax.

It is nicer than importing all the time, but granted: You have a nicer trick. 😄 How do you do the sub-components as properties? Like this?

<script lang="ts" context="module">
    export const Body = CardBody;
</script>

I like it.

Ok, children is something that appears to solve the default slot for sure. The automatic behavior is what I was asking for, and it seems children does it!! I'm so glad!

Question: I have components with up to 7 slots, I think. For the cases where we have named slots, will I be forced to write snippets like I did in the examples I presented? In other words, is the grievance I feel isolated to named slots?

from svelte.

brunnerh avatar brunnerh commented on June 3, 2024

For named slots you will still have to define snippets explicitly, but you can also put them inside the component and they will automatically be passed as property with the name of the snippet.

To have components as properties of others, using a separate file might be a good idea.
E.g. something like this:

// card/index.js
import Card from './card.svelte';
import Body from './body.svelte';
...

Card.Body = Body;

export default Card;

(With TS, additional typing will be necessary.)

from svelte.

webJose avatar webJose commented on June 3, 2024

@brunnerh what about default slot with slot variables? I don't see any mention of syntax that replaces the let:XXX properties (I'm reading this page). Please don't tell me that children only works with no properties. That would make children a half-solution. 😞 If my default slots pass variables, then I must add {#snippet snippetName(vars)}, or so it seems. Yes?

By the way, I realize that if I had found that page before I tested (and panicked about) snippets, I would not have been as scandalized. I guess I used an old or incomplete article to read about them. I think you alleviated most of my concerns. Thank you for that.

from svelte.

brunnerh avatar brunnerh commented on June 3, 2024

If children needs arguments, you would have to declare it to define the arguments.
It is a bit more verbose but gets rid of the weird scoping behavior of let props and is more explicit in general.

from svelte.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.