iJS CONFERENCE Blog

Svelte vs. Angular vs. React vs. Vue - Who wins?

Comparing Svelte with other well-known frontend frameworks

Oct 4, 2022

Right now, there’s really no shortage of frontend frameworks. In addition to the top dogs Angular, React, and Vue, a fourth framework, Svelte, is shaking up the market.

There are lots of blog articles circulating online comparing the frameworks with one another. Depending on the author, they choose a different winner. In this article, we’ll take a less emotional look at the capabilities of the newcomer Svelte and see how the other frameworks tackle the respective problems. Of course, there is some additional background information.

Initialisation

The motto of many frameworks today is “just get started quickly”. Many solutions’ recipe for success is to provide developers with a fully functional environment as quickly as possible so that they can get started. Usually, initialisation takes place on the command line. This is also the case with Svelte. The project’s website presents two alternative ways to start developing a Svelte application. The first variant, using the REPL, is suitable for smaller experiments. With the second variant, you will use degit to set up the project. The Svelte REPL is a website where you can interactively build your application and immediately see the results. Once you’ve reached a state where you’re either satisfied or you’ve reached the limits of the REPL, you can download your project as a ZIP file and continue working on your system. Regardless of if you start with the REPL’s ZIP download or with the digit variant, the structures will match if you don’t make any further modifications in either. You’ll receive an application that you can use the basis of to immediately start development. The application uses Rollup as a bundler, but you can customise this. Here, most other frameworks rely on webpack to hide the occasionally quite complex configuration from developers. In Svelte, the bundler configuration is located in the application’s root directory and you can directly adapt it according to your requirements.

Svelte and TypeScript

In addition to how quickly you can start developing an application with a framework, other aspects that affect the development process also flow into the characterisation. For example, the programming language you use is of utmost importance. Although we generally speak of frontend frameworks in JavaScripts, this is only partly true. For example, Angular relies heavily on TypeScript. Pure JavaScript applications in Angular are unusual. When initialising the application with Vue, you have the choice between JavaScript and TypeScript. Although, the choice tends to be TypeScript here. React works more with JavaScript, but with Create React App, initialising a TypeScript app is very easy. Here, you can also see that most of the more extensive applications are developed in TypeScript.

 

iJS Newsletter

Keep up with JavaScript’s latest news!

 

Svelte apps are initialised in JavaScript by default. However, in the scripts directory there is a file called setupTypeScript.js that you can execute on the command line with the command node scripts/setupTypeScript.js. This file makes sure that your application is converted to TypeScript and that you can use TypeScript in your .svelte files. The script also adapts the development and build process so you do not have to make any further manual changes to your application.

Test environment

However, Svelte applications generated from scratch still have one small weakness compared to the other frameworks. Where other frameworks already provide a fully functional test environment, you have to take care of setup and configuration yourself with Svelte. The Angular CLI sets up a test environment with Karma and Jasmine. It also provides extensive auxiliary constructus so you can actively work with dependency injection in the test setup, for instance. With Vue, you can integrate Vitest and the Vue CLI will also create an environment and initial tests for automatically generated components. The situation with React is similar. The test framework is Jest. In this case, Create React App also generates exemplary tests for generated components.

The test setup in Svelte can also be done in just a few steps. You can use any of the popular testing frameworks, such as Jest. Additionally, the Svelte Testing Library is a tool that helps you formulate tests. An example of a simple component test is shown in Listing 1.

import '@testing-library/jest-dom';
 
import { render } from '@testing-library/svelte';
 
import Hello from '../Hello';
 
test('shows proper heading when rendered', () => {
  const { getByText } = render(Hello, { name: 'World' });
 
  expect(getByText('Hello World!')).toBeInTheDocument();
});

For step-by-step instructions on setting it up, see the Svelte Testing Library documentation at [1]. In the test, use the render function from the @testing-library/svelte package to render your component. As a return value, you’ll receive an object that you can use with the getByText method to locate a rendered element, for example. The test structure is similar to a typical React test, so React developers will quickly feel right at home.

