├── .gitignore
├── 0000-template.md
├── README.md
└── text
├── 0001-reactive-assignments.md
├── 0002-reactive-stores.md
├── 0003-reactive-declarations.md
├── 0004-better-composition.md
├── 0005-svelte-component-interface.md
├── 0006-style-properties.md
├── 0007-markup-constants.md
└── 0008-style-directives.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /scratch
--------------------------------------------------------------------------------
/0000-template.md:
--------------------------------------------------------------------------------
1 | - Start Date: (fill me in with today's date, YYYY-MM-DD)
2 | - RFC PR: (leave this empty)
3 | - Svelte Issue: (leave this empty)
4 |
5 | # (RFC title goes here)
6 |
7 | ## Summary
8 |
9 | > One paragraph explanation of the feature.
10 |
11 | ## Motivation
12 |
13 | > Why are we doing this? What use cases does it support?
14 |
15 | > Please provide specific examples. If you say "this would be more flexible" then
16 | > give an example of something that becomes easier. If you say "this would be make
17 | > it easier to do X" then give an example of what that looks like today and what's
18 | > hard about it.
19 |
20 | > Don't assume that others recognize the problem is one that needs to be solved
21 | > Is there some concrete issue you cannot accomplish without this?
22 | > What does it look like to accomplish some set of goals today and how could
23 | > that be improved?
24 | > Are there any workarounds that are necessary today?
25 | > Are there open issues on Github where people would be helped by this?
26 | > Will the change have performance impacts? Can you quantify them?
27 |
28 | > Please focus on explaining the motivation so that if this RFC is not accepted,
29 | > the motivation could be used to develop alternative solutions. In other words,
30 | > enumerate the constraints you are trying to solve without coupling them too
31 | > closely to the solution you have in mind.
32 |
33 | ## Detailed design
34 |
35 | ### Technical Background
36 |
37 | > There are a lot of ways Svelte is used. It's hosted on different platforms;
38 | > integrated with different libraries; built with different bundlers, etc. No one
39 | > person knows everything about all the ways Svelte is used. What does someone who
40 | > knows about Svelte but hasn't necessarily used anything outside of it need to
41 | > know? Are there docs you can share?
42 |
43 | > How do different libraries or frameworks implement this feature? We can take
44 | > design inspiration from others who have done this well and improve upon the
45 | > designs or make them better fit Svelte.
46 |
47 | ### Implementation
48 |
49 | > Explain the design in enough detail for somebody familiar with the framework to
50 | understand, and for somebody familiar with the implementation to implement. Any
51 | > new terminology should be defined here.
52 |
53 | > Explain not just the final design, but also how you arrived at it. What
54 | > constraints did you face? Are there corner cases you've come up with solutions for?
55 |
56 | > Explain how your design fits into the larger picture. Are the other open problems
57 | > in this area you're familiar with? How does this design fit with potential
58 | > solutions for those issues?
59 |
60 | > Connect your design to the motivations you listed above. When describing a part of
61 | > the design, it can be useful to share an example of what it would look like to
62 | > utilize the implementation as solution to the problem.
63 |
64 | ## How we teach this
65 |
66 | > What names and terminology work best for these concepts and why? How is this
67 | idea best presented? As a continuation of existing Svelte patterns, or as a
68 | wholly new one?
69 |
70 | > Would the acceptance of this proposal mean the Svelte guides must be
71 | re-organized or altered? Does it change how Svelte is taught to new users
72 | at any level?
73 |
74 | > How should this feature be introduced and taught to existing Svelte
75 | users?
76 |
77 | ## Drawbacks
78 |
79 | > Why should we *not* do this? Please consider the impact on teaching Svelte,
80 | on the integration of this feature with other existing and planned features,
81 | on the impact of the API churn on existing apps, etc.
82 |
83 | > There are tradeoffs to choosing any path, please attempt to identify them here.
84 |
85 | ## Alternatives
86 |
87 | > What other designs have been considered? What is the impact of not doing this?
88 |
89 | > This section could also include prior art, that is, how other frameworks in the
90 | > same domain have solved this problem differently.
91 |
92 | ## Unresolved questions
93 |
94 | > Optional, but suggested for first drafts. What parts of the design are still TBD?
95 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Svelte RFCs
2 |
3 | This is a place to discuss major changes to Svelte — where 'major' implies significant changes either to public interfaces or internal implementation details, particularly those that could be controversial or involve breaking changes.
4 |
5 | Most changes don't need to go through the Request for Comments (RFC) process and can rely on issues and pull requests.
6 |
7 | A huge part of the value on an RFC is defining the problem clearly, collecting use cases, showing how others have solved a problem, etc. Coming up with a design is very iterative and only one part of the process. An RFC can provide tremendous value without the design described in it being accepted.
8 |
9 | The is inspired by (which is to say shamelessly ripped off from) the RFC process adopted by [React](https://github.com/reactjs/rfcs), [Ember](https://github.com/emberjs/rfcs) and others. The process itself is subject to change (or even abandonment!) as we gain experience with it.
10 |
11 |
12 | ## The process
13 |
14 | * Fork the RFC repo http://github.com/sveltejs/rfcs
15 | * Copy 0000-template.md to text/0000-my-feature.md (where 'my-feature' is descriptive. Don't assign an RFC number yet).
16 | Fill in the RFC. Put care into the details: **RFCs that do not present convincing motivation, demonstrate understanding of the impact of the design, or are disingenuous about the drawbacks or alternatives tend to be poorly-received.**
17 | * Submit a pull request. As a pull request the RFC will receive design feedback from the larger community, and the author should be prepared to revise it in response.
18 | * Build consensus and integrate feedback. RFCs that have broad support are much more likely to make progress than those that don't receive any comments.
19 | * Eventually, the team will decide whether the RFC is a candidate for inclusion in Svelte.
20 | RFCs that are candidates for inclusion in Svelte will enter a "final comment period" lasting 3 calendar days. The beginning of this period will be signaled with a comment and tag on the RFCs pull request.
21 | * An RFC can be modified based upon feedback from the team and community. Significant modifications may trigger a new final comment period.
22 | An RFC may be rejected by the team after public discussion has settled and comments have been made summarizing the rationale for rejection. A member of the team should then close the RFCs associated pull request.
23 | * An RFC may be accepted at the close of its final comment period. A team member will merge the RFCs associated pull request, at which point the RFC will become 'active'.
24 |
25 |
26 | ## The RFC life-cycle
27 |
28 | Once an RFC becomes active, then authors may implement it and submit the feature as a pull request to the Svelte repo. Becoming 'active' is not a rubber stamp, and in particular still does not mean the feature will ultimately be merged; it does mean that the core team has agreed to it in principle and are amenable to merging it.
29 |
30 | Furthermore, the fact that a given RFC has been accepted and is 'active' implies nothing about what priority is assigned to its implementation, nor whether anybody is currently working on it.
31 |
32 | Modifications to active RFCs can be done in followup PRs. We strive to write each RFC in a manner that it will reflect the final design of the feature; but the nature of the process means that we cannot expect every merged RFC to actually reflect what the end result will be at the time of the next major release; therefore we try to keep each RFC document somewhat in sync with the language feature as planned, tracking such changes via followup pull requests to the document.
33 |
34 |
35 | ## Implementing an RFC
36 |
37 | The author of an RFC is not obligated to implement it. Of course, the RFC author (like any other developer) is welcome to post an implementation for review after the RFC has been accepted.
38 |
39 | If you are interested in working on the implementation for an 'active' RFC, but cannot determine if someone else is already working on it, feel free to ask (e.g. by leaving a comment on the associated issue).
40 |
41 |
42 | ## Reviewing RFCs
43 |
44 | We tend to do our thinking informally, in the open, when time allows. There are a large number of community members relative to a small number of a core contributors who have many responsibilities. You can help ensure your RFC is reviewed in a timely manner by putting in the time to think through the various details discussed in the template. It doesn't scale to push the thinking onto a small number of core contributors. If reviewers raise an issue, don't dismiss it as irrelevant, but take the time to provide examples or data explaining it and coming up with ways that the design might be changed in response. Sometimes answering a single question can be very time consuming (such as setting up a benchmark), but discussions tend to stall out if concerns don't get thoroughly addressed.
45 |
--------------------------------------------------------------------------------
/text/0001-reactive-assignments.md:
--------------------------------------------------------------------------------
1 | - Start Date: 2018-11-01
2 | - RFC PR: https://github.com/sveltejs/rfcs/pull/4
3 | - Svelte Issue: https://github.com/sveltejs/svelte/issues/1826
4 |
5 | # Reactive assignments
6 |
7 | ## Summary
8 |
9 | This RFC proposes a radically new component design that solves the biggest problems with Svelte in its current incarnation, resulting in smaller apps and less code to write. It does so by leveraging Svelte's unique position as a compiler to solve the problem of *reactivity* at the language level.
10 |
11 | ## Motivation
12 |
13 | Many developers report being intrigued by Svelte, but unwilling to accommodate some of the quirks of its design. For example, using features like nested components or transitions requires that you *register* them, involving unfortunate boilerplate:
14 |
15 | ```html
16 |
17 |
18 |
19 |
20 |
29 | ```
30 |
31 | Declaring methods is also somewhat cumbersome, and in many editor configurations leads to red squigglies since it violates the normal semantics of JavaScript:
32 |
33 | ```html
34 |
35 |
36 |
47 | ```
48 |
49 | Perhaps the biggest hangup is that the markup doesn't have access to arbitrary values inside the `
82 | ```
83 |
84 | There are legitimate technical and historical reasons for this design. And some people — particularly those with a background in Vue or Ractive — are familiar with it and enjoy it. But the reality is that for most of us, it sucks. It would be much nicer to do something like this:
85 |
86 | ```html
87 |
92 |
93 |
Hello {capitalise(name)}!
94 |
Your name has {len()} letters in it.
95 | ```
96 |
97 | Why can't we? Because of *reactivity*. There's no way for the view to 'know' if `name` has changed.
98 |
99 |
100 | ## The reactivity problem
101 |
102 | UI frameworks are all, to some degree, examples of reactive programming. The notion is that your view can be thought of as a function of your state, and so when your state changes the view must, well... react.
103 |
104 | Reactivity is implemented in a variety of different ways (note: I am not expert in these frameworks other than Svelte, please correct any misapprehensions):
105 |
106 | * **React**'s approach is to re-render the world on every state change. This necessitates the use of a virtual DOM, which in turn necessitates a virtual DOM reconciler. It is effective, but an inefficient approach for three reasons: first, the developer's code must run frequently, providing ample opportunity for 'death by a thousand paper cuts' particularly if the developer is unskilled; second, the virtual DOM itself is a short-lived object, thirdly, discovering 'what changed' via virtual DOM diffing is wasteful in the common case that the answer is 'not much'. Reasonable people can (and do) disagree about whether these inefficiences are impactful on real apps (the answer is presumably 'it depends'), but it is well known that the most reliable way to make code faster is for it to do less work, and React does a lot of work.
107 |
108 | * **Vue** can track which values changed using getters and setters (accessors). In other words, your data forms the basis of a 'viewmodel', with accessors corresponding to the initial properties. Assigning to one of them — `vm.foo = 1` — schedules an update. This code is pleasant to write but is not without its downsides — it can result in unpredictable behaviour as values are passed around the app, and it encourages mutation. It also involves some minor computational and memory overhead, and some gotchas when setting as-yet-unknown properties (this will be fixed in Vue 3 with proxies, but presumably at the cost of IE support).
109 |
110 | * **Svelte** provides each component with a `set` method (and a corresponding `get` method) allowing the developer to make explicit state changes. (Those changes are fully synchronous, which is both good and bad — good because the mental model and resulting stack traces are simple, bad because it can result in layout thrashing, buggy lifecycles, and infinite loops that need to be guarded against.) This is verbose when mutating or copying objects, and puts valuable things like first-class TypeScript integration essentially out of reach.
111 |
112 | None of these are ideal. But [we're a compiler](https://mobile.twitter.com/Rich_Harris/status/1057290365395451905), which means we have tools in our toolbox that are unique to Svelte. What if we could solve reactivity *at the language level*?
113 |
114 | This proposal outlines a novel (as far as we're aware) solution, while bringing a number of significant benefits:
115 |
116 | * Easier-to-grok design
117 | * Less application code
118 | * Smaller bundles
119 | * Simpler, more robust lifecycles
120 | * A realistic path towards first-class TypeScript support
121 | * Well-defined contracts between components and their consumers
122 | * Opportunities to adopt ideas like [time slicing and Suspense](https://auth0.com/blog/time-slice-suspense-react16/)
123 |
124 |
125 | ## Detailed design
126 |
127 | Consider a counter component, the workhorse of examples such as these:
128 |
129 | ```html
130 |
133 |
134 |
137 | ```
138 |
139 | > The quotes around the event handler are unnecessary as far as Svelte is concerned, but included for the sake of non-Svelte-aware syntax highlighting
140 |
141 | Here, we have declared a single state variable, `count`, with an initial value of `0`. The code inside the `
163 |
164 |
167 | ```
168 |
169 | ...would be transformed to something like this:
170 |
171 | ```js
172 | function create_main_fragment(component, ctx) {
173 | // ... the code that creates and maintains the view —
174 | // this will be relatively unaffected by this proposal
175 | }
176 |
177 | const Component = defineComponent((__update) => {
178 | let count = 0;
179 | const incr = () => {
180 | count += 1;
181 | __update({ count: true });
182 | };
183 |
184 | return [
185 | // this creates the top-level `ctx` variable for `create_main_fragment`
186 | () => ({ count })
187 | ];
188 | }, create_main_fragment);
189 | ```
190 |
191 | **This behaviour might seem surprising, even shocking at first.** Variable assignment does not have side-effects in JavaScript, meaning that this is arguably something else — [SvelteScript](https://mobile.twitter.com/youyuxi/status/1057291776724271104), perhaps. In practice though, developers readily embrace 'magic' that makes their day-to-day lives easier as long as the mechanisms are ultimately easy to understand — something observed with React Hooks, Vue's reactivity system, Immer's approach to immutable data, and countless other examples. This does underscore the need for this code transformation to be well-documented and explained, however.
192 |
193 |
194 | ### Mutating objects and arrays
195 |
196 | While it's good practice to use immutable data in components, Svelte should not force developers to do so. Assigning to a property of an object or array would have the same basic behaviour — `a.b = c` would trigger an update on `a`. It's possible that changes could be tracked at a more granular level (i.e. tracking `a.b` rather than `a`, so that `
{a.d}
` is unaffected), but these are implementation details beyond the scope of this RFC.
197 |
198 | Calling array methods would *not* trigger updates on those arrays.
199 |
200 | > `x = x` can be used to 'force' an update on `x`, in the rare cases it is necessary
201 |
202 |
203 | ### Props
204 |
205 | Many frameworks (and also web components) have a conceptual distinction between 'props' (values passed *to* a component) and 'state' (values that are internal to the component). Svelte does not. This is a shortcoming.
206 |
207 | There is a solution to this problem, but brace yourself — this may feel a little weird at first:
208 |
209 | ```html
210 |
213 |
214 |
Hello {name}!
215 | ```
216 |
217 | Here, we are exporting a *contract* with the outside world — we are saying that a consumer of this component can specify a value for `name`, but that it will default to `world`:
218 |
219 | ```html
220 |
221 | ```
222 |
223 | This would compile to something like the following:
224 |
225 | ```js
226 | const Component = defineComponent((__update) => {
227 | let name = 'world';
228 |
229 | return [
230 | () => ({ name }),
231 | props => {
232 | if ('name' in props) name = props.name;
233 | }
234 | ];
235 | }, create_main_fragment);
236 | ```
237 |
238 | **This is an abuse of syntax**, but no worse than Svelte's existing crime of abusing `export default`. As long as it is well-communicated, it is easy to understand. Using existing syntax, rather than inventing our own, allows us to take full advantage of existing tooling and editor support.
239 |
240 | > Ordinarily in JavaScript, `export` means that other consumers of this module can read the value. In a sense, that's true (as we'll learn in the [Component API](#component-api) section), but here it also allows the consumer of this module to *write* the value. That's why we say that we're exporting a *contract* rather than a *value*.
241 |
242 | To summarise, there are **3 simple rules** for understanding the code in a Svelte component's `
272 |
273 |
The time is {format_time(time)}
274 | ```
275 |
276 | The `onDestroy` callback is associated with the component instance because it is called during instantiation; no compiler magic is involved. Beyond that (if it is called outside instantiation, an error is thrown), there are no rules or restrictions as to when a lifecycle function is called. Reusability is therefore straightforward:
277 |
278 | ```js
279 | // helpers.js
280 | import { onDestroy } from 'svelte';
281 |
282 | export function useInterval(fn, ms) {
283 | const interval = setInterval(fn, ms);
284 | onDestroy(() => clearInterval(interval));
285 | fn();
286 | }
287 | ```
288 |
289 | ```html
290 |
296 |
297 |
The time is {formatTime(time)}
298 | ```
299 |
300 | > This might seem less ergonomic than React Hooks, whereby you can do `const time = useCustomHook()`. The payoff is that you don't need to run that code on every single state change, and it's easier to see which values in a component are subject to change, and *when* a specific value is changing.
301 |
302 | There are three other lifecycle functions required — `onMount` (similar to `oncreate` in Svelte v2), `beforeUpdate` (similar to `onstate`) and `afterUpdate` (similar to `onupdate`):
303 |
304 | ```html
305 |
329 | ```
330 |
331 | > Note that the `changed`, `current` and `previous` arguments, present in Svelte 2, are absent from these callbacks — I believe they are now unnecessary.
332 |
333 | Currently, `onstate` and `onupdate` run before and after every state change. Because Svelte 2 doesn't differentiate between external props and internal state, this can easily result in confusing cyclicality:
334 |
335 | ```js
336 |
356 | ```
357 |
358 | Under this proposal, `beforeUpdate` callbacks run only once per cycle. This makes infinite loops impossible:
359 |
360 | ```html
361 |
378 | ```
379 |
380 | > Note that `previous_temperature`, if unused in the view, will not get the reactive treatment.
381 |
382 | Any `afterUpdate` callbacks would run after the view was updated, whether as a result of prop or state changes. Assignments in an `afterUpdate` callback would result in an immediate re-render (once all `afterUpdate` callbacks have run, instead of on the next frame) but would *not* cause the callback to run again. This would allow components to respond to layout changes, for example.
383 |
384 |
385 | ---
386 |
387 | The remainder of this section will address how existing Svelte concepts are affected by this RFC. Some things (CSS etc) are unmentioned because they are unaffected, though if items are missing please raise your voice.
388 |
389 |
390 | ### Directives
391 |
392 | In Svelte 2 there are six different kinds of directive:
393 |
394 | * `on:[event]` and `on:[event]=[callExpression]`
395 | * `use:[action]` and `use:[action]=[argument]`
396 | * `ref:[name]`
397 | * `in:[transition]`, `out:[transition]` and `transition:[transition]`, and `in:[transition]=[params]` etc
398 | * `bind:[name]` and `bind:remote=[local]`
399 | * `class:[name]` and `class:[name]=[expression]`
400 |
401 | Currently, directive values do not have curly braces as delimiters. This is an occasional source of confusion (and compiler complexity) that can be resolved by making directives more like attributes:
402 |
403 | ```html
404 |
405 |
...
406 |
407 |
408 |
...
409 | ```
410 |
411 | Even though this is two extra characters (we preserve the quotes because we care about syntax highlighting in non-Svelte-aware environments) it's arguably more readable and consistent. This pattern would be used across all directives, but there are particular changes that apply to `ref:` and `on:`, discussed below.
412 |
413 |
414 | #### `ref:`
415 |
416 | The `ref:` directive is anomalous in that it cannot have a value. This relates to a very common feature request — to be able to set refs inside `each` blocks. We can fix both things — and reduce API surface area — by replacing `ref:x` with `bind:this={x}`:
417 |
418 | ```html
419 |
420 | ```
421 |
422 | In Svelte 2, that element could be accessed (from `oncreate` onwards) as `this.refs.canvas`. Under this proposal, refs are simple variables:
423 |
424 | ```html
425 |
438 | ```
439 |
440 | (Since a binding can have any [l-value](https://en.wikipedia.org/wiki/Value_(computer_science)#lrvalue), we can also do `bind:this={array[index]}` inside `each` blocks.)
441 |
442 | The example above could compile to something like the following:
443 |
444 | ```js
445 | import { afterUpdate } from 'svelte';
446 |
447 | const Component = defineComponent((__update) => {
448 | let width;
449 | let height;
450 | let canvas;
451 | let ctx;
452 |
453 | afterUpdate(() => {
454 | if (!ctx) ctx = canvas.getContext('2d');
455 | draw_some_shapes(ctx);
456 | });
457 |
458 | return [
459 | () => ({ width, height }),
460 | props => {
461 | if ('width' in props) width = props.width;
462 | if ('height' in props) height = props.height;
463 | },
464 | refs => {
465 | if ('canvas' in refs) canvas = refs.canvas;
466 | }
467 | ];
468 | }, create_main_fragment);
469 | ```
470 |
471 | The same would apply to component refs.
472 |
473 |
474 | #### `on:`
475 |
476 | Currently, inline event handlers must be call expressions...
477 |
478 | ```html
479 |
480 | ```
481 |
482 | ...and the callee must be either a method of the component, the event, or some other whitelisted thing. It's weird, inconsistent and annoying to learn. Since there are no longer any problems around `this`, we can simplify things greatly by passing functions rather than their call expressions:
483 |
484 | ```html
485 |
486 | ```
487 |
488 | This also allows us to do fairly sophisticated things like this:
489 |
490 | ```
491 |
492 | ```
493 |
494 | In Svelte 2, there is a shorthand:
495 |
496 | ```html
497 |
498 |
...
499 |
...
500 | ```
501 |
502 | It's not yet clear if we should keep this, or replace it entirely with event bubbling, discussed [below](#events).
503 |
504 | **Custom events** in Svelte 2 enable you to use user-defined events in addition to built-in DOM events:
505 |
506 | ```html
507 |
drag me
508 |
509 |
518 | ```
519 |
520 | This doesn't translate well to Svelte 3. But we can do something better — we can use actions instead:
521 |
522 | ```html
523 |
528 |
529 |
drag me
530 | ```
531 |
532 | > (The 🐃 emoji used throughout this document indicates a yak that needs shaving.)
533 |
534 | The event system itself will be discussed in more detail [below](#events).
535 |
536 |
537 | ### namespace/tag options
538 |
539 | The default export from a Svelte 2 component can include compiler options — for example declaring a namespace (for SVG components) or a tag name (for custom elements):
540 |
541 | ```js
542 | export default {
543 | namespace: 'svg',
544 | tag: 'my-cool-thing'
545 | };
546 | ```
547 |
548 | Under this proposal, these options would be expressed by a new `` tag:
549 |
550 | ```html
551 |
552 | ```
553 |
554 | ### Script-less components
555 |
556 | At present it is possible to create components with no `
568 |
569 |
Hello {name}!
570 | ```
571 |
572 |
573 | ### Events
574 |
575 | Svelte 2 components can fire events with `this.fire(eventName, optionalData)`. These events can be listened to programmatically, with `component.on(eventName, callback)`, or declaratively in component markup:
576 |
577 | ```html
578 |
579 | ```
580 |
581 | Since there's no more `this`, there's no more `this.fire`, which means we need to rethink events. This gives us an opportunity to align with web components, where `CustomEvent` is used ([issue here](https://github.com/sveltejs/svelte/issues/1655)) — this also gives us event bubbling, which avoids needing to manually propagate events.
582 |
583 | The high-level proposal is to introduce a function called `createEventDispatcher` that would return a function for dispatching events:
584 |
585 | ```js
586 | import { createEventDispatcher } from 'svelte';
587 |
588 | const dispatch = createEventDispatcher();
589 |
590 | let i = 0;
591 | const interval = setInterval(() => {
592 | dispatch(i++ % 2 ? 'tick' : 'tock', {
593 | answer: 42
594 | });
595 | }, 1000);
596 | ```
597 |
598 | This would create a `CustomEvent` with an `event.type` of `tick` (or `tock`) and an `event.detail` of `{ answer: 42 }`.
599 |
600 | > 🐃 We could match the arguments to `new CustomEvent(name, opts)` instead — where `detail` is one of the options passed to the constructor alongside things like `bubbles` and `cancelable`
601 |
602 | > Note that `new CustomEvent` is unsupported in IE; legacy mode would need to use `document.createEvent('whatever')` instead
603 |
604 | As with lifecycle functions, the `dispatch` is bound to the component because of when `createEventDispatcher` is called.
605 |
606 |
607 |
608 | ### Component API
609 |
610 | Under this proposal, there is no longer a `this` with state and methods *inside* a component. But there needs to be a way to interact with the component from the outside world.
611 |
612 | Instantiating a top-level component remains the same, except that `data` is renamed to `props` to match the language used elsewhere:
613 |
614 | ```js
615 | import App from './App.html';
616 |
617 | const app = new App({
618 | target: document.querySelector('body'),
619 | props: {
620 | name: 'world'
621 | }
622 | });
623 | ```
624 |
625 | Exported properties can be exposed as accessors:
626 |
627 | ```js
628 | app.name; // world
629 | app.name = 'everybody'; // triggers an update synchronously
630 | ```
631 |
632 | This creates consistent behaviour between Svelte components that are compiled to custom elements, and those that are not, while also making it easy to understand the component's contract.
633 |
634 | Of the five **built-in methods** that currently comprise the [component API](https://svelte.technology/guide#component-api) — `fire`, `get`, `set`, `on` and `destroy` — we no longer need the first two. `on` and `destroy` are still necessary, and we'll keep `set` for cases where someone needs to change multiple props at the top-level with a single update.
635 |
636 | To differentiate those built-in methods from regular properties (so that people don't need to worry about potential conflicts), `on`, `destroy` and `set` become `$on`, `$destroy` and `$set`.
637 |
638 | In some cases, a component that is designed to be used as a standalone widget will create its own **custom methods**. In Svelte 2, these are lumped in with 'private' (except not really) methods. Under this proposal, custom methods are just exported variables that happen to be functions:
639 |
640 | ```html
641 |
652 |
653 |
654 | {#if visible}
655 |
now you see me
656 | {/if}
657 |
658 | ```
659 |
660 | ```js
661 | import Peekaboo from './Peekaboo.html';
662 |
663 | const peekaboo = new Peekaboo(...);
664 | peekaboo.show();
665 | ```
666 |
667 | An `export const`, `export function` or `export class` would be a signal to the compiler that a property is read-only — in other words, attempting to assign to it would cause an error.
668 |
669 |
670 | ### `preload` and `setup`
671 |
672 | There is a rather awkward mechanism for declaring static properties on a component constructor in Svelte 2 — the `setup` hook:
673 |
674 | ```html
675 |
682 | ```
683 |
684 | This is deeply weird, and due to an oversight (that we can't correct without a breaking change) only runs on the client.
685 |
686 | Since [Sapper](https://sapper.svelte.technology/) requires that components have some way of declaring their data dependencies prior to rendering, and since `setup` is so cumbersome, there is a special case made for `preload`. The `preload` function is attached to components on both client and server, and has no well-defined behaviour; it is purely convention.
687 |
688 | We can do better, **but it requires something potentially controversial** — a second `
698 |
699 |
702 |
703 | {#each things as thing}
704 |
{thing}
705 | {/each}
706 | ```
707 |
708 | `things` would be injected into the instance `
782 | ```
783 |
784 | That opportunity no longer exists in this RFC. Instead we need some other way to express the concept of 'all the properties that were passed into this component'. One suggestion is to use the `bind` directive on (🐃) the `` element described above:
785 |
786 | ```html
787 |
788 |
789 |
800 |
801 |
802 |
803 | ```
804 |
805 |
806 | ### Sync vs async rendering
807 |
808 | One of the things that differentiates Svelte from other frameworks is that updates are synchronous. In other words:
809 |
810 | ```js
811 | app.set({ size: 'supersize' });
812 | console.log(app.refs.box.offsetWidth); // already updated
813 | ```
814 |
815 | This is easily understood, and if there is an error during rendering it is easy to isolate the source of the problem since there is usually a short stack trace to the offending `set` call.
816 |
817 | But it also has major drawbacks. It can result in the cyclical behaviour observed above, and it provides the framework no opportunity to optimise updates by (for example) batching operations that are likely to result in DOM reads (such as transition or `onupdate` callbacks) separately from the framework-initiated cycle of DOM writes. It also prevents us from implementing ideas like [time slicing](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html).
818 |
819 | In addition, while it's possible to batch changes *within a component* (`this.set({ a, b, c })` results in a single update), changes *across* components (or those that affect stores and components together) are not batched. Now that `set` is no longer available, it is important that variable reassignments don't result in a sync update, but rather schedule an update (in a microtask).
820 |
821 | Exceptions to this rule could be made where appropriate. For example if you're tracking the height of a list...
822 |
823 | ```html
824 |
835 |
836 |
the list is {listHeight}px tall:
837 |
838 |
839 | {#each items as item}
840 |
{item}
841 | {/each}
842 |
843 | ```
844 |
845 | ...then you want the change to `listHeight` to be reflected in the view as soon as all the `onupdate` callbacks have fired, *not* after an animation frame (which would result in lag).
846 |
847 | Similarly, in terms of the public component API, it might be necessary for `customElement.foo = 1` to result in a synchronous update.
848 |
849 |
850 | ### Suspense
851 |
852 | This proposal isn't directly concerned with [implementing Suspense](https://github.com/sveltejs/svelte/issues/1736), but care should be taken to ensure that Suspense can be implemented atop this proposal without breaking changes.
853 |
854 |
855 | ### TypeScript
856 |
857 | A major advantage of this proposal over Svelte 2 is that components become far more amenable to typechecking and other forms of static analysis. Even without TypeScript, VSCode is able to offer much richer feedback now than with Svelte 2.
858 |
859 | An eventual goal is to be able to use TypeScript in components:
860 |
861 | ```html
862 |
865 |
866 |
Hello {name}!
867 | ```
868 |
869 | Editor integrations would ideally offer autocompletion and as-you-type typechecking inside the markup. This is not my area of expertise, however, so I would welcome feedback on this proposal from people who are more familiar with the TypeScript compiler API and ecosystem.
870 |
871 |
872 | ### Custom elements
873 |
874 | Svelte would continue to offer a custom element compile target. No real changes would be involved here, since this proposal brings 'vanilla' Svelte components in line with web components viz. property access and event handling.
875 |
876 | The `props` compiler option is redundant now that the contract is defined via `export`ed variables. The `tag` option can be set via `` as discussed above.
877 |
878 |
879 | ### Dependency tracking
880 |
881 | In Svelte 2, 'computed properties' use compile-time dependency tracking to derive values from state, avoiding recomputation when dependencies haven't changed. These computed properties are 'push-based' rather than 'pull-based', which can result in unnecessary work:
882 |
883 | ```html
884 | {#if visible}
885 |
{bar}
886 | {/if}
887 |
888 |
900 | ```
901 |
902 | In the example above, there is no real need to calculate `bar` since it is not rendered, but it happens anyway.
903 |
904 | [RFC 3](https://github.com/sveltejs/rfcs/pull/8) proposed a Svelte 3 friendly alternative to computed properties that is more flexible and involves less boilerplate.
905 |
906 | ### svelte-extras
907 |
908 | Most of the methods in [svelte-extras](https://github.com/sveltejs/svelte-extras) no longer make sense. The two that we do want to reimplement are `tween` and `spring`.
909 |
910 | Happily, these will no longer involve monkey-patching components:
911 |
912 | ```html
913 |
934 |
935 |