Recently, I've been experimenting with a new-ish JavaScript framework called Svelte. You might be thinking: "ANOTHER JavaScript framework?! But I already use React/Vue/Angular, what could yet another framework possibly offer me?" Someone once said that frameworks are not tools for organizing your code, they are tools for organizing your mind. Let's take a look into how Svelte wants us to rearrange our heads, and maybe that will offer us some new ideas along the way.
Frameworks are not necessarily about the efficiency of the code they generate. Frameworks try to make the difficult easier, eliminate the repetitive, and reduce opportunities for mistakes. For us as developers, frameworks should also provide a design metaphor that makes sense and is consistent. We can see these benefits in React, for instance. React's use of the virtual DOM works to make managing DOM repaints easier, less repetitive as well as less error-prone. The virtual DOM pattern/technique/metaphor has become very popular, and several other JS frameworks use some variation of it.
Svelte takes a different approach. Realizing that the virtual DOM is a trade-off, not necessarily an efficiency, Svelte tries to move the work that the virtual DOM does from the browser to the server. Let's break that idea down a bit. The virtual DOM is a trade-off, trading efficiency for an abstraction/simplification. The virtual DOM costs memory and processing, but it's easier to work with than managing the complicated and changing rules of DOM repaints and reflows. Ideally, it should get back some of the efficiency lost in the trade by making fewer updates to the DOM and batching them together. All this work is done in the browser, regardless of the library. This means that the end user is paying some of the cost of working with these client-side frameworks.
Svelte moves this work to the server by introducing a compile step into building your code. This is also a metaphor, since the JavaScript is still interpreted, not converted into binary. At compile-time, Svelte looks at your code and figures out where changes in the DOM will actually happen. The compiler zeroes in on the elements in the DOM that have variable values, and ties changes to those elements individually. Contrast this with component-based frameworks, which rely on developers to build components around changing and unchanging parts. With Svelte, developers can still write code that follows the metaphor of the component, but the compiler outputs vanilla JavaScript. The effort that other frameworks keep in the browser is offloaded to the compiler.
We should look at these differences in practice. Start with a basic demonstration of state, using React:
import React, { useState } from 'react'; function ReactState() { const [counter, setCounter] = useState(0); function handleClick() { setCounter(counter + 1); } return ( <button onClick={handleClick}>{counter} clicks</button> ) }
Click on the button, the counter on the button label increments. We are using the useState hook and an event handler. Any time the button is clicked on, it triggers setCounter which provokes re-rendering the button. Fine. Now, let's add a little more:
import React, { useState } from 'react'; function ReactState() { const [counter, setCounter] = useState(0); const [userName, setUserName] = useState(''); function handleClick() { setCounter(counter + 1); } function handleUpdate(event) { setUserName(event.target.value); } return ( <div> <label htmlFor="userName">Enter your name:</label> <input type="text" onChange={handleUpdate} value={userName} id="userName" /> <button onClick={handleClick}>{counter} clicks</button> <p hidden={userName.length < 3}>Hello, {userName}</p> </div> ); }
We added one more item of state, but we have now made our component quite a bit more complicated. Every time the user types something in the input field, the entire component is re-evaluated. That means that React, or any other virtual DOM-based framework, has to assess whether the div, the input, the button and/or the paragraph (p) has updated. This is the trade-off. No one is denying that this is easier to write and manage than vanilla JS or even jQuery, but it means that the browser is doing extra work every time the user interacts with this component. Depending on how the rest of the application is built out, this extra work builds up and eventually has a cumulative effect on the app's responsiveness.
Now, let's look at Svelte's approach:
<script> let userName=''; let counter = 0; function handleClick() { counter = counter + 1; } </script> <div> <label for="userName">Enter your name:</label> <input type="text" bind:value={userName} id="userName"/> <button on:click={handleClick}>{counter} clicks</button> <p hidden={userName.length < 3}>Hello, {userName}</p> </div>
As code, this does not look all that different from the React example. Unlike React, with its controlled components, Svelte has a simpler bind syntax to bind a variable to the value of a form field. Also, there is no explicit mention of state, useState or this.setState or similar. The variables count and userName are not particularly special. So how does Svelte make this work?
At this point, you should check out the Svelte REPL (read-eval-print-loop) at https://svelte.dev/repl/. The REPL lets you try out all sorts of Svelte experiments and, if you log in with GitHub, you can save your experiments, or download them as a zip file. Also, on the right-hand side of the REPL, you can view the rendered result of your code, as well as the output from the Svelte compiler. This is where we can dive into what Svelte is doing behind the scenes. You can either copy and paste the code above into the REPL, or you can look at the saved version here.
In the "JS output" view, we can see the code above transformed. Here is the relevant portion:
/* App.svelte generated by Svelte v3.42.4 */
/* Lots of code deleted for brevity */ function create_fragment(ctx) { /* Lots more code deleted for brevity */ return { p(ctx, [dirty]) { if (dirty & /*userName*/ 1 && input.value !== /*userName*/ ctx[0]) { set_input_value(input, /*userName*/ ctx[0]); } if (dirty & /*counter*/ 2) set_data(t3, /*counter*/ ctx[1]); if (dirty & /*userName*/ 1) set_data(t7, /*userName*/ ctx[0]); if (dirty & /*userName*/ 1 && p_hidden_value !== (p_hidden_value = /*userName*/ ctx[0].length < 3)) {
p.hidden = p_hidden_value; } }, }; } function instance($$self, $$props, $$invalidate) { let userName = ''; let counter = 0; function handleClick() { $$invalidate(1, counter = counter + 1); } function input_input_handler() { userName = this.value; $$invalidate(0, userName); } return [userName, counter, handleClick, input_input_handler]; }
The code is a little bit convoluted, and you are very much encouraged to look at the full version here. But, we can still see how Svelte optimizes our original code. In the p function, Svelte tries to figure out if userName or counter ever changes. If either does, the setData function is called (a Svelte internal) which will update the components affected. Note that changes to userName are decoupled from changes to counter!
In the instance function, Svelte transforms the event handler for handleClick into a call to another internal $$invalidate. This function registers that the variable counter has changes. In turn, this kicks off a call to the aforementioned p function, which will update the appropriate part of the component. Similarly, our binding of the input element has generated an event handler (saving us the trouble, unlike React), which uses $$invalidate to update the userName variable, provoking a re-rendering of its portion of the DOM.
One very important thing to note: the code above is generated at compile-time. In the real world, we would write this code as a separate Svelte component, ask Svelte to compile and build a bundle for us, and then serve that bundle from our web server. That means that this code, optimized and simplified and concentrating on making minimal changes to the DOM as necessary, is what is actually deployed to the browser. The code we wrote, in the previous snippet, is neither sent to, nor interpreted by the browser. Svelte is faster, because it's smaller and has much, much less overhead. The work of interpreting the framework's code happens at compile time at the leisure of the build system, rather than run-time, consuming the resources of the browser.
Svelte is a fascinating framework. In this article, we have only lightly scratched its surface. But we have also picked up an inside view into how Svelte works, and some of its architectural priorities. If you're interested in more explorations with Svelte, you can visit its homepage at https://svelte.dev/. Rich Harris, who is the main designer and organizer for Svelte, also gave a great talk two years ago called "Rethinking Reactivity" available at YouTube here. This article is a distillation of one or two of several ideas brought up in that talk. Finally, if you would like live hands-on training on Svelte for your team, please visit my friends at Accelebrate.
Written by John Paxton
Our live, instructor-led lectures are far more effective than pre-recorded classes
If your team is not 100% satisfied with your training, we do what's necessary to make it right
Whether you are at home or in the office, we make learning interactive and engaging
We accept check, ACH/EFT, major credit cards, and most purchase orders
Alabama
Birmingham
Huntsville
Montgomery
Alaska
Anchorage
Arizona
Phoenix
Tucson
Arkansas
Fayetteville
Little Rock
California
Los Angeles
Oakland
Orange County
Sacramento
San Diego
San Francisco
San Jose
Colorado
Boulder
Colorado Springs
Denver
Connecticut
Hartford
DC
Washington
Florida
Fort Lauderdale
Jacksonville
Miami
Orlando
Tampa
Georgia
Atlanta
Augusta
Savannah
Hawaii
Honolulu
Idaho
Boise
Illinois
Chicago
Indiana
Indianapolis
Iowa
Cedar Rapids
Des Moines
Kansas
Wichita
Kentucky
Lexington
Louisville
Louisiana
New Orleans
Maine
Portland
Maryland
Annapolis
Baltimore
Frederick
Hagerstown
Massachusetts
Boston
Cambridge
Springfield
Michigan
Ann Arbor
Detroit
Grand Rapids
Minnesota
Minneapolis
Saint Paul
Mississippi
Jackson
Missouri
Kansas City
St. Louis
Nebraska
Lincoln
Omaha
Nevada
Las Vegas
Reno
New Jersey
Princeton
New Mexico
Albuquerque
New York
Albany
Buffalo
New York City
White Plains
North Carolina
Charlotte
Durham
Raleigh
Ohio
Akron
Canton
Cincinnati
Cleveland
Columbus
Dayton
Oklahoma
Oklahoma City
Tulsa
Oregon
Portland
Pennsylvania
Philadelphia
Pittsburgh
Rhode Island
Providence
South Carolina
Charleston
Columbia
Greenville
Tennessee
Knoxville
Memphis
Nashville
Texas
Austin
Dallas
El Paso
Houston
San Antonio
Utah
Salt Lake City
Virginia
Alexandria
Arlington
Norfolk
Richmond
Washington
Seattle
Tacoma
West Virginia
Charleston
Wisconsin
Madison
Milwaukee
Alberta
Calgary
Edmonton
British Columbia
Vancouver
Manitoba
Winnipeg
Nova Scotia
Halifax
Ontario
Ottawa
Toronto
Quebec
Montreal
Puerto Rico
San Juan