Further integration possibilities

In addition to command line-based initialisation, some solutions like Vue advertise that it can be integrated into an existing website in just a few steps. Svelte works fundamentally differently than React or Vue, for instance. Because of this, integration into an HTML document isn’t exactly straightforward. It’s possible, but you usually use Svelte in the form of a single page application with a build process.

Components

Major modern single-page frameworks rely on a component-based approach. Svelte is no exception. The advantage is that if you already have experience with another framework, finding your way around is much easier. Components are relatively lightweight in Svelte. They consist of a script section for the component’s logic, an HTML structure that stands for the component’s structure, and an optional styling section. Like Vue, the styling specifications are limited to the component. So they only have an effect there and nowhere else in the application. Each component is in a separate file with the .svelte extension. The general structure of this component is strongly reminiscent of Vue’s Single File Components. In contrast, React takes a completely different approach and mixes logic and template in one function. On the other hand, Angular takes the three-way logic splitting, template, and styling even further and recommends you keep all three elements in separate files. Svelte uses a lightweight middle ground.

Each component can manage its own state. In contrast to the other frameworks, Svelte is very lightweight. If you define a variable in the script section, it represents a part of the component’s local state. Then, you can access it from the template. What’s interesting is that the change directly affects the visible structure in the browser. State management in React is much more complex. You can use the useState function to obtain an array that you can use to access the state in read mode via the first element. The second element, a function, is used to modify the state. To demonstrate, Listing 2 shows an example of local state management in comparison between React and Svelte.

<script>
  let count;
</script>
 
<div>
  <output>{count}</output>
  <button on:click={() => counter++}> increase </button>
</div>

Because of Svelte components’ architecture, there’s significantly less source code for this simple exercise than in React, for example. In React, you have to manage the component’s state via a separate state, which you can only modify using functions, not directly. But in a Svelte application, you can modify variables directly and change the state of your components. The biggest difference between established solutions like Vue, React, and Svelte is that Svelte transforms components during the build process, creating optimised structures. React also performs these tasks. Both code examples work the same, but React does the work at runtime instead of build time.

In the example, in addition to the state management, you can also see how you can react to events. In this case, it’s a click event on a button. The various solutions are similar here, with only different syntax. Whether you write on:click in Svelte, onClick in React, (click) in Angular, or @ click in Vue, the result is the same. They bind the user’s interaction—like a click—to a function or method of the component.

A special feature comes into play with state administration in Svelte. Sooner or later when working with Svelte, you’ll stumble across the $ sign in a component. This sign stands for reactivity integrated into the framework. For example, in React, if you have a local state in a component and the state’s value changes, then the component will be re-rendered. Dependent variables and expressions are re-evaluated. Here, Svelte works differently. As the simplest example, suppose you have two variables: firstname and lastname. Both form the full name and you store it in the variable fullname. Now, if one of the two variables changes, contents in fullname will remain unaffected by the change. Svelte doesn’t recalculate it unless you put a $: in front of the definition. Listing 3 shows a short example of this.

