-
Notifications
You must be signed in to change notification settings - Fork 82
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
Constants in markup #33
Conversation
I could imagine people putting a more complex expression in an |
Do these blocks allow for reactive assignments? |
Yes.
No.
No.
Topologically so they work like reactive statements. |
This might happen yes. And yes, template tags cannot have TS right now. But I think we should look at this separately, since I think this is a good addition either way. About TDZ: I would say it should apply, at least for me it would be more confusing to use something like this first and then declare it. So we would force the user to write more readable code. |
how are we handling circular dependencies? {@const a = a + 1} or {@const a = b}
{@const b = a + a} |
Also related: sveltejs/svelte#4601 |
I was just coming here to comment about sveltejs/svelte#4601 One of the main differences with that implementation is that the scope of the declared variable is much more explicit. |
@lukeed They're read-only, but values update when their dependencies do
@tanhauhau if we use topological ordering, the same way as reactive declarations currently do. if not, then we can rely on simple TDZ logic (even if we don't insist that declarations happen before usage, applying TDZ after hoisting declarations in the order in which they were written would handle circularity) Had forgotten about sveltejs/svelte#4601, thanks. I'm more inclined towards an {#if n}
<p>{n}^4 = {hypercubed}</p>
{@const squared = n * n}
{@const cubed = squared * n}
{@const hypercubed = cubed * n}
{/if} ...would look like this: {#if n}
{#with n * n as squared}
{#with squared * n as cubed}
{#with cubed * n as hypercubed}
<p>{n}^4 = {hypercubed}</p>
{/with}
{/with}
{/with}
{/if}
I'm not too concerned about this — in practical terms I don't think it's any less clear, in the same way that this... if (foo) {
const total = a + b;
console.log(`${a} + ${b} is ${total}`);
} ...is just as easily understood as this if not more so: if (foo) {
with ({ total: a + b }) {
console.log(`${a} + ${b} is ${total}`);
}
} |
This seems solid to me. If you want to thumb your nose in the direction of the whole "everyone is confused about the difference between const variables and const values" thing you could go with |
I am not a fan of this for the simple reason that it moves even more logic out of the script block and into the template. Currently, Svelte strongly encourages users to put most of their logic in the script block. This has the value that when I come across a component I can scan the script to see the bulk of the logic. Logic inside the template itself is both limited and easy to spot due to the block syntax. One of my big criticisms of JSX is that it encourages users to put significant amount of logic in the 'template' instead of separating things out. With SVelte it doesn't matter who wrote it, i tend to see more consistencies in how people write their code. 'Onboarding' complexity is reduced because everything is roughly where I expect it to be. This would work against that to a degree. In terms of 'doing the same thing twice': using the function example and wrapping it in a I'm mainly not a fan of this because it adds additional syntax for something that can already be solved in user-land (albeit with a little function call + memory overhead), it encourages putting even more logic into the component template which can make components difficult to reason about, and it also allows for more divergence in how people write there code and where they put their logic, making different svelte codebases potentially even more different due to fewer constraints. This last point is actually something I really value, I read a lot of Svelte code by a lot of different people and broadly speaking things look the same and are in the same places. |
Also a vote against, for the simple reason that logicless templates would be the ultimate goal for me. I'm even against the |
About the argument against it, "{@const will make code less consistent ": I think the same is true now, since people can come up with very different ways of dealing with the "computed value inside each loop/if function" problem. Some extract components, some use functions, some will prepare the array differently beforehand. I also think this would be great in terms of colocation. If I need some intermediate result I no longer have to jump between script and markup. But of course this could be overused. |
But you always know where the logic will be. Either it is defined where the value is used or it is in the script. There are no other options. |
Agree with @pngwn and @antony. I believe this feature is overrated in RFC. Cons aren't covered by pros. Alternatives are fine. But, seems this feature mostly makes sense inside I'm not sure how exactly it should look like, but maybe something like: {#each
boxes as box,
index,
area = box.width * box.height
}
<div
class="box"
class:large={area >= 10000}
style="width: {box.width}px; height: {box.height}px"
>
{box.width} * {box.height} = {area}
</div>
{/each}
@Rich-Harris what do you think about this counter-proposal? )))) |
I like this, mostly because it allows me to write small components without creating another separate sub-component for holding the value simple computation. I get annoyed every time I need to create a component just to hold a variable, or even move the computation away from the relevant location. It reminds me of the days where variables in C had to be declared at the top of the function. It might not be the academically correct way of creating a template language (or maybe it is?), but it is at least in the line of getting things done. |
I agree with the objections of @pngwn and others for most of the use cases I have, but this RFC addresses a significant pain point I've had with
@PaulMaly's suggestion would solve my primary use case. An alternative (maybe not good) would be to restrict |
Agree with all counterpoints. The only places where I think something like this would be permissible would be:
The latter two are more questionable, though, since they have access to the component's WRT inline components (IC): If they inherit the parent's full context (#34 (comment)) then that removes |
Of the three proposals rendered today, this is the one I want the most. Arguably, it leans into JSX land—including logic in the templates. Counter argument: one of the reasons people sometimes balk at mustache-like syntax is just that: logic in the templates. I'm persuaded that co-locating just this one type of logic will be useful. It's certainly something I've wanted to reach for prior, but always end up with a boilerplate function somewhere, which feels hacky. I'm also persuaded by the arguments that it will be easier to track stuff down by co-location. @lukeed inline components, await...then, and each blocks are the exact use case where I want this. |
@arxpoetica Right, because those are the only 3 instances where new "scopes" are created, which means you're seeing data for (probably) the first time. That said, |
This is exactly why I'm in favor of |
There's also |
Yes.
not understand this question
yes
yes What I want to say - this is very useful and similar to what Mako template has. |
In the motivation for #32 you mention
But I feel like this proposal removes even more of this 'artificial pressure'. Taking all three of today's proposals (#32 #33 #34) together it would become theoretically possible to make an entire application in a single file which is the complete opposite direction of what Svelte has been so far. |
@stephane-vanraes hmm looks like if we create a new branch or scope for some corner cases, we should have full features of a component without creating a new file. The main question of how to solve these cases without making a bomb for common behavior. We are very close to full inline components (with css, js logic, and templates). PS idea for syntax:
|
This seems crucial, for example for {#each boxes as box}
{@const b = box?.a?.b}
{#if b}
<p>{b}</p>
{/if}
{/each} |
What about
|
|
@pngwn I think this potentially has a memoization role. |
That certainly isn't part of this proposal and would introduce a significant amount of complexity. |
Memoization would be another way to achieve a similar end result as this feature, but this isn't memoization. |
Well, then, |
Is there an implementation branch? Possibly @RedHatter can adapt from sveltejs/svelte#4601? |
I keep running into cases where this would be useful. At first it seemed like I also hope that destructuring will be supported. I assume it would be; I just don't see any specific mention of it here yet (looks like it already is in sveltejs/svelte#4601). {@const {submitting, pristine, values} = state} My latest use case: I want to both make use of object destructuring (so that I can use nice short names for things that I will be referencing a lot within the block) and still be able to pass on the full object as needed, like this: <Form let:state>
{@const {submitting, values, errors} = state}
… <!-- (make use of submitting, values, etc.) -->
<Something {state} />
</Form> Then you can have it both ways, reduce duplication, — and take advantage of the let:var shorthand! If you start out by destructuring: <Form let:state={{submitting, values, errors}}>
… <!-- (make use of submitting, values, etc.) -->
</Form> and then later realize that you need the entire object for something, you can't simply pass it through like: <!-- (pass along entire contents of let:state ... can't!) -->
<Something {state} /> without rewriting the rest of your block to use long-handed Sure, you could try to recombine the props like this: <Something state={submitting, values, errors}} /> But that's duplication and only works if the destructuring lists all possible keys of the object — which it shouldn't have to know about in order to cherry pick some of them for convenient referencing within the current block. Otherwise, any keys that were omitted from the destructuring would get "permanently" lost, since we don't have access to the original object. I tried to work around is like this: <Form let:state={{submitting, values, errors, ...rest}}>
…
<Something state={submitting, values, errors, ...rest}} /> but then you run into this error:
... which is why I'm specifically mentioning the |
I also want to say that the advantages of co-location — being able to put related code that is used together (particularly when that is the only place it is used) close together in the source code — seems (to me at least) like a far more compelling and pragmatic concern than concerns about logic-less templates or The other advantage of being able to closely co-locate constants with where those constants are used is that it allows you to more easily experiment with the idea of possibly extracting a new component; its lets you start "bringing together" all the pieces that a section of template depends on, in preparation for later extraction. Coming from React, one of the things I miss the most is how trivially easy it was to extract an inline function component out of any part of your component that started to look like it belonged in its own component, or was being duplicated in several places in your component. Just start with Being able to easily define reactive constants closer to where they are used brings Svelte one step closer to being able to easily prepare/stage code for possible extraction into a new component. @Rich-Harris mentioned this before, which I resonated with:
and from the inline-components RFC:
I also resonated with @dummdidumm's comments:
and @knobo's:
|
I have changed my mind. I'm in favour of this proposal (especially in conjunction with the inline component proposal). I think the main issue here is simplicity. Yes, you can do this with temporary variables or memoisation but temps vars have to live in the script tag making the code more difficult to reason about and involve lots of jumping around. In the case of using memoisation to solve this problem: this is a poorly understood concept with a huge potential for complexity, memoisation as a solution means you need to think about caching and @TehShrike has convinced me that this is an unreasonable expectation, especially for simpler cases where I generally think that |
I thought using mapping was a good option. I do this to use ready-made mapped properties that you just need to place in the right place in the component. But using @const in an inline component makes sense. @const in inline component is ok. Maybe also in #each and #await. |
Suggestion: {@const} inside a tag. The constant would be available in each mustache used in the tag, e.g .:
But it won't be available outside of the tag. |
any progress on this?
<script lang="ts">
const func1(val: MyType): MyType2 => {
// do something with val
return MyType2 ;
}
</script >
{#directive my1}
{@set my2 = func1(my1)}
{my2.prop}
{/directive} |
Merged after discussion with the core team — PR exists here sveltejs/svelte#6413 |
it would be interesting with a summary of the discussion. (or at least the hightlights) |
Was basically a combination of the benefits pointed out in the original RFC and this comment which are counter-arguments to my original rejection. |
Would Would often be sufficient and neater than a whole big |
For anyone else stumbling here trying to understand what |
Rendered