2 Your first Svelte app

This chapter covers

In this chapter, you will learn everything you need to know to experiment with Svelte without having to download or install anything. This will be accomplished using a web-based REPL tool.

REPL stands for read, evaluate, print, loop. A REPL reads the code you enter, evaluates it (possibly compiling and reporting errors), prints the result of the code, and loops to allow for repeating this process. There are REPLs for many programming languages and web frameworks.

The REPL is a perfect gateway to developing larger apps locally, outside of the REPL, using common programming editors such as VS Code. This chapter will cover how to download REPL apps for further local development and how to create an app from scratch without using the REPL.

By the end of the chapter, you will be ready to start developing your own Svelte applications.

2.1 The Svelte REPL

Svelte provides a browser-based REPL that supports defining Svelte components, seeing their rendered output, and more. Using the REPL is the easiest way to experiment with Svelte. Your first Svelte application is just a click away!

To get started, browse to the main Svelte website at https://svelte.dev/. Then click the REPL link near the upper-right corner of the page.

Code for a Hello World app is provided by default, as shown in figure 2.1. You can modify this code to experiment with the various features of Svelte.

Figure 2.1 The initial REPL screen

Experimenting is just one use of the REPL. Another is creating an ever-growing collection of examples you can draw from as you learn about Svelte. I highly encourage you to take advantage of this feature.

2.1.1 Using the Svelte REPL

Initially, the only file provided in the REPL is App.svelte. This file can import other files defined in additional tabs within the REPL (not in separate browser tabs).

To add more .svelte or .js files, click the plus button (+) to the right of the existing file tabs, and give the new file a name. By default, newly created files have a .svelte extension, but this can be changed to .js by typing .js at the end of the filename.

Note The REPL error “Failed to construct 'URL': Invalid base URL” typically means that a file created in the REPL is missing its file extension and couldn’t be imported.

To delete a file, click its tab and then click the “X” that appears to the right of the filename.

The right side of the REPL page contains three tabs:

The top bar in the REPL (see figure 2.2) contains links to many Svelte resources including a tutorial, the API docs, examples, the Svelte blog, the Svelte FAQ, the main page for Sapper, the Discord chat, and the Svelte GitHub repo.

Figure 2.2 The Svelte website header

To hide this bar and gain more editing room, click the full-screen editor icon, which looks like a dashed square (see figure 2.3).

Figure 2.3 The REPL full screen button

After you click it, this icon will change to an “X” (see figure 2.4). Click it to restore the top bar.

Figure 2.4 The REPL exit full screen button

There is currently no option in the REPL to format .svelte files. Perhaps an option to do this using Prettier will be added in the future.