<script>
  let firstname = 'John';
  const lastname = 'Doe';
  $: const fullname = `${firstname} ${lastname};
 
  setTimeout(() => (firstname = 'Jane'), 1000);
</script>
 
<div>
  {fullname}
</div>

Component trees

As with all component-based frameworks, individual components in Svelte form a tree structure that information flows through. In Svelte, you include components as custom tags in your HTML structure. For this to work, you just need to import the child component in the component’s script section. Without the import statement, the browser acknowledges a reload with a ReferenceError. Svelte automatically provides the component of a .svelte file as a default export. Theoretically, this means that you can already choose any name during the import. However, if possible, you should use the same name as the component file to avoid confusion.

As previously mentioned, information flows through the component tree during the entire application runtime. In this component hierarchy, it’s common for higher-level components to be responsible for parts of the application state and pass it on to their child components. Svelte refers to transferring information as props. The term is also common in React and Vue. But in Angular, we speak of Input Binding. The principles are the same everywhere. They pass information from parent components to child components. Svelte takes a somewhat unconventional approach. You can define variables in the script section of a component in order to manage the local state. These variables are only valid locally in the component. But if you export them, they become props and an initial value assignment sets the default value for the prop.

 

EVERYTHING AROUND ANGULAR

The iJS Angular track

 

One interesting thing is the inverse data flow—the transfer of information from the child component to the parent component. One example of this is if you wanted to change the state of a specific record in a list display from the child component. Different frameworks handle this in very different ways. In React, you simply pass a function from the parent to the child component and execute it when needed. Meanwhile, Angular offers the @Output Decorator for this. It helps you send events to the parent class with the EventEmitter class and reacts to them accordingly. Svelte provides several options for communication from the child to parent component. You can work with bind:property. This lets you modify specific information of the parent component from the child component. This change will also update the users’ views. Another possibility, similar to Angular, is to build an event system. For this, Svelte provides the createEventDispatcher function. When called, it returns a function that you can use to create a user-defined event. Listing 4 shows an example of this implementation.

// List.svelte
<script>
  import ListItem from './ListItem.svelte';
 
  let items = [
    { id: 1, title: 'Get up', done: true, },
    { id: 2, title: 'eat', done: false, },
    { id: 3, title: 'code', done: false, },
  ];
 
  function handleDone({ detail: item }) {
    items = items.map((i) => {
      if (i.id === item.id) {
        i.done = !i.done;
      }
      return i;
    });
  }
</script>
 
<ul>
  {#each items as itemz}
    <ListItem item={itemz} on:done={handleDone} />
  {/each}
</ul>
 
 
// ListItem.svelte
<script>
  import { createEventDispatcher } from 'svelte';
 
  export let item = {};
 
  const dispatch = createEventDispatcher();
 
  function handleDone() {
    dispatch('done', item);
  }
</script>
 
<li>
  <div>
    <span>{item.title}</span>
    <button on:click={handleDone}>
      {item.done ? 'done' : 'not done'}
    </button>
  </div>
</li>

In the child component, create the dispatch function with the createEventDispatcher function. When you call it, you pass the name of the event you want to trigger—in this case, done—and the data you want to pass. In the parent component, you can react to the event with on:done and get access to the event object in the callback function. The passed object is located in its detail property.

Component lifecycle

Components have a lifecycle in all popular frameworks. It ranges from the creation of the component to its removal from the component tree. And Svelte is no exception. Only the type of implementation differs from framework to framework. React follows a rather unusual strategy with the Effect Hook, since the various lifecycle hooks are combined into just one function. On the other hand, with Angular and Vue, you implement the lifecycle hooks as methods. For example, in Vue they are called created, mounted, or unmounted. In Angular, they have names like ngOnInit, ngOnChanges, or ngOnDestroy. In Svelte, you import the following lifecycle hooks from the Svelte package and call them with a callback function:

  • onMount: Once the component has been inserted into the DOM tree, the passed function is executed. Similar to React, if the callback function returns a function, then this function is executed when the component is removed from the DOM tree. Usually, you place the logic that takes care of loading data from the server in the onMount hook.
  • beforeUpdate: Svelte executes the callback function of the beforeUpdate function right before the component is updated by a state change.
  • afterUpdate: In a way, the afterUpdate function is the counterpart to the beforeUpdate function. Your callback function is executed after a component has been updated because of a state change.
  • onDestroy: This lifecycle hook is executed immediately before the component is unhooked. In the callback function, you usually perform clean-up tasks, like closing open resources.

Templates

Besides State, Props and the Lifecycle, handling templates is another important aspect of Svelte. As you’ve already seen, Svelte uses ordinary HTML as a template language. You can also use curly brackets to indicate that you want to access the state or evaluate a JavaScript expression. Other frameworks also apply this principle in a similar form. Angular calls this evaluation in the templates interpolation and uses opening and closing brackets. Angular solves conditions and loops with directives, which you can use as attributes in components. So you can only use *ngIf components to render under certain conditions and *ngFor helps you process arrays in loops.

Like Angular, Vue also uses two pairs of curly braces for interpolation. And like Svelte, React uses a simple pair of braces instead. Vue also follows the Angular model for conditions and loops, providing appropriate attributes for your template with v-if and v-for. Once again, React goes in a completely different direction with its JSX templates. For conditions, use either JavaScript if-statement or the ternary operator. You implement loops with the map method of an array object to create components from array elements, which React displays.

Svelte takes a different approach to working with conditions and loops and gives you expressions in the template. You can formulate conditions with #if and iterate over array structures with #each. You’ve already seen an example of this kind of iteration in Listing 4.

Forms

Single page applications live from interaction with users. Typically, these interactions are clicks on elements like buttons. If this simple way isn’t sufficient, you can usually use forms for data entry. Different frameworks take different approaches here. These range from React’s minimalist approach, which only provides the technical basis for form management, to Angular’s extensive form handling. In Svelte, you can easily link different form elements to a component’s state. For this link, use the element directive bind:property, in this case bind:value. Listing 5 shows an example of a simple registration form.

<script>
  const data = {
    username: '',
    password: '',
  };
 
  function handleSubmit() {
    console.log(data);
  }
</script>
 
<form on:submit|preventDefault={handleSubmit}>
  <label>
    Username:
    <input
      type="text"
      name="username"
      placeholder="Username"
      bind:value={data.username}
    />
  </label>
  <label>
    Password:
    <input
      type="password"
      name="password"
      placeholder="password"
      bind:value={data.password}
    />
  </label>
  <button type="submit">Submit</button>
</form>

The script section defines the state of the component and a handler function for the Submit. In the template itself, the handler function binds to the Submit event of the form. In the template itself, the handler function binds to the submit event of the form. At this point, it’s important to remember to use preventDefault to prevent the browser from trying to send the form to the server. Within the form, use the bind:value that we mentioned to synchronise the state with the form.

At this point, Svelte doesn’t specify how you validate forms. This means that you either have to handle it yourself or include an additional library, svelte-forms.

The Context API

If the component tree becomes more extensive, and you need to access a piece of information from several places, you must bind this state to a common parent component and forward the information as a prop to the child components that need the access. Depending on the length of the path between the source and destination components, many components may only need to pass information. This disadvantage of this solution approach is that the intermediate components become dependent on the data flow, limiting reusability. To solve this problem, many frameworks provide mechanisms that let you access central structures in a lightweight way, without having to pass them through the entire component tree.

For example, you can solve this in Angular with services registered in a common module. You can use these services to access both read and write data. In Vue, you can implement the passing of information across multiple component levels with the provide and inject properties. In React, you can use the Context API to pass information from parent to child component independently of the component hierarchy. Svelte takes a similar approach to React with its Context API. Svelte lets you save any structures in the context. This means you can store primitive data types like numbers or strings, as well as object types. Use the setContext function to write a value to the context and access the value in all child components of the component with the getContext function. Listing 6 shows a simple example of using the Context API.

// Parent.svelte
<script>
  import { setContext } from 'svelte';
  import Child from './Child.svelte';
 
  let todos = [
    { id: 1, title: 'Get up', completed: true, },
    { id: 2, title: 'eat', completed: false, },
    { id: 3, title: 'code', completed: false, },
  ];
 
  function markDone(id) {
    todos = todos.map((todo) => {
      if (todo.id === id) {
        todo.completed = !todo.completed;
      }
      return todo;
    });
  }
 
  setContext('markDone', markDone);
</script>
<div>
  {#each todos as todo}
    <Child {todo} />
  {/each}
</div>
 
// Child.svelte
<script>
  import { getContext } from 'svelte';
 
  export let todo = {};
  const markDone = getContext('markDone');
</script>
 
<div>
  {todo.title}
  <button on:click={() => markDone(todo.id)}>
    {todo.completed ? 'Undo' : 'Done'}
  </button>
</div>

In the parent component, define an array of todos and a markDone function. This accepts a record’s ID and makes sure that the completed status of the corresponding record is reversed. Since the function is in the current component context and overwrites the todo array, the function call automatically makes sure that the display is updated.

More features

Svelte offers many other features, like stores and transitions. Stores give you similar functionality to the Context API, the difference being that they offer a little more structure. Transitions and Animations let you integrate animations and transitions natively into Svelte.

Other features—like routing in your application or internationalisation—you’ll either have to implement yourself or use libraries such as svelte-navigator or svelte-i18n. As a rule, you’re better off relying on existing, established solutions. These additional libraries offer similar functionality to their counterparts in other frameworks. For example, the svelte-navigator package is based on React Router’s structure and syntax, where you build the routing definition of your application declaratively with components of the package. Listing 7 shows a simple example of this kind of routing configuration.

<script>
  import { Router, Link, Route } from "svelte-navigator";
  import List from "./List.svelte";
  import Form from "./Form.svelte";
</script>
 
<Router>
  <nav>
    <Link to="/">List</Link>
    <Link to="form">Form</Link>
  </nav>
  <div>
    <Route path="/">
      <List />
      </Route>
    <Route path="form" component={Form} />
  </div>
</Router>

By default, the svelte-navigator package uses the browser’s HTML5 history API to modify the browser’s address bar and save the history stack, supporting forward and backward navigation. You can use the memory mode to test the application. In addition to these two modes, most router libraries of other frameworks still support the older, no longer recommended Hash mode. Navigation takes place via the hash part of the URL and the hashChange event of the browser. Implementing this mode as a custom history is up to you with svelte-navigator.

Something similar to routing applies to other libraries in the Svelte ecosystem. If you have experience with another framework, you’ll find specific features either directly in Svelte or an additional library. Most of the time, approaches in Svelte work similarly to proven solutions that already exist in other frameworks.

Conclusion

Svelte isn’t really a new framework. Version 3 is now available and the last major version subjected it to extensive refactoring, advancing the framework’s victory march. Unlike other major SPA frameworks, Svelte takes a slightly different approach by compiling the source code files with the .svelte extension, converting them into JavaScript code. Whereas, the other frameworks evaluate the source code at runtime. This is one of the reasons why a relatively small framework has become serious competition for existing solutions. At its core, Svelte is a lightweight framework with a rather small memory footprint, which also improves application load times.

In many other areas, Svelte follows similar approaches to those found in the other frameworks. Svelte uses a component-based architecture to build an application. Data usually flows from the parent components to the child components. In turn, the child components can communicate with their parent components using events. You can also break out of this pattern using the Context API and the stores to access central information directly. This way you don’t have to pass the data through many unnecessary component layers.

But Svelte doesn’t only have advantages. The framework has very good documentation, it’s easy to understand and has a low entry barrier, but its community is still relatively small compared to Angular, React, and Vue. Acceptance of the framework in larger companies isn’t up to the same level as the competition. However, these problems will solve themselves over time if development continues as it has and more and more developers give the framework a chance.

 

Links & Literature

[1] https://testing-library.com/docs/svelte-testing-library/setup 

Sign up for the iJS newsletter and stay tuned to the latest JavaScript news!

 

BEHIND THE TRACKS OF iJS

JavaScript Practices & Tools

DevOps, Testing, Performance, Toolchain & SEO

Angular

Best-Practises with Angular

General Web Development

Broader web development topics

Node.js

All about Node.js

React

From Basic concepts to unidirectional data flows