Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
March 20, 2022 10:12 am GMT

Stop Overcomplicating Web Development - Try Svelte

Svelte was rated as the most loved web framework by developers in 2021 (link). So what is Svelte and why is it so loved?

Svelte is a rather unique javascript framework which aims to be 'truly reactive' and help developers 'write less code'.

It has the awesome feature of creating tiny code bundles by making the framework itself 'disappear' in a compilation stage, shipping optimised vanilla JavaScript code rather than using large and complex libraries loaded at run time.
In my opinion, this is the game changer that makes Svelte standout from other JavaScript frameworks. A tiny bundle size means a much faster load time, which seems to be the direction the web is taking as more and more data shows the benefits of a quick website. This compilation stage also removes the need for techniques such as the virtual DOM, used by React and Vue, further increasing the speed of a website.

Another feature is the absence of boilerplate. Svelte feels very close to standard web development, where components can look exactly like vanilla HTML. Im sure this is a big reason why developers love this framework.

To introduce Svelte, lets use the the poke api to create a simple single page app where users can select a pokemon, with a live search bar to filter through the list of all the pokemon. This will demonstrate all the core features of Svelte in a useful way. The full code can be found here

Table of Contents

  1. Installation
  2. Component Features
  3. Variables & Reactivity
  4. onMount & Async Fetching
  5. Reactive Declarations
  6. Loops
  7. Conditional Rendering
  8. Components & Props
  9. Custom Events
  10. Bind Forwarding
  11. Stores
  12. Final Notes

Installation

Lets first install basic Svelte. To do this, run the following command

npx degit sveltejs/template new-svelte-project

This will copy the svelte starter template into your desired folder.
To enable typescript, go into your new svelte folder, and run

node scripts/setupTypeScript.js

Now all you need to do is install the necessary files by running

npm install

Component Features

A svelte component is a file ending with .svelte.
As you can see in App.svelte, a Svelte component can be pretty simple, containing three parts; html, a script tag to put your javascript, and a style tag to place css.
This is similar to Vue, just without the boilerplate code.

Clear the script and html contents of App.svelte, and lets use the given css in the style tag.

Variables & Reactivity

Variables are created in the script tag.
We can create a string variable and display it in the DOM very easily by using curly braces {}.

<!-- For example --><script lang="ts">  let name: string = 'pokemon searcher'</script><h1>{name}</h1>

To search through pokemon names, well need an input field and a variable that holds the contents of that field.
To make the 'pokemonName' variable equal to the contents of an input field, we can use a special svelte keyword 'bind', which enables two way binding of the 'pokemonName' variable.

<!-- App.svelte --><script lang="ts">  let pokemonName: string = ''</script><main>  <span>Search: </span>  <input type="text" bind:value="{pokemonName}" />  <h1>Pokemon: {pokemonName}</h1></main>

Now, typing into the input field changes the output of the pokemon title.
This bind keyword enables two way binding without using an 'onInput' function that changes the value of the pokemonName variable like in React.

onMount & Async Fetching

For this sample app, we store pokemon names from the pokeapi in a variable as an array of strings.

We want to fetch the data and map it as soon as the component is rendered.
For this, we can use 'onMount', which is a svelte lifecycle function that runs after the component is first rendered to the DOM. Lets use this to fetch from the pokeapi and map it into an array of pokemon names.

<!-- App.svelte - script tag --><script lang="ts">  import { onMount } from 'svelte'  let pokemonName: string = ''  // Fetch from api then store the mapped names.  let pokemonData: string[] = []  onMount(() => {    const setPokemonData = async (): Promise<void> => {      const rawPokemonData = await (        await fetch('https://pokeapi.co/api/v2/pokemon?limit=99')      ).json()      pokemonData = rawPokemonData.results.map(        (p: { name: string; url: string }) => p.name      )    }    setPokemonData()  })</script>

We now have a list of pokemon names in the 'pokemonData' array, which we can use in our simple project.

Reactive Declarations

For the live search feature, we need to have an array that holds the items filtered by the user input from the pokemon names.

Svelte has an awesome tool to deal with states that are derived from other properties, reactive declarations.
They look like this.

$: reactiveVar = otherVar * 2;

Now, 'reactiveVar' is a variable, but its value is computed every time the 'otherVar' variable changes (svelte runs the computation when the variables used in that computation changes).
We can make the variable that holds the filtered pokemon names into a reactive declaration. Well call this 'suggestions'.

<!-- App.svelte - bottom of script tag --><script>  // ...  let suggestions: string[]  $: suggestions =        pokemonName.length > 0         ? pokemonData.filter(             (name) => name.includes(pokemonName)           )         : pokemonData</script>

So, 'suggestions' is an array of pokemon names that includes the string entered in the input field.
The reactive assignment doesnt work with typescript, so we can declare a 'suggestions' variable normally to preserve type checks.