Note Prettier (https://prettier.io) is a very popular tool for formatting code from many languages, including JavaScript, HTML, CSS, and more.

To reset the REPL to its starting point, which is a basic Hello World app, click the REPL link in the header (see figure 2.5).

Figure 2.5 The REPL reset button

2.1.2 Your first REPL app

Using only what you have learned so far, let’s create a simple app and begin learning about some features of Svelte.

The provided Hello World app always says hello to “world.” Let’s change this so it can say hello to anybody. Add the following HTML before the h1 element:

<label for="name">Name</label>
<input id="name" value={name}>

Note The input HTML element is an example of an empty element, which means it cannot have child nodes. When empty elements are used in Svelte components, they do not need to be terminated with />. However, if Prettier is used to format the code, it will change empty elements so they’re terminated this way. This is why some code examples in this book show terminated empty elements.

Now we can enter a name, but it doesn’t change the greeting. We need to add event handling so the value of the name variable is updated when the user types in the input. We can do this using an inline arrow function or a reference to a function defined in the script element. Change the input to the following:

<input
  id="name"
  on:input={event => name = event.target.value}
  value={name}
>

This works, but it’s a bit verbose. We can do better using the Svelte bind directive. You will see many uses of the bind directive later, but one use is to bind the value of a form element to a variable. This causes the form element to display the current value of the variable. If a user changes the value of the form element, the variable will be updated to match.

Change the input element to match the following:

<input id="name" bind:value={name}>

Oh my, that’s nice! And if the variable name is value instead of name, it can be shortened as follows:

<input id="name" bind:value>

This isn’t very stylish, though. Let’s add styling to change the color of the greeting. Add the following at the bottom of the file, after the HTML.

<style>
  h1 {
    color: red;
  }
</style>

Great! Now the greeting is red.

Let’s allow the user to select a color. We will do this by adding an input element with a type of color. This uses the native color picker to select a color. It appears as a rectangle containing a horizontal line in figure 2.6. Clicking it opens a Color Picker dialog.

Figure 2.6 A REPL app with color input

Top-level variables in the script element of a component define the state of the component. For example, the state of the component below includes the color and name variables.

Here is the full code for this version of App.svelte.

Listing 2.1 A REPL app with color input

<script>
  let color = 'red';
  let name = 'world';
</script>

<label for="name">Name</label>
<input id="name" bind:value={name}>

<label for="color">Color</label>
<input id="color" type="color" bind:value={color}>
<div style="background-color: {color}" class="swatch" />

<h1 style="color: {color}">Hello {name}!</h1>

<style>
  .swatch {
    display: inline-block;
    height: 20px;
    width: 20px;
  }
</style>

Let’s allow the user to toggle a checkbox to change the case of the greeting. Add the following inside the script element, at the bottom:

let upper = false;
$: greeting = `Hello ${name}!`;
$: casedGreeting = upper ? greeting.toUpperCase() : greeting;

What does $: mean? It’s called a reactive statement. Reactive statements are re-executed any time the value of a variable they reference changes. Reactive statements that assign a value to a variable are also called reactive declarations. They are covered in more detail in chapter 3.

In the preceding code above we calculate a new value for greeting any time the value of name changes. Then we calculate a new value for casedGreeting any time the value of upper or greeting changes. This is incredibly convenient!

Now we just need a way to change the value of upper when a checkbox is toggled, and to render the value of casedGreeting. Add the following code before the h1 element to render the checkbox, as shown in figure 2.7.

Figure 2.7 A REPL app with an Uppercase checkbox

<label>
  <input type="checkbox" bind:checked={upper}>
  Uppercase
</label>

Change the h1 element to the following:

<h1 style="color: {color}">{casedGreeting}</h1>

So far we have implemented everything in a single .svelte file named App.svelte. Let’s move beyond this by defining a second component.

Click the plus button (+) to the right of the App .svelte tab in the REPL. Enter Clock for the name of the component, as shown in figure 2.8. Component names and their source file names should use camel case, starting with an uppercase letter.

Figure 2.8 REPL app with Clock tab

We want this component to display the current time in hh:mm:ss format.

Start by adding only the following in Clock.svelte:

<div>
  I will be a clock.
</div>

Back in App.svelte, add the following inside the script element, at the top:

import Clock from './Clock.svelte';

Add the following at the bottom of the HTML in App.svelte:

<Clock />

Note The space before the slash in <Clock /> is not required, but many developers, and the Prettier code formatter, prefer to include it.

You will now see “I will be a clock.” at the bottom of the rendered output (see figure 2.9).

Figure 2.9 A REPL app with “I will be a clock.”

Props allow data to be passed into components (they will be discussed in detail in chapter 5). Svelte uses the export keyword to define a prop that a component accepts. In a .js file, export makes a value visible outside that file and able to be imported. When you use export in a component, other components that use this component can pass in a value.

Change Clock.svelte so it contains the following. In this component, color is a prop with a default value.

<script>
  export let color = 'blue';                     
  let hhmmss = '';
  setInterval(() => {
    hhmmss = new Date().toLocaleTimeString();    
  }, 1000);
</script>
 
<span style="color: {color}">{hhmmss}</span>

This defines the color prop.

This is executed once every second.

 

 

Modify App.svelte to pass a color prop to the Clock component. The following line demonstrates a shorthand syntax that is equivalent to <Clock color={color} />.

<Clock {color} />

We now have a functioning clock component, as shown in figure 2.10.

Figure 2.10 A REPL app with a clock

This gives you a taste of some of what you can do in Svelte. We will dive deeper in the chapters that follow.

If you are familiar with other web frameworks, now is a good time to pause and think about what code you would need to write to implement this in those frameworks. Is it longer? Is it more complicated?

Figure 2.11 The REPL Log In to Save button

2.1.3 Saving REPL apps

To save apps created in the REPL so they can be recalled in the future, click the Log In to Save button in the upper right (see figure 2.11). This will open the browser window shown in figure 2.12.

Figure 2.12 The GitHub Sign In page

Enter your GitHub username and password, and click the Sign In button.

If you do not already have a GitHub account, browse to https://github.com/ and click the large, green Sign Up for GitHub button (see figure 2.13). After signing into GitHub, you will be returned to the REPL.

Figure 2.13 The GitHub Sign Up page

To save your current app, enter a name for it on the left side of the gray bar at the top, and click the floppy disk icon (see figure 2.14). Pressing Ctrl-S (or Cmd-S on macOS) also saves the app.

Figure 2.14 The REPL save button

To load a previously saved app, hover over your username at the upper-right of the page, and click Your Saved Apps (see figure 2.15).

Figure 2.15 The REPL Your Saved Apps menu option

This will display a list of saved app names. Click one to load it.

To make a copy of your current app so it can be modified without changing the current version, click the fork button (see figure 2.16).

Figure 2.16 The REPL fork button

Currently it is not possible to delete saved REPL apps, but this feature has been requested (see https://github.com/sveltejs/svelte/issues/3457). Until it is added, consider renaming REPL apps you no longer need to something like “delete me.”

2.1.4 Sharing REPL apps

To share a REPL app with other developers, copy its URL from the browser address bar. Then provide it to others via chat, text message, or email. They will be able to modify the REPL app and save the changes as their own app, but they cannot modify your version of the app.

Sharing REPL apps is great for asking questions in the Svelte Discord chat. Post a question along with a REPL URL that demonstrates something you would like to share.

2.1.5 REPL URLs

The URL of every REPL app ends with a query parameter named version that specifies the version of Svelte being used. It defaults to the newest version, but you can change this to test the REPL app with a different version of Svelte.

This is particularly useful if you suspect a bug has been introduced in Svelte. You can try the REPL app in several versions and determine if its behavior changes.

2.1.6 Exporting REPL apps

At some point in the development of an app in the REPL, you may want to break out of the REPL. There are many reasons to do this:

To download the current app so you can continue developing it locally, click the download button (see figure 2.17). This downloads a zip file whose name defaults to svelte-app.zip.

Figure 2.17 The REPL download button

Follow these steps to run the app locally:

If Node.js is not already installed, install it from https://nodejs.org. This installs the node, npm, and npx commands.

  1. Unzip the downloaded zip file.

  2. cd to the directory it creates.

  3. Enter npm install.

Enter npm run dev to run the app in development mode.

Running Svelte apps outside of the REPL in this way will be covered in more detail later.

2.1.7 Using npm packages

REPL apps can import functions and other values from npm packages. For example, to use the capitalize function from the lodash package, first import it:

import capitalize from 'lodash/capitalize';

You can then call this function with capitalize(someString).

Alternatively, the entire library can be imported.

import _ from 'lodash';

Note An underscore is a valid variable name in JavaScript. It is commonly used as the name of the variable that holds the Lodash library because an underscore looks like a “low dash.”

With this kind of import, you can call the capitalize function with _.capitalize (someString).

The REPL obtains npm package code from https://unpkg.com, which is a content delivery network (CDN) for all the packages in npm. To import a specific version of an npm package, follow the name with @ followed by the version. For example,

2.1.8 REPL limitations

The Svelte REPL is great, but it does have some limitations. It cannot do these things:

2.1.9 CodeSandbox

CodeSandbox provides an alternative to the Svelte REPL for building Svelte applications without downloading or installing anything. This is an online version of the VS Code editor.

To use it, browse to https://codesandbox.io. No signup is required. To save your work, sign in with your GitHub account.

To create a Svelte project, click the + Create Sandbox button. Under Official Templates, select Svelte.

To enable Vim keybindings, select File > Preferences > CodeSandbox Settings > Editor and toggle Enable VIM Extension to on.

Apps developed in CodeSandbox can be deployed to Vercel Now or Netlify by clicking the left navigation button containing a rocket icon, and then clicking the Deploy to Vercel or the Deploy to Netlify button. This will take a couple of minutes to complete. For Vercel Now, click the supplied link to visit the new app. For Netlify, click the Visit button to visit the new app, and click the Claim button to add the app to your Netlify dashboard.

Note Deploying Svelte apps using Netlify and Vercel Now is covered in more detail in chapter 13. The ZEIT company was renamed to Vercel in April 2020.

CodeSandbox supports a Live mode where you can live-edit the files of an application collaboratively with others.

2.2 Working outside the REPL

There are two common ways to create a Svelte application that can be developed and run outside the REPL.

You have already seen the first option. An app created in the REPL can be downloaded as a zip file, and unzipping this provides all the files you need to get started. It is not necessary to enter any code in the REPL. It is sufficient to download the example Hello World app that the REPL provides, and modify the code from that starting point.

The second option is to use the npx tool that is installed with Node.js to execute the degit command. Rich Harris, the creator of Svelte, created degit to simplify project scaffolding. Think of the “de” prefix in the name as meaning “out of.” It simply downloads a Git repository that contains a predefined directory structure, including starting files. By default, it downloads the master branch.

It is helpful to use a powerful code editor when working outside the REPL. Although any code editor or IDE can be used, VS Code is recommended. Details on using VS Code to develop Svelte and Sapper apps, including the use of specific extensions, is provided in appendix F.

2.2.1 Starting with npx degit

Let’s walk through the steps for creating and running a Svelte application using the degit command.

  1. Enter npx degit sveltejs/template app-name.

    In this case, sveltejs is the organization, and template is the name of a repository owned by that organization.

    The second argument is the name of the subdirectory to create, and it is also the name of the application.

    This repository uses the Rollup module bundler (https://rollupjs.org). The provided rollup.config.js file uses rollup-plugin-terser which uses the terser library. This minimizes the resulting JavaScript bundle when building for production using npm run build.

    Webpack (https://webpack.js.org/) is a module bundler that is an alternative to Rollup. If there are specific Webpack plugins you would like to use, enter the following to create a Svelte project that uses Webpack for module bundling instead of Rollup: npx degit sveltejs/template-webpack app-name

    Parcel (https://parceljs.org/) is another module bundler that is an alternative to Rollup. Currently there is no official support for Parcel in Svelte, but unofficial support is available at https://github.com/DeMoorJasper/parcel-plugin-svelte.

  2. Enter cd app-name.

  3. Enter npm install to install all the required dependencies from npm. To use TypeScript instead of JavaScript, enter node scripts/setupTypeScript.js. This will modify the project to use TypeScript and delete the scripts directory.

  4. Enter npm run dev to run the app in development mode.

    This starts a local HTTP server. It also provides live reload, which rebuilds the app and refreshes it in the browser if any source files used by the app are modified using any editor.

    An alternative way to run the app is to enter npm run build followed by npm start. This omits live reload. The build step is required to create the bundle files that npm start expects to exist in the public directory.

  5. Browse to localhost:5000, which renders the page shown in figure 2.18.

    Note that the default Webpack configuration uses port 8080 instead of 5000.

Figure 2.18 The sveltejs/template Hello World app

Module bundlers

A JavaScript module bundler combines the JavaScript code (and sometimes other assets) needed by an application into a single JavaScript file. This includes JavaScript library dependencies such as those from npm (https://npmjs.com). It can also remove code that is not used and minimize the remaining code. All of this reduces the time required to download a JavaScript-based app to a web browser.

There are several popular JavaScript module bundlers, the most popular of which are Webpack, Rollup, and Parcel. Rollup was created by Rich Harris, the creator of Svelte.

Regardless of whether the app is built by entering npm run dev or npm run build, the following files will be created in the public directory:

The .map files support debugging the app. They map the generated code to the code you wrote so you can view and step through the code in a debugger.

Now you are ready to start modifying the app. If the browser doesn’t render the expected output after you save your changes, check for compilation errors in the terminal window where the server is running. The Svelte compiler is unable to parse some syntax errors (such as unbalanced braces) and a message describing the issue may appear there. These error messages cannot be displayed in the browser because Svelte doesn’t produce a new version of the app if there are syntax errors. For this reason, it is recommended that you keep the window where npm run dev is running in view.

Tip Sometimes npm run dev is able to start the server despite warning messages being output. These may scroll off the screen, but you can scroll backwards in the window where npm run dev is running to see them.

Tip If you are not seeing the results of your latest code changes, it could be because you ran npm start instead of npm run dev. The former command runs the code that was compiled the last time you ran npm run build or npm run dev. It’s easy to make this mistake because in many other web frameworks npm start is the command that runs the app locally in dev mode with live reload.

2.2.2 Provided package.json

A peek at the package.json file reveals two things.

The first is that Svelte uses Rollup by default for module bundling, as seen in all the references to rollup in the devDependencies. If desired, you can change this to use Webpack or Parcel.

The second is that Svelte apps have only one required runtime dependency, sirv-cli, which provides the local HTTP server used by the npm start command. To see this, look at the values of dependencies and devDependencies in package.json. No other runtime dependencies are required because all the code needed by Svelte apps is included in the bundle.js file that the Svelte compiler produces.

2.2.3 Important files

The most important starting files in a Svelte app are public/index.html, src/main.js, and src/App.svelte. All of these can be modified to suit the needs of the application being developed.

These files use tabs for indentation, but feel free to change these to spaces if tabs aren’t your cup of tea. As you will see later, Prettier can do this for you, changing all indentation to use either spaces or tabs.

The public/index.html file contains the following:

Listing 2.2 The public/index.html file

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Svelte app</title>
    <link rel="icon" type="image/png" href="/favicon.png" />
    <link rel="stylesheet" href="/global.css" />
    <link rel="stylesheet" href="/build/bundle.css" />
 
    <script defer src="/build/bundle.js"></script>
  </head>
  <body>
  </body>
</html>

Note that this includes two CSS files and one JavaScript file. The global.css file holds CSS that can affect any component. The bundle.css file is generated from the CSS in each component .svelte file. The bundle.js file is generated from the JavaScript and HTML in each component .svelte file and any other JavaScript code the components use.

The src/main.js file contains the following:

Listing 2.3 The src/main.js file

import App from './App.svelte';
 
const app = new App({
  target: document.body,
  props: {
    name: 'world'
  }
});
 
export default app;

This renders the App component. The target property specifies where the component should be rendered. For most apps this is the body of the document.

This code passes the name prop to the App component. Typically the topmost component does not need props, and the props property seen here can be deleted. This is because any data that is hardcoded in main.js can alternatively be hardcoded in App.svelte instead of passing it in through props.

The src/main.js file is expected to make the instance of the topmost component, App in this case, be the default export.

The src/App.svelte file contains the following:

Listing 2.4 The src/App.svelte file

<script>
  export let name;
</script>
 
<main>
  <h1>Hello {name}!</h1>
  <p>
    Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a>
    to learn how to build Svelte apps.
  </p>
</main>
 
<style>
  main {
    text-align: center;
    padding: 1em;
    max-width: 240px;
    margin: 0 auto;
  }
 
  h1 {
    color: #ff3e00;
    text-transform: uppercase;
    font-size: 4em;
    font-weight: 100;
  }
 
  @media (min-width: 640px) {
    main {
      max-width: none;
    }
  }
</style>

All .svelte files can contain a script element, arbitrary HTML, and a style element. Each section is optional. The order of these sections is not important, but the most common order is script first, HTML second, and style last. This places the parts that interact next to each other. Variables and functions in the script element are typically used by the HTML, and the HTML is styled by the CSS, but the style element is rarely affected by code in the script element. To use TypeScript instead of JavaScript inside a script element, change the opening tag to <script lang="ts">

The export keyword before the name variable at the top of the script element indicates it is a prop. Prop values can be set in other components that use this component. This is a case where the Svelte compiler treats valid JavaScript syntax (the export keyword) in a special way. Another such case is the syntax $: for reactive statements, which you learned about earlier.

Curly braces are used to output or render the value of a JavaScript expression, in this case just the variable name. This is referred to as interpolation. As you will see later, curly braces are also used for dynamic attribute values.

At this point you are free to modify the provided files and add more, just like we did earlier in the REPL.

2.2.4 Your first non-REPL app

Let’s build a Svelte app that calculates monthly loan payments given a loan amount, annual interest rate, and number of years. Users should be able to change any of the inputs and see the newly calculated monthly payment. We can build this in just 28 lines of code. The details of the calculation are not important. Focus on the layout of the code and the use of reactive declarations.

Follow along with these steps:

  1. Enter npx degit sveltejs/template loan.

  2. Enter cd loan.

  3. Enter npm install.

  4. Edit src/App.svelte and change it to match the code in listing 2.5.

  5. Enter npm run dev.

  6. Browse to localhost:5000.

  7. Try changing any of the inputs and verify that the monthly payment is updated.

Listing 2.5 Loan calculator app in src/App.svelte

<script>
  let interestRate = 3;
  let loanAmount = 200000;
  let years = 30;
  const MONTHS_PER_YEAR = 12;
 
  $: months = years * MONTHS_PER_YEAR;
  $: monthlyInterestRate = interestRate / 100 / MONTHS_PER_YEAR;
  $: numerator = loanAmount * monthlyInterestRate;
  $: denominator = 1 - (1 + monthlyInterestRate) ** -months;
  $: payment =
    !loanAmount || !years ? 0 :
    interestRate ? numerator / denominator :
    loanAmount / months;
</script>
 
<label for="loan">Loan Amount</label>
<input id="loan" type="number" bind:value={loanAmount}>      
 
<label for="interest">Interest Rate</label>
<input id="interest" type="number" bind:value={interestRate}>
 
<label for="years">Years</label>
<input id="years" type="number" bind:value={years}>
 
<div>
  Monthly Payment: ${payment.toFixed(2)}
</div>

When an input type is “number,” bind coerces the value of the variable, in this case loanAmount, to a number.

Note the use of reactive declarations in the preceding code. When the value of interestRate, loanAmount, or years changes, the value of payment is recomputed and displayed in the div element at the bottom.

It’s not fancy. There is no styling. But it works and demonstrates the ease with which useful web apps can be created with Svelte.

2.3 Bonus app

Let’s implement the famous Todo app in Svelte, as shown in figure 2.19. I have implemented this app in Svelte, React, and Vue, and you can find my implementations here:

Figure 2.19 Todo app

With this app, users can

We will implement this with two Svelte components named Todo and TodoList.

To create your own version of this app, begin by entering npx degit sveltejs/ template todo.

The Todo component renders a list item (<li>) for a single todo item. It contains a checkbox, the text of the todo, and a Delete button. This component doesn’t know what to do when the checkbox is toggled. It also doesn’t know what to do when the Delete button is clicked. It just dispatches events that are received by the TodoList component.

The TodoList component is the topmost component in the app. It renders a list of todos inside an unordered list (<ul>). It also listens for events from the Todo components and takes the appropriate actions.

This simple implementation does not actually persist todos to a storage location such as a database when they are archived. It just deletes them.

Listing 2.6 shows the code for the Todo component. We will cover event dispatching in more detail in chapter 5. When todos are added or deleted, the built-in fade transition is used so they gradually appear and disappear.

Listing 2.6 Todo component in src/Todo.svelte

<script>
  import {createEventDispatcher} from 'svelte';
  import {fade} from 'svelte/transition';
  const dispatch = createEventDispatcher();                     
 
  export let todo;                                              
</script>
 
<li transition:fade>
  <input
    type="checkbox"
    checked={todo.done}
    on:change={() => dispatch('toggleDone')} />                 
  <span class={'done-' + todo.done}>{todo.text}</span>
  <button on:click={() => dispatch('delete')}>Delete</button>   
</li>
 
<style>
  .done-true {                                                  
    color: gray;
    text-decoration: line-through;
  }
 
  li {
    margin-top: 5px;
  }
</style>

This creates a dispatch function that is used to dispatch an event.

TodoList passes an object describing a todo as a prop.

This dispatches a custom event named toggleDone, for which parent components can listen.

This dispatches a custom event named delete, for which parent components can listen.

The todo text will have a CSS class of done-true or done-false. We do not need any special styling for done-false.

Next is the code for the TodoList component.

Listing 2.7 TodoList component in src/TodoList.svelte

<script>
  import Todo from './Todo.svelte';
 
  let lastId = 0;
 
  const createTodo = (text, done = false) => ({id: ++lastId, text, done}); 
 
  let todoText = '';
 
  let todos = [                                                            
    createTodo('learn Svelte', true),
    createTodo('build a Svelte app')
  ];
 
  $: uncompletedCount = todos.filter(t => !t.done).length;                 
 
  $: status = `${uncompletedCount} of ${todos.length} remaining`;          
 
  function addTodo() {
    todos = todos.concat(createTodo(todoText));
    todoText = ''; // clears the input
  }
 
  function archiveCompleted() {
    todos = todos.filter(t => !t.done);                                    
  }
 
  function deleteTodo(todoId) {
    todos = todos.filter(t => t.id !== todoId);                            
  }
 
  function toggleDone(todo) {
    const {id} = todo;
    todos = todos.map(t => (t.id === id ? {...t, done: !t.done} : t));
  }
</script>
 
<div>
  <h1>To Do List</h1>
  <div>
    {status}
    <button on:click={archiveCompleted}>Archive Completed</button>
  </div>
  <form on:submit|preventDefault>                                          
    <input
      size="30"
      placeholder="enter new todo here"
      bind:value={todoText} />
    <button disabled={!todoText} on:click={addTodo}>Add</button>           
  </form>
  <ul>
    {#each todos as todo}                                                  
      <Todo                                                                
        {todo}
        on:delete={() => deleteTodo(todo.id)}
        on:toggleDone={() => toggleDone(todo)} />
    {/each}
  </ul>
</div>
 
<style>
  button {
    margin-left: 10px;
  }
 
  ul {
    list-style: none; /* removes bullets */
    margin-left: 0;
    padding-left: 0;
  }
</style>

This is a function that creates a todo object.

The app starts with two todos already created.

This is recomputed when the todos array changes.

This is recomputed when the todos array or uncompletedCount changes.

This keeps only the todos that are not done.

This deletes the todo with a given ID.

When we use a form, pressing the return key while focus is in the input activates the Add button, calling the addTodo function. But we don’t want to POST the form data. The preventDefault modifier prevents that.

The Add button is disabled if no text has been entered in the input.

This is the Svelte syntax for iterating over an array.

We listen for delete and toggleDone events here.

The main.js file is modified to render a TodoList component instead of the default App component.

Listing 2.8 Todo app src/main.js file

import TodoList from './TodoList.svelte';
 
const app = new TodoList({target: document.body});
 
export default app;

The following steps will build and run this app:

  1. Enter npm install.

  2. Enter npm run dev.

  3. Browse to localhost:5000.

That’s it! We now have a functioning Todo app implemented with a very small amount of code. It demonstrates many of the Svelte concepts we have covered up to this point.

Hopefully what you have seen so far is enough to convince you that there is something special about Svelte. It has a minimal syntax that makes implementing web applications very easy compared to the alternatives.

Svelte has many more features that we will cover in the following chapters. Along the way we will build a web application that serves as a travel-packing checklist. In the next chapter we will dive deeper into defining Svelte components.

Summary