Einstieg in

enterJS Svelte Day 2022 | Nils Röhrig | REWE digital

  1. Was ist Svelte?
  2. Wo kommt Svelte her?
  3. Was kann Svelte?
  4. Was macht Svelte anders?
  5. Wie sieht das Zusammenspiel aus?

Was ist Svelte?

Kerneigenschaften

  • Komponenten-Framework
  • Kapselung von UI-Elementen
  • Deklarative Komposition

Beispiel-Komponente

<form action="/login" method="post">
  <Input type="email" label="E-Mail" name="email" />
  <Input type="password" label="Password" name="password" />
  <Button type="submit" label="Log in" />
</form>

Wo kommt Svelte her?

HTML & CSS

.block__element--modifier

<form action="/login" method="post" class="form form--login">
  <div class="form__row">
    <label for="email">E-Mail</label>
    <input type="email" name="email" class="form__input form__input--email" />
  </div>
  <div class="form__row">
    <label for="password">Password</label>
    <input type="password" class="form__input form__input--password" />
  </div>
  <div class="form__row">
    <button class="form__button form__button--submit">Submit</button>
  </div>
</form>
  .form {}
  .form--login {}
  .form__row {}
  .form__input {}
  .form__input--email {}
  .form__input--password {}
  .form__button {}
  .form__button--submit {}

Bootstrap

<div class="modal">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h1 class="modal-title">Modal Example</h1>
        <button class="btn-close" data-bs-dismiss="modal" />
      </div>
      <div class="modal-body">
        <p>Example content.</p>
      </div>
      <div class="modal-footer">
        <button class="btn btn-primary" data-bs-dismiss="modal">Ok</button>
      </div>
    </div>
  </div>
</div>

JavaScript

Model-View-ViewModel

React

  • Veröffentlicht 2013 von Facebook
  • Game Changer
  • Kein MVVM
  • Immutability
  • One-Way-Dataflow
  • Virtuelles DOM

One-Way-Dataflow

Virtual DOM

Und was hat das mit Svelte zu tun?

  • Veröffentlicht 2016 von Rich Harris
  • Beeinflusst durch Vorgänger
  • Übernahme guter Aspekte
  • Überarbeitung weniger guter Aspekte
  • Zunächst Nischendasein

Was kann Svelte?

Svelte ist ein Compiler

Svelte-Komponente

<h1>Hello Svelte Day!</h1>

Svelte-Komponente

JavaScript

Compiler

<h1>Hello Svelte Day!</h1>
/* HelloSvelteDay.svelte generated by Svelte v3.46.4 */
import { SvelteComponent, detach, element, init,
         insert, noop, safe_not_equal } from "svelte/internal";

function create_fragment(ctx) {
  let h1;

  return {
    c() {
      h1 = element("h1");
      h1.textContent = "Hello Svelte Day!";
    },
    m(target, anchor) {
      insert(target, h1, anchor);
    },
    p: noop,
    i: noop,
    o: noop,
    d(detaching) {
      if (detaching) detach(h1);
    },
  };
}

class HelloSvelteDay extends SvelteComponent {
  constructor(options) {
    super();
    init(this, options, null, create_fragment, safe_not_equal, {});
  }
}

export default HelloSvelteDay;

JavaScript

<h1>Hello Svelte Day!</h1>
/* HelloSvelteDay.svelte generated by Svelte v3.46.4 */
import { SvelteComponent, detach, element, init,
         insert, noop, safe_not_equal } from "svelte/internal";

function create_fragment(ctx) {
  let h1;

  return {
    c() {
      h1 = element("h1");
      h1.textContent = "Hello Svelte Day!";
    },
    m(target, anchor) {
      insert(target, h1, anchor);
    },
    p: noop,
    i: noop,
    o: noop,
    d(detaching) {
      if (detaching) detach(h1);
    },
  };
}

class HelloSvelteDay extends SvelteComponent {
  constructor(options) {
    super();
    init(this, options, null, create_fragment, safe_not_equal, {});
  }
}

export default HelloSvelteDay;

Svelte ist eine Sprache

Komponenten-Struktur

<script>
  // STATE & BEHAVIOR
</script>

<style>
  /* PRESENTATION */
</style>

<!-- DECLARATIVE MARKUP -->

Direktiven

<script>
  import { fade } from "svelte/transition";
  let value = 0;
</script>

<input bind:value type="number" />
<button on:click={() => console.log(value)}>Log</button>
<p transition:fade>Fading</p>

Kontrollstrukturen

<script>
  const fruits = ["banana", "apple", "orange"];
  let visible = false;
</script>

<p>
  <button on:click={() => (visible = !visible)}>Toggle Fruit Visibility</button>
</p>

<h2>Fruits</h2>
{#if visible}
  <ul>
    {#each fruits as fruit}
      <li>{fruit}</li>
    {/each}
  </ul>
{:else}
  <p>Fruits are invisible, please toggle fruit visibility.</p>
{/if}

Reaktivität

Ausgangstabelle

Manuelles Update

Direktes Reaktives Update

Transitives Reaktives Update

Event Handling

State Management

<script>
  let count = 0;
</script>

<p>
  <button on:click={() => count++}> increment </button>
  <button on:click={() => count--}> decrement </button>
</p>
<p>{count}</p>

Local State

  • Einfache Variablen
  • Direktzugriff in der Komponente
  • Zuweisung triggert Update
  • Obacht bei Sets, Arrays, etc.

Props

<!-- PropsContainer -->
<script>
  import PropsDisplayer from "./PropsDisplayer.svelte"
</script>

<PropsDisplayer name="Svelte Day" />
<!-- PropsDisplayer -->
<script>
  export let name
</script>

<p>Hello {name}!</p>

Props

<!-- PropsContainer -->
<script>
  import PropsDisplayer from "./PropsDisplayer.svelte"
</script>

<PropsDisplayer name="Svelte Day" />
<!-- PropsDisplayer -->
<script>
  export let name
</script>

<p>Hello {name}!</p>

Props

<!-- PropsContainer -->
<script>
  import PropsDisplayer from "./PropsDisplayer.svelte"
</script>

<PropsDisplayer name="Svelte Day" />
<!-- PropsDisplayer -->
<script>
  export let name
</script>

<p>Hello {name}!</p>

Props

<!-- PropsContainer -->
<script>
  import PropsDisplayer from "./PropsDisplayer.svelte"
</script>

<PropsDisplayer name="Svelte Day" />
<!-- PropsDisplayer -->
<script>
  export let name
</script>

<p>Hello {name}!</p>

Context

  • Zentraler Speicher im Framework
  • Gebunden an Hierarchie
  • Datenzugriff nur wo nötig
  • Nicht reaktiv

Context-Beispiel

<!-- ContextContainer -->
<script>
  import { setContext } from "svelte"
  import ContextDisplayer from "./ContextDisplayer.svelte"

  setContext("name", "Svelte Day")
</script>

<ContextDisplayer />
<!-- ContextDisplayer -->
<script>
  import { getContext } from "svelte"

  const name = getContext("name")
</script>

<p>Hello {name}!</p>

Context-Beispiel

<!-- ContextContainer -->
<script>
  import { setContext } from "svelte"
  import ContextDisplayer from "./ContextDisplayer.svelte"

  setContext("name", "Svelte Day")
</script>

<ContextDisplayer />
<!-- ContextDisplayer -->
<script>
  import { getContext } from "svelte"

  const name = getContext("name")
</script>

<p>Hello {name}!</p>

Context-Beispiel

<!-- ContextContainer -->
<script>
  import { setContext } from "svelte"
  import ContextDisplayer from "./ContextDisplayer.svelte"

  setContext("name", "Svelte Day")
</script>

<ContextDisplayer />
<!-- ContextDisplayer -->
<script>
  import { getContext } from "svelte"

  const name = getContext("name")
</script>

<p>Hello {name}!</p>

Stores

  • Publish/Subscribe-Modell
  • Verfügbar in gesamter App
  • Besonderer Sprachsupport
  • Reaktiv

Counter Store

import { writable } from "svelte/store";

const { update, subscribe } = writable(0);

export default {
  subscribe,
  increment: () => update((value) => value + 1),
  decrement: () => update((value) => value - 1),
};
<script>
  import counter from "./counter.js";
</script>

<button on:click={counter.decrement}>decrement</button>
<button on:click={counter.increment}>increment</button>

<p><output>{$counter}</output></p>

Counter Store

import { writable } from "svelte/store";

const { update, subscribe } = writable(0);

export default {
  subscribe,
  increment: () => update((value) => value + 1),
  decrement: () => update((value) => value - 1),
};
<script>
  import counter from "./counter.js";
</script>

<button on:click={counter.decrement}>decrement</button>
<button on:click={counter.increment}>increment</button>

<p><output>{$counter}</output></p>

Counter Store

import { writable } from "svelte/store";

const { update, subscribe } = writable(0);

export default {
  subscribe,
  increment: () => update((value) => value + 1),
  decrement: () => update((value) => value - 1),
};
<script>
  import counter from "./counter.js";
</script>

<button on:click={counter.decrement}>decrement</button>
<button on:click={counter.increment}>increment</button>

<p><output>{$counter}</output></p>

Stylesheets

Was macht Svelte anders?

  1. Vermeidung unnötiger Arbeit
  2. Geringere Code-Menge
  3. Funktionsvielfalt
  4. Nähe zur Plattform
  5. Flache Lernkurve für Web-Entwickler

Verzicht auf Virtual DOM

  • Ist-/Soll-Vergleich ist aufwändig
  • Virtual DOM ermuntert unnötige Arbeit
  • In Svelte unnötig, weil es ein Compiler ist
  • Änderungen zur Build-Zeit bekannt

Vermeidung unnötiger Arbeit

React

Svelte

import React, { useState } from "react";

function UnnecessaryWork(props) {
  const [fruit, setFruit] = useState(null);
  const items = props.items ?? ["banana", "apple", "orange"];

  return (
    <>
      <p>Your favorite Fruit: {fruit ?? "nothing"}</p>
      <hr />
      <p>Pick yor favorite fruit:</p>
      <ul>
        {items.map((item) => (
          <li>
            <button onClick={() => setFruit(item)}>
              {item}
            </button>
          </li>
        ))}
      </ul>
    </>
  );
}
<script>
  export let items = ["banana", "apple", "orange"];
  let fruit;
</script>

<p>Your favorite Fruit: {fruit ?? 'nothing'}</p>
<hr />
<p>Pick yor favorite fruit:</p>
<ul>
  {#each items as fr}
    <li>
      <button on:click={() => (fruit = fr)}>
        {fr}
      </button>
    </li>
  {/each}
</ul>

Vermeidung unnötiger Arbeit

React

Svelte

import React, { useState } from "react";

function UnnecessaryWork(props) {
  const [fruit, setFruit] = useState(null);
  const items = props.items ?? ["banana", "apple", "orange"];

  return (
    <>
      <p>Your favorite Fruit: {fruit ?? "nothing"}</p>
      <hr />
      <p>Pick yor favorite fruit:</p>
      <ul>
        {items.map((item) => (
          <li>
            <button onClick={() => setFruit(item)}>
              {item}
            </button>
          </li>
        ))}
      </ul>
    </>
  );
}
<script>
  export let items = ["banana", "apple", "orange"];
  let fruit;
</script>

<p>Your favorite Fruit: {fruit ?? 'nothing'}</p>
<hr />
<p>Pick yor favorite fruit:</p>
<ul>
  {#each items as fr}
    <li>
      <button on:click={() => (fruit = fr)}>
        {fr}
      </button>
    </li>
  {/each}
</ul>

Vermeidung unnötiger Arbeit

React

Svelte

import React, { useState } from "react";

function UnnecessaryWork(props) {
  const [fruit, setFruit] = useState(null);
  const items = props.items ?? ["banana", "apple", "orange"];

  return (
    <>
      <p>Your favorite Fruit: {fruit ?? "nothing"}</p>
      <hr />
      <p>Pick yor favorite fruit:</p>
      <ul>
        {items.map((item) => (
          <li>
            <button onClick={() => setFruit(item)}>
              {item}
            </button>
          </li>
        ))}
      </ul>
    </>
  );
}
<script>
  export let items = ["banana", "apple", "orange"];
  let fruit;
</script>

<p>Your favorite Fruit: {fruit ?? 'nothing'}</p>
<hr />
<p>Pick yor favorite fruit:</p>
<ul>
  {#each items as fr}
    <li>
      <button on:click={() => (fruit = fr)}>
        {fr}
      </button>
    </li>
  {/each}
</ul>

Geringere Code-Menge

React

Svelte

import React, { useState } from "react";

export default function Summing() {
  const [operand1, setOperand1] = useState(0);
  const [operand2, setOperand2] = useState(0);

  return (
    <>
      <h1>Summing</h1>
      <input
        type="number"
        value={operand1}
        onChange={(e) => setOperand1(Number(e.target.value))}
      />
      +
      <input
        type="number"
        value={operand2}
        onChange={(e) => setOperand2(Number(e.target.value))}
      />
      =
      <strong>{operand1 + operand2}</strong>
    </>
  );
}
<script>
  let operand1 = 0;
  let operand2 = 0;
</script>

<h1>Summing</h1>
<input type="number" bind:value={operand1} />
+
<input type="number" bind:value={operand2} />
=
<strong>{operand1 + operand2}</strong>

Geringere Code-Menge

React

Svelte

import React, { useState } from "react";

export default function Summing() {
  const [operand1, setOperand1] = useState(0);
  const [operand2, setOperand2] = useState(0);

  return (
    <>
      <h1>Summing</h1>
      <input
        type="number"
        value={operand1}
        onChange={(e) => setOperand1(Number(e.target.value))}
      />
      +
      <input
        type="number"
        value={operand2}
        onChange={(e) => setOperand2(Number(e.target.value))}
      />
      =
      <strong>{operand1 + operand2}</strong>
    </>
  );
}
<script>
  let operand1 = 0;
  let operand2 = 0;
</script>

<h1>Summing</h1>
<input type="number" bind:value={operand1} />
+
<input type="number" bind:value={operand2} />
=
<strong>{operand1 + operand2}</strong>

Geringere Code-Menge

React

Svelte

import React, { useState } from "react";

export default function Summing() {
  const [operand1, setOperand1] = useState(0);
  const [operand2, setOperand2] = useState(0);

  return (
    <>
      <h1>Summing</h1>
      <input
        type="number"
        value={operand1}
        onChange={(e) => setOperand1(Number(e.target.value))}
      />
      +
      <input
        type="number"
        value={operand2}
        onChange={(e) => setOperand2(Number(e.target.value))}
      />
      =
      <strong>{operand1 + operand2}</strong>
    </>
  );
}
<script>
  let operand1 = 0;
  let operand2 = 0;
</script>

<h1>Summing</h1>
<input type="number" bind:value={operand1} />
+
<input type="number" bind:value={operand2} />
=
<strong>{operand1 + operand2}</strong>

Geringere Code-Menge

React

Svelte

import React, { useState } from "react";

export default function Summing() {
  const [operand1, setOperand1] = useState(0);
  const [operand2, setOperand2] = useState(0);

  return (
    <>
      <h1>Summing</h1>
      <input
        type="number"
        value={operand1}
        onChange={(e) => setOperand1(Number(e.target.value))}
      />
      +
      <input
        type="number"
        value={operand2}
        onChange={(e) => setOperand2(Number(e.target.value))}
      />
      =
      <strong>{operand1 + operand2}</strong>
    </>
  );
}
<script>
  let operand1 = 0;
  let operand2 = 0;
</script>

<h1>Summing</h1>
<input type="number" bind:value={operand1} />
+
<input type="number" bind:value={operand2} />
=
<strong>{operand1 + operand2}</strong>

Geringere Code-Menge

React

Svelte

import React, { useState } from "react";

export default function Summing() {
  const [operand1, setOperand1] = useState(0);
  const [operand2, setOperand2] = useState(0);

  return (
    <>
      <h1>Summing</h1>
      <input
        type="number"
        value={operand1}
        onChange={(e) => setOperand1(Number(e.target.value))}
      />
      +
      <input
        type="number"
        value={operand2}
        onChange={(e) => setOperand2(Number(e.target.value))}
      />
      =
      <strong>{operand1 + operand2}</strong>
    </>
  );
}
<script>
  let operand1 = 0;
  let operand2 = 0;
</script>

<h1>Summing</h1>
<input type="number" bind:value={operand1} />
+
<input type="number" bind:value={operand2} />
=
<strong>{operand1 + operand2}</strong>

Funktionsvielfalt

Svelte React
CSS-Support
Transitionen
Animationen
State Management /
Deklarativer Zugriff auf Head, Body oder Window

Nähe zur Plattform

React

Svelte

import React, { useRef } from "react";
import tippy from "tippy.js";

export default function App() {
  const tippyRef = useRef();

  if (tippyRef != null) {
    tippy(tippyRef.current, {
      content: "Tippy Tooltip!",
    });
  }

  return (
    <>
      <button ref={tippyRef}>Tippy Button</button>
    </>
  );
}
<script>
  import tippy from "tippy.js";
</script>

<button use:tippy={{ content: 'Tippy Tooltip!' }}>
  Tippy Button!
</button>

Nähe zur Plattform

React

Svelte

import React, { useRef } from "react";
import tippy from "tippy.js";

export default function App() {
  const tippyRef = useRef();

  if (tippyRef != null) {
    tippy(tippyRef.current, {
      content: "Tippy Tooltip!",
    });
  }

  return (
    <>
      <button ref={tippyRef}>Tippy Button</button>
    </>
  );
}
<script>
  import tippy from "tippy.js";
</script>

<button use:tippy={{ content: 'Tippy Tooltip!' }}>
  Tippy Button!
</button>

Nähe zur Plattform

React

Svelte

import React, { useRef } from "react";
import tippy from "tippy.js";

export default function App() {
  const tippyRef = useRef();

  if (tippyRef != null) {
    tippy(tippyRef.current, {
      content: "Tippy Tooltip!",
    });
  }

  return (
    <>
      <button ref={tippyRef}>Tippy Button</button>
    </>
  );
}
<script>
  import tippy from "tippy.js";
</script>

<button use:tippy={{ content: 'Tippy Tooltip!' }}>
  Tippy Button!
</button>

Lernkurve

  • HTML, CSS, JS sind schon die halbe Miete
  • Syntaxerweiterungen ähneln Template-Engines
  • Svelte-Spezifika sind leicht unterwegs zu erlernen

Wie sieht das Zusammenspiel aus?

  1. Offizielles Tutorial bearbeiten
  2. Im REPL experimentieren
  3. Svelte für einen kleinen Teil eines Projekts / Produkts einsetzen

Vielen Dank!