Svelte: New Kid on the JavaScript Block

September 13, 2021 in Web Development Articles

Written by John Paxton


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

John Paxton

John is a trainer, programmer and consultant with more than twenty years of experience in JavaScript, Java, SQL, web applications, and other technologies. He has written several courses and one book and has taught on six continents.


Learn faster

Our live, instructor-led lectures are far more effective than pre-recorded classes

Satisfaction guarantee

If your team is not 100% satisfied with your training, we do what's necessary to make it right

Learn online from anywhere

Whether you are at home or in the office, we make learning interactive and engaging

Multiple Payment Options

We accept check, ACH/EFT, major credit cards, and most purchase orders



Recent Training Locations

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