Loops

Well want to display the contents of this 'suggestions' array on the page, and we can do this by using a svelte loop. Svelte has a special keyword 'each' that enables us to display DOM elements for each item in the given iterable.
To display each pokemon name, simply use the each keyword to loop over the 'pokemonData' variable.

<!-- App.svelte - html --><main>  <span>Search: </span>  <input type="text" bind:value="{pokemonName}" />  {#each suggestions as suggestion}    <h2>{suggestion}</h2>  {/each}</main>

As we type into the input field, we can see the list of suggestions change. Pretty cool for such simple code.

Conditional Rendering

Svelte has other keywords. Another useful one is #if.
The #if keyword allows for conditional logic.
For example, we can render a loading screen while we fetch the data from the pokeapi.

<!-- App.svelte - html --><main>  {#if pokemonData && pokemonData.length > 0}    <span>Search: </span>    <input type="text" bind:value="{pokemonName}" />    {#each suggestions as suggestion}      <h2>{suggestion}</h2>    {/each}  {:else}    <h2>Loading...</h2>  {/if}</main>

Components & Props

Props are used to pass data from one component to another. This is fairly standard for frameworks, and the syntax for this is very simple.
To signal that a component accepts a prop, an export keyword is used for a variable.

<script lang="ts">  export let stringProp: string</script>

Now, to pass the value for 'stringProp', simply use the name of the exported variable when writing the component.

<script lang="ts">  import NewComponent from './NewComponent.svelte'</script><NewComponent  stringProp="prop value"  />

For our app, lets create a component for each suggestion.
Create a new file 'Suggestion.svelte' in src/, and simply accept and display a 'suggestion' prop.

<!-- Suggestion.svelte --><script lang="ts">  export let suggestion: string</script><div class="suggestion">{suggestion}</div><style>    .suggestion {        font-size: 1.25rem;    }</style>

Now, we can import this component and use it in our #each loop.

<!-- App.svelte - top of script tag --><script lang="ts">  import Suggestion from './Suggestion.svelte'  // ...  // ...</script>
<!-- App.svelte - html --><main>  {#if pokemonData && pokemonData.length > 0}    <span>Search: </span>    <input type="text" bind:value="{pokemonName}" />    {#each suggestions as suggestion}      <Suggestion suggestion="{suggestion}"/>    {/each}  {:else}    <h2>Loading...</h2>  {/if}</main>

This is pretty pointless at the moment, so lets add some logic to the 'Suggestion' component in the form of events.

Custom Events

Custom events can be dispatched from one component to another. This allows us to have parent-child communication.
For our app, we want to be able to click on a suggestion to select our pokemon. We can do this by dispatching a custom event from the 'Suggestion' component to the App component, and then setting the value of a variable which holds our chosen pokemon.

First, create the new 'chosenPokemon' variable, and display it on the screen in App.svelte.

<!-- App.svelte - bottom of script tag --><script lang="ts">  // ...  // ...  let chosenPokemon: string = ''</script>
<!-- App.svelte - html --><main>  {#if pokemonData && pokemonData.length > 0}    <h1>Chose Your Pokemon</h1>    <h2>Chosen Pokemon: {chosenPokemon}</h2>    <div>      <span>Search: </span>      <input type="text" bind:value="{pokemonName}" />      {#each suggestions as suggestion}        <Suggestion suggestion="{suggestion}"/>      {/each}    </div>  {:else}    <h2>Loading...</h2>  {/if}</main>

Now, in Suggestion.svelte, we can dispatch a custom 'chosePokemon' event when clicking on a suggestion.
To create a custom event, we need to import the createEventDispatcher from svelte.

<!-- Suggestion.svelte - script tag --><script lang="ts">  import { createEventDispatcher } from 'svelte'  export let suggestion: string  // Function to dispatch a custom event.  const dispatch = createEventDispatcher()  const chosePokemon = (): void => {    dispatch('chosePokemon', {      pokemon: suggestion    })  }</script>

We now have a 'chosePokemon' function that dispatches a custom 'chosePokemon' event to the parent component.

To call this function when clicking on a suggestion, we need to use the svelte 'on:click' event like this.

<!-- Suggestion.svelte - html --><div class="suggestion" on:click="{chosePokemon}">  {suggestion}</div>

Back in the App.svelte file, we can handle this custom event by using the 'on:(event name)' syntax.

<!-- App.svelte - 'Suggestion' component in html --><Suggestion  suggestion="{suggestion}"  on:chosePokemon="{(e) => {    chosenPokemon = e.detail.pokemon  }}"/>

This handler sets the value of the chosenPokemon variable to be equal to the pokemon name passed in the custom event (located in the 'detail' property).
When we click on a suggestion, that pokemon name is shown.

I've set the 'chosenPokemon' variable this way to introduce custom events, however, there is a much cleaner and easier way of doing this: bind forwarding.

Bind Forwarding

We saw how the bind keyword was used to set up two way binding when creating an input field, but this keyword can also be used across our components.

In App.svelte, we can replace the chosePokemon event handler with a bind keyword on a chosenPokemon prop.

<!-- App.svelte - 'Suggestion' component in html --><Suggestion suggestion="{suggestion}" bind:chosenPokemon />

And in the 'Suggestion' component we can accept this prop and make the 'on:click' function simply set this 'chosenPokemon' variable.

<!-- Suggestion.svelte --><script lang="ts">  export let suggestion: string  export let chosenPokemon: string = ''</script><div   class="suggestion"   on:click="{() => chosenPokemon = suggestion}">  {suggestion}</div>

We now have the same functionality as before using a fraction of the code.

Stores

I want to wrap things up by introducing stores.
With Svelte, we dont need to use an external library like Redux to have a central store, it comes with the framework.

Fundamentally, a store is an object with a subscribe method that allows our Svelte components to be notified of any changes to the store value. Svelte defines 2 different types of stores, a writable store, and a readable store. As the names suggest, a writable store allows reads and writes, while a readable store only allows a read.

For our app, well send the 'pokemonData' variable into the store. Since this variable simply gathers and stores the pokemon data, well use a readable store.

First, we need a new file in the src folder (Ill name it stores.ts).
We can import the readable store function from svelte, along with the required types.

// stores.tsimport { readable, Readable, Subscriber } from 'svelte/store'

A readable function, as its first argument, takes the stores initial value, and, as its second argument, a 'start' function.
This 'start' function is called when the store gets its first subscriber, so it is where well retrieve our api data.
The function receives a 'set' callback function, which is used to set the value of the store, and returns a 'stop' function which is called when the last subscriber unsubscribes (where we can perform some cleanup).

In our store, we can simply copy the contents of our 'setPokemonData' function, but instead of assigning the value of 'pokemonData', we call the 'set' function.

import { readable, Readable, Subscriber } from 'svelte/store'const setPokemonData =   async (set: Subscriber<string[]>): Promise<void> => {    const rawPokemonData = await (      await fetch('https://pokeapi.co/api/v2/pokemon?limit=99')    ).json()    set(      rawPokemonData.results.map(        (p: { name: string; url: string }) => p.name      )    )  }// Export the new store 'pokemonData' variable.export const pokemonData: Readable<string[]> =   readable([], (set) => {    setPokemonData(set)    return () => set([])  })

Thats it. We now have a central store holding our pokemon names in 'pokemonData'.

To use our store, we need to import the 'pokemonData' variable from our store file.
We can then use the special svelte '$' symbol to reference the store's value.

<!-- App.svelte --><script lang="ts">  import { pokemonData } from './stores.js'  import Suggestion from './Suggestion.svelte'  let pokemonName: string = ''  let suggestions: string[]  // $pokemonData instead of pokemonData  $: suggestions =    pokemonName.length > 0      ? $pokemonData.filter((name) =>           name.includes(pokemonName)        )      : $pokemonData  let chosenPokemon: string = ''</script><main>  {#if $pokemonData && $pokemonData.length > 0}    <h1>Chose Your Pokemon</h1>    <h2>Chosen Pokemon: {chosenPokemon}</h2>    <div>      <span>Search: </span>      <input type="text" bind:value="{pokemonName}" />      {#each suggestions as suggestion}        <Suggestion           suggestion="{suggestion}"           bind:chosenPokemon         />      {/each}    </div>  {:else}    <h2>Loading...</h2>  {/if}</main>

Our app works the same, but our api data is now centrally stored and can be used in any component.

Now, while svelte has writable and readable stores, anything that sticks to the svelte store 'contract' and implements the subscribe method is a store.
This means that stores are very flexible and can be tailored to your needs. You can even create a store in another language, like Rust, as shown here.

Final Notes

Svelte stands out in the cluttered world of JavaScript frameworks by not compromising user experience for developer experience or vice versa.
Svelte provides fantastic tools to make developing apps easy, while its compiler results in a tiny package for end users, reducing download times significantly.

Svelte has been among the most loved frameworks for a while now, even as its usage grows, a potential sign that it will become as big as Vue or React. This coincides with the recent push towards a more performant web, moving away from crazy large JavaScript packages served to the client towards server-side or hybrid rendering.

The Svelte team is now working on SvelteKit, which is Sveltes version of Next.js, which you can learn about here.

If you enjoyed this article, please consider sharing it.
Checkout my github and other articles.


Original Link: https://dev.to/emmanuilsb/stop-overcomplicating-web-development-try-svelte-47ln

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To