Skip to content

Commit 36f2290

Browse files
authored
Merge pull request #33 from sveltejs/markup-constants
Constants in markup
2 parents 8bf70d8 + 43e3863 commit 36f2290

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed

text/0000-markup-constants.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
- Start Date: 2020-09-09
2+
- RFC PR: [#33](https://github.com/sveltejs/rfcs/pull/33)
3+
- Svelte Issue: (leave this empty)
4+
5+
# Constants in markup
6+
7+
## Summary
8+
9+
Add a new `{@const ...}` tag that defines a local constant.
10+
11+
## Motivation
12+
13+
Consider a component with an `each` block:
14+
15+
```svelte
16+
<script>
17+
export let boxes;
18+
</script>
19+
20+
{#each boxes as box}
21+
<div
22+
class="box"
23+
style="width: {box.width}px; height: {box.height}px"
24+
>
25+
{box.width} * {box.height} = {box.width * box.height}
26+
</div>
27+
{/each}
28+
```
29+
30+
Suppose we'd like to add a `large` class for boxes over 10,000 square pixels. We could do it like this...
31+
32+
```svelte
33+
<script>
34+
export let boxes;
35+
</script>
36+
37+
{#each boxes as box}
38+
<div
39+
class="box"
40+
class:large={box.width * box.height >= 10000}
41+
style="width: {box.width}px; height: {box.height}px"
42+
>
43+
{box.width} * {box.height} = {box.width * box.height}
44+
</div>
45+
{/each}
46+
```
47+
48+
...but that duplicates the `box.width * box.height` expression. If we were to add `medium` and `jumbo` classes, this would quickly get out of hand. We could define a helper function instead...
49+
50+
```svelte
51+
<script>
52+
export let boxes;
53+
54+
const area = box => box.width * box.height;
55+
</script>
56+
57+
{#each boxes as box}
58+
<div
59+
class="box"
60+
class:large={area(box) >= 10000}
61+
style="width: {box.width}px; height: {box.height}px"
62+
>
63+
{box.width} * {box.height} = {area(box)}
64+
</div>
65+
{/each}
66+
```
67+
68+
...but adding logic to the `<script>` is unfortunate, and we're still calculating the area twice per box.
69+
70+
We could create what our forebears called a 'viewmodel'...
71+
72+
```svelte
73+
<script>
74+
export let boxes;
75+
76+
const boxes_with_area = boxes.map(box => ({
77+
...box,
78+
area: box.width * box.height
79+
}));
80+
</script>
81+
82+
{#each boxes_with_area as box}
83+
<div
84+
class="box"
85+
class:large={box.area >= 10000}
86+
style="width: {box.width}px; height: {box.height}px"
87+
>
88+
{box.width} * {box.height} = {box.area}
89+
</div>
90+
{/each}
91+
```
92+
93+
...but that feels like a hack.
94+
95+
These situations crop up from time to time, and the various workarounds are labour-intensive, involve wasted computation, and impede readability and refactoring.
96+
97+
## Detailed design
98+
99+
Suppose we added a new `{@const ...}` tag to the Svelte template language. We could define `area` where it is used:
100+
101+
```svelte
102+
<script>
103+
export let boxes;
104+
</script>
105+
106+
{#each boxes as box}
107+
{@const area = box.width * box.height}
108+
109+
<div
110+
class="box"
111+
class:large={area >= 10000}
112+
style="width: {box.width}px; height: {box.height}px"
113+
>
114+
{box.width} * {box.height} = {area}
115+
</div>
116+
{/each}
117+
```
118+
119+
The `@const` indicates that the value is read-only (i.e. it cannot be assigned to or mutated in an expression such as an event handler), and communicates, through its similarity to `const` in JavaScript, that it only applies to the current scope (i.e. the current block or element).
120+
121+
Attempting to read a constant outside its scope would be a reference error (unless the constant was already shadowing a value, of course):
122+
123+
```svelte
124+
{#each boxes as box}
125+
{@const area = box.width * box.height}
126+
127+
<!-- ... -->
128+
{/each}
129+
130+
<p>this is a reference error: {area}</p>
131+
```
132+
133+
### Hoisting and TDZ
134+
135+
With JavaScript `const` (and `let`), values cannot be read before they have been initialised. We could choose to do the same thing here, though in our case it is an unnecessary restriction — we could very simply hoist values to the top of their block, in the order in which they're declared:
136+
137+
```svelte
138+
{#if n}
139+
<p>{n}^4 = {hypercubed}</p>
140+
141+
{@const squared = n * n}
142+
{@const cubed = squared * n}
143+
{@const hypercubed = cubed * n}
144+
{/if}
145+
```
146+
147+
> 🐃 You could argue that this improves or degrades readability depending on your perspective. You could also argue in favour of topological ordering, as we have in the case of reactive statements/declarations. I don't yet have strong opinions one way or the other.
148+
149+
### Conflicts
150+
151+
Defining the same constant twice in a single block would be a compile error:
152+
153+
```svelte
154+
{@const foo = a}
155+
{@const foo = b}
156+
```
157+
158+
159+
## How we teach this
160+
161+
I've provisionally suggested `{@const ...}` for the reasons articulated above, namely the similarities to `const`, so this new feature could be called a 'const tag'. The keyword is open to bikeshedding though — perhaps someone could make a case for calling it `{@value ...}` or `{@local ...}` instead.
162+
163+
Accepting this proposal wouldn't require any changes to the existing docs, but would require some new documentation to be created.
164+
165+
## Drawbacks
166+
167+
The evergreen answer to the 'why should be *not* do this?' question is 'it increases the amount of stuff to learn, and is another thing that we have to implement and maintain'.
168+
169+
In this case though there's an additional reason we might consider not adding this. One of the arguments that's frequently deployed in favour of JSX-based frameworks over template-based ones is that JSX allows you to use existing language constructs:
170+
171+
```js
172+
{boxes.map(box => {
173+
const area = box.width * box.height;
174+
175+
return <div
176+
class="box"
177+
class:large={area >= 10000}
178+
style="width: {box.width}px; height: {box.height}px"
179+
>
180+
{box.width} * {box.height} = {area}
181+
</div>
182+
})}
183+
```
184+
185+
The complaint is that by choosing less powerful languages, template-based frameworks are then forced to reintroduce uncanny-valley versions of those constructs in order to add back in missing functionality, thereby increasing the mount of stuff people have to learn.
186+
187+
In general, I'm unpersuaded by these arguments (learning curve is determined not just by unfamiliar syntax, but by unfamiliar semantics and APIs as well, and the frameworks in question excel at adding complexity in those areas). But this *is* a case where it feels like we're papering over a deficiency in our language, and is the sort of thing detractors might well point to and say 'ha! see?'. Whether or not that's a reason not to pursue this RFC is a matter for collective judgment.
188+
189+
## Alternatives
190+
191+
The alternatives are shown above in the 'motivation' section; they are not good.
192+
193+
## Unresolved questions
194+
195+
* Is `@const` the best keyword?
196+
* Should we allow multiple declarations per tag? (Probably not.)
197+
* Does TDZ apply?
198+
* Are declarations ordered as-authored, or topologically?

0 commit comments

Comments
 (0)