Comments (11)
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.
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.
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.
I don't think your examples are fairly representing how the code would look using snippets.
from svelte.
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.
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.
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.
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.
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.
@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.
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)
- Critial: static analyze some.svelte.ts HOT 3
- Hooks to manipulate the Svelte compiler HOT 5
- Svelte 5: svelte-ignore unknown/legacy code warnings are misplaced
- `state_referenced_locally` warning should skip type declarations
- Transitions not working properly in Svelte 5. HOT 2
- Svelte 5: `<select>`/`<option>` elements get incorrect/inconsistent values when applying `undefined` with spread or reactive value
- playground: console formatting isn't applied HOT 2
- Svelte 5: Setting the `loading` property for `img` in the `$effect` rune does not work HOT 2
- Svelte 5: Throw an error when importing svelte/internal/* HOT 3
- Binding scrollY to `svelte:window` causes implicit scrolling to the top of the page HOT 6
- Object literal may only specify known properties, and '"on:accept"' does not exist in type 'Omit<HTMLInputAttributes, keyof HTMLAttributes<any>> & HTMLAttributes<any>'. HOT 3
- Svelte 5 playground is freezing HOT 2
- Binding to store values not working correctly in Svelte 5 HOT 4
- Remove `indeterminate` from `HTMLInputAttributes` type HOT 1
- Default values for array-like props breaks rendering HOT 1
- Svelte 5: broken reactivity in legacy mode in rare case HOT 1
- Cannot find base config file "./.svelte-kit/tsconfig.json" [tsconfig.json] HOT 2
- Svelte 5: Binding `scrollY` to `svelte:window` does not update the initial value
- Svelte 5: `contenteditable="true"` with `bind:innerText` breaks the output of `{@render children()}` HOT 2
- Allow `textContent` / `innerHTML` / `innerText` binding without setting the `contenteditable` attribute HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from svelte.