owned this note
owned this note
Published
Linked with GitHub
---
slideOptions:
theme: dark
slideNumber: false
controls: false
transition: fade
---
<style>
img {
border: none !important;
width: 90vh;
object-fit: contain;
background: none !important;
box-shadow: none !important;
}
.reveal .slides>section, .reveal .slides>section>section {
padding: 0;
}
.reveal pre code {
max-height: none;
}
.slides code {
background: rgba(255, 255, 255, 0.1);
border-radius: 0.4vh;
padding: 0.5vh 1vh;
color: #EF9A9A;
}
.slides pre code {
color: white;
}
.hljs {
color: #a9b7c6;
background: #282b2e;
display: block;
overflow-x: auto;
padding: 0.5em;
}
.hljs-number,
.hljs-literal,
.hljs-symbol,
.hljs-bullet {
color: #6897BB;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-deletion {
color: #cc7832;
}
.hljs-variable,
.hljs-template-variable,
.hljs-link {
color: #629755;
}
.hljs-comment,
.hljs-quote {
color: #808080;
}
.hljs-meta {
color: #bbb529;
}
.hljs-string,
.hljs-attribute,
.hljs-addition {
color: #6A8759;
}
.hljs-section,
.hljs-title,
.hljs-type {
color: #ffc66d;
}
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #e8bf6a;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.profilepics > div {
display: flex;
justify-content: space-around;
}
.profilepics img {
border-radius: 50%;
height: 25vh;
width: 25vh;
object-fit: cover;
max-width: 100%;
}
.profilepics h2 {
font-size: 3vh;
margin-bottom: 0;
}
.profilepics h3 {
font-size: 2vh;
}
</style>
# SPAs with web components, lit-html + redux
---
<!-- .slide: class="profilepics" -->
<div class="image">
<img src="https://i.imgur.com/WIwJk9U.jpg" width="300" height="300">
<h2>Marcus Weiner</h2>
<h3>github.com/mraerino</h3>
</div>
<div class="image">
<img src="https://i.imgur.com/vE8DKi4.jpg" width="300" height="300">
<h2>Moritz Gunz</h2>
<h3>github.com/NeoLegends</h3>
</div>
<div class="image">
<img src="https://i.imgur.com/PHtIbl3.jpg" width="300" height="300">
<h2>Leo Bernard</h2>
<h3>github.com/leolabs</h3>
</div>
---

## Festify
Note:
- Hobby project
- Kostenlose Party App
- Gäste können über die Musik auf deinen Partys und Veranstaltungen entscheiden
---

Note:
- Basiert auf den Themen dieses Talks
---

Note:
- 100% kostenlos & open-source
- Beispiel für die Technologien, die wir heute anwenden
---
# 🏄
## Let's dive in!
Note:
- *Wer hat schonmal mit ES6 gearbeitet?*
- Achtung: Einsatz vieler ES6 Features
- Syntax weicht von herkömmlichem JS ab
---
# 📦
## Componentization
Note:
- Komplexität bewältigen
- Großes Problem in mehrere kleine Probleme – Komponenten – aufteilen
- Zusammenarbeit vereinfachen
- Einzelne Teams oder Personen können an einzelnen Bereichen eines Projektes arbeiten
- Seperation of Concerns
- Komponenten tun eine Sache, und diese dafür gut
- UNIX Prinzip
- Abhängigkeiten vermeiden
- Komponenten können unabhängig von anderen in mehreren Projekten eingesetzt werden
- Verwendung in Design Systems
- Designteams können UI Elemente wie Buttons, Inputs, etc. direkt als Komponenten gestalten -> konsistentes UI
---
**React**
```jsx
class MyComponent extends React.Component { /* ... */ }
render(document.body, <MyComponent/>);
```
**Angular**
```jsx
@Component({ selector: 'my-component' /* ... */ })
export class MyComponent { /* ... */ }
<my-component></my-component>
```
**VueJS**
```jsx
Vue.component('my-component', { /* ... */ });
<my-component></my-component>
```
Note:
- Beispiel für Komponentisierung
- Alle grundsätzlich gleich aufgebaut
- Komponenten sind nach außen hin eine Black Box, interne Prozesse sind nach außen hin irrelevant
---
# 🎁
## Web Components
---
Encapsulated elements defined as JS classes
Note:
Verkapselte Elemente in Form von JS Klassen
---
# 💻
## Demo
---
# 🚉
## Use the platform
---
Runs natively in the browser:
No library code needed 🎉
---
True style encapsulation
---
Web Standard is here to stay
Note:
- Quick edit-debug-reload cycle
- No library lock-in (React users must use react libraries)
---
# 🤔
## Browser Support
---


Note:
- Shims / Polyfills available, see ShadyDom and ShadyCSS
- Google AMP uses Web Components
---
# ✨
## UI Views
Note:
- Moderne Webapps brauchen View Templates
---
## Straightforward
<!-- .element: class="fragment" data-fragment-index="1" -->
## Native
<!-- .element: class="fragment" data-fragment-index="2" -->
## Fast
<!-- .element: class="fragment" data-fragment-index="3" -->
Note:
- Wichtig: Keine exotische Syntax
- Wie kann man nah an der Plattform bleiben?
---
# 🔥
## lit-html
Note:
- 2017, Justin Fagnani
- Google, Polymer Project
- *Wer kennt das?*
---
```javascript
import { html, render } from 'lit-html';
const template = ({ title, greeting }) => html`
<h1>${title}</h1>
<p>${greeting}</p>
`;
render(template({
title: "Talking about rendering",
greeting: "Welcome, Webworkers"
}), document.body);
```
---
# 📝
## Template Literals
Note:
- Javascript
- Platform Feature
- ES2015 / ES6
---
```javascript
const simple = `Hello, World!`;
```
```javascript
const multiline = `
<ul>
<li>One thing</li>
<li>another thing</li>
</ul>
`;
```
<!-- .element: class="fragment" data-fragment-index="1" -->
```javascript
const withExpression = `
Your lucky number is
${Math.floor(Math.random() * 10) + 1}
`;
```
<!-- .element: class="fragment" data-fragment-index="2" -->
```javascript
const tagged = tag`Hello ${somevar}!`;
```
<!-- .element: class="fragment" data-fragment-index="" -->
---
# `LIVE`
Note:
- Ineffizient: HTML Parsing erfolgt bei jedem Aufruf
- `innerHTML`
----
```javascript
const tag = (parts: string[], ...values: any[]) => {
console.log(parts, values);
return values.length;
};
const interpolationsCount = tag`Hello, ${'World'}!`; // = 1
```
----
```javascript
const parse = (parts, ...values) => {
console.log(parts, values);
return parseImpl(parts, ...values);
// Returns DocumentFragment made from template string
};
```
```javascript
const template = (name) =>
parse`<h1>Hello ${name}!</h1>`;
document.body.append(template("Webworker"));
```
---
# ⚡️
## Rendering efficiently
Note:
- Ineffizient: HTML Parsing erfolgt bei jedem Aufruf
- `innerHTML`
- Ansatz:
- Statische und dynamische Teile des Templates unterscheiden
- Statische Teile cachen!
---
## **Enter:**
## `<template>`
---
```html
<template id="demo">
<h1>Hello, World!</h1>
</template>
```
```javascript
const fragment =
document.querySelector('#demo').content.cloneNode(true);
```
<!-- .element: class="fragment" data-fragment-index="" -->
Note:
- Verstecktes Markup
- Parsing erledigt
- Rendert beim Hinzufügen zum DOM
- Templates klonen ist sehr effizient
- Plattform Feature :tada:
---
```javascript
const template = (title, greeting) => html`
<h1>${title}!</h1>
<p>${greeting}</p>
`;
```
Note:
- Zurück zu `lit-html`
- *Was passiert?*
---
```html
<template>
<h1>{{}}</h1>
<p>{{}}</p>
</template>
```
Note:
- Erstellt `<template>` aus statischen Teilen des Literals
- Browser parst Markup **einmal** (`innerHTML`)
- Dynamische Teile durch Platzhalter ersetzt
---
```html
<h1> </h1>
<p> </p>
```
Note:
- Rendering-Phase
- Template klonen
- Platzhalter-Positionen merken
---
```html
<h1>Talking about rendering</h1>
<p>Welcome, Webworkers</p>
```
Note:
- Dynamische Teile einsetzen
- Ergebnis in DOM einsetzen
---
```html
<h1>Rendering efficiently</h1>
<p>Like a pro</p>
```
Note:
- Update-Phase
- DOM-Elemente gleichbleibend
- Dynamische Teile werden neu gesetzt
- nach Dirty Check
---
# 🗂
## Nested Templates
---
```javascript
const template = (todos) => html`
<h2>Todos</h2>
${todos.map(todo => html`
<div class="todo" id="todo-${todo.id}">
${todo.text}
</div>
`)}
`;
```
---
# 🦄
## Uniflow
Note:
- lit-html implementiert kein Two-Way Databinding
- vgl. Angular, Polymer
- Würde bedeuten, spezielle Logik mit Verhalten zu bauen, das vom DOM abweicht
---
```javascript
let inputValue = "";
const template = html`
<input value=${inputValue}
on-change=${e => inputValue = e.target.value}>
`;
```
Note:
- Lösung: Event Listener über spezielle Syntax anfügen
---
## 🏃💨 💥
## Benchmarks

<!-- .element: class="fragment" data-fragment-index="1" -->
---
## Easy template syntax
<!-- .element: class="fragment" data-fragment-index="1" -->
## No build tooling needed
<!-- .element: class="fragment" data-fragment-index="2" -->
## Tiny (< 2kb)
<!-- .element: class="fragment" data-fragment-index="3" -->
## Considerably fast
<!-- .element: class="fragment" data-fragment-index="4" -->
---
# ⚛️
## Redux
Note:
- *Wer kennt das schon?*
- Moderne SPAs haben viel State
- Routing, Selected Items, Spinners
- App als Komponente ohne sonstige Patterns zu verwalten bringt schnell Komplexitätsexplosion
- Redux weitere Vorteile
- MVC - Massive View Controller
---

Note:
- State zwischen entfernten Komponenten teilen
- View 1 updates Model 1 -> Model 2 -> Update Propgation?
- Two way data binding
- Redux gibt Schranken um Updates vorhersehbarer und skalierbarer zu machen
- Uniflow
---
# ✅
## Three Principles
---
## Single Source of Truth
Note:
- Appp state ein großes Objekt
- No encapsulation
- Normalerweise komplexe Dinge werden einfach
- Undo / Redo
- Clean reset
- Hydrating State vom server in universal apps
---
```javascript
{
todos: {
'todo-1': {
id: 'todo-1',
title: "Listen to cool web talk at sipgate",
priority: 3,
completed: false,
},
'todo-2': {
id: 'todo-2',
title: "Do the laundry",
priority: 1,
completed: true,
},
},
}
```
---
## Immutable State
Note:
- State kann nicht direkt verändert werden
- Modifications durch Anfragen 'Actions'
---
```javascript
{
type: 'ADD_TODO',
payload: {
id: 'todo-3',
title: "Buy groceries",
priority: 2,
completed: false,
},
}
```
Note:
- Standard-Form
- Type & Payload
- In sich geschlossene Beschreibung der Mutation
- Replay & time travel
- Serialization & Logging
---
## Pure State Modification
```javascript
(State, Action) => State
```
<!-- .element: class="fragment" data-fragment-index="1" -->
```javascript
Array.prototype.reduce(reducer, inital);
```
<!-- .element: class="fragment" data-fragment-index="2" -->
Note:
- State modification durch reine Funktionen "reducers"
- Kopieren Daten anstelle von Modifikation
- Vier Aufgaben
---
```javascript
function reduce(state = { todos: {} }, action) {
// ...
}
```
Note:
- Setup via @INIT-Action
---
```javascript
function reduce(state = { todos: {} }, action) {
switch (action.type) {
case 'ADD_TODO':
// ...
default:
return state;
}
}
```
Note:
- Reagieren nur auf bekannte Actions
---
```javascript
function reduce(state = { todos: {} }, action) {
switch (action.type) {
case 'ADD_TODO':
const todos = {
...state.todos,
[action.payload.id]: action.payload
};
// ...
default:
return state;
}
}
```
Note:
- Transformieren nur relevanten Teil des States
---
```javascript
function reduce(state = { todos: {} }, action) {
switch (action.type) {
case 'ADD_TODO':
const todos = {
...state.todos,
[action.payload.id]: action.payload
};
return { ...state, todos };
default:
return state;
}
}
```
Note:
- Kopieren des Reststates (flat)
---
# ⚙️
## The Store
Note:
- Simple vs. in Flux / Mobx
- Plain JS object in implementation
- Bringt "Drei Konzepte" zusammen
- Enthält Reducers
- Drei Funktionen
---
`store.getState()`
---
`store.dispatch(action)`
---
`store.subscribe(listener)`
---
# 🔁
## From State to View and Back
Note:
- Redux ist view-layer agnostisch
- Glue-libraries kombinieren View and Redux layer
- react-redux
- fit-html
- Beispiele generisch und prinzip-kompatibel mit vielen Redux-libraries
---
## Deriving Data
Note:
- Der größte Teil der Daten im View ist sowieso derived
- Abgeleitete Daten müssen nicht verwaltet werden
- Weniger Komplexität im Reducer
- Redux hat perfekte Eigenschaften fürs Deriven
---
Task: Render a list of Todos (yaaaay! 🙌)
```html
<todo-list>
#shadow-root (open)
<h2>Todos</h2>
<todo-item todo-id="todo-1"></todo-item>
<todo-item todo-id="todo-3"></todo-item>
<h3>Completed Todos</h3>
<todo-item todo-id="todo-2"></todo-item>
</todo-list>
```
Note:
- Welche Daten brauchen wir?
- Liste von noch-offenen und abgeschlossenen Todos
- Welche Daten haben wir bereits?
- Liste aller Todos
- Wo haben wir die Daten? -> im State
---
MapStateToProps Function
```javascript
const mapStateToProps = (state) => ({
remainingTodos: Object.values(state.todos)
.filter(todo => !todo.completed)
.sort((a, b) => b.priority - a.priority),
completedTodos: Object.values(state.todos)
.filter(todo => todo.completed),
});
```
Note:
- Reine Funktion, die nötige View-Daten derived
- View ist dumm, berechnet selber nix
- Bei jedem State-Update aufgerufen
- Wenn Performance-Probleme, dann Selector-Library mit Caching
---
lit-html example
```javascript
const renderTodo = (todo) => html`
<todo-item todo-id="${todo.id}"></todo-item>
`;
const render = (props) => html`
<h2>Todos</h2>
${props.remainingTodos.map(renderTodo)}
<h3>Completed Todos</h3>
${props.completedTodos.map(renderTodo)}
`;
```
Note:
- Vorher derivede Daten zum Rendern nutzen
- Rendern ist deklarativ
---
Passing Properties
```javascript
const renderTodo = (todo) => html`
<todo-item todo-id="${todo.id}"></todo-item>
`;
```
```javascript
const mapStateToProps = (state, ownProps) => ({
todo: state.todos[ownProps.todoId],
});
```
<!-- .element: class="fragment" data-fragment-index="1" -->
Note:
- Jedes einzelne Todo kriegt ID durch Attribut / Property
- Attribute bilden zweiten Parameter von mapStateToProps ("ownProps")
- Daten können darauf basiert derived werden
---
## Triggering Actions
Note:
- Bisher nur Daten ausgegeben, keine State-Modification, event handling (bzw. Data Upwards flow)
---
Task: Render a single Todo-Item
```html
<todo-item>
#shadow-root (open)
<p>Watch cool web talk at sipgate</p>
<button>✅</button>
</todo-item>
```
Note:
- Einzelnes Todo anzeigen
- Button, der Todo abhakt
- Button mit State verknüpfen
---
MapDispatchToProps Function
```javascript
const mapDispatchToProps = (dispatch, ownProps) => ({
complete: (todoId) => dispatch({
type: 'COMPLETE_TODO',
payload: todoId,
}),
});
```
Note:
- Reine Funktion, die dispatchende Event Handler erstellt
- Kann an ownProps angepasst werden
- Side effects triggern durch mehrfach-dispatch
- In Reality über Middleware
---
```javascript
const props = {
...stateProps,
...dispatchProps,
};
```
Note:
- State & Dispatch props werden für View gemerged
---
lit-html example
```javascript
const render = (props) => html`
<p>${props.todo.title}</p>
<button on-click=${() => props.complete(props.todo.id)}>
✅
</button>
`;
```
---
# ⛓
## Side Effects
---
Middlewares
```javascript
const middleware = store => next => action => {
// ...
};
```
Note:
- Wird beim erstellen des Stores registriert
- Zugriff auf Store, jede Action und nächste Middleware
---
Middlewares
```javascript
const middleware = store => next => action => {
next(action); // call next middleware
// ...
};
```
Note:
- Andere Middlewares aufrufen (oder auch nicht)
---
Middlewares
```javascript
const middleware = store => next => action => {
next(action); // call next middleware
if (action.type !== 'FETCH_TODOS') {
return;
}
// ...
};
```
Note:
- Auf spezifische Actions reagieren
---
Middlewares
```javascript
const middleware = store => next => action => {
next(action); // call next middleware
if (action.type !== 'FETCH_TODOS') {
return;
}
const dispatchTodo = todo => store.dispatch({
type: 'ADD_TODO',
payload: todo,
});
fetch(/* ... */)
.then(resp => resp.json())
.then(todos => todos.forEach(dispatchTodo))
};
```
Note:
- Actions transformen bevor sie zum Store kommen
- Side effects triggern
- => Redux Saga, Thunk, etc.
---

Note:
- Uniflow erkenntlich
- Taken from http://slides.com/jenyaterpil/redux-from-twitter-hype-to-production#/
---
🏁 Summing up
- Change state through actions via reducers
- Render views declaratively
- Derive view data in `mapStateToProps`
- Prepare event handlers in `mapDispatchToProps`
---
# 💪
## fit-html
Note:
- Glue library between lit-html, web components and redux
- Produces web components that take state from redux and render to shadow root
- <3kB size, including lit-html and redux
- Code looks like previous examples
---
```javascript
const SearchBar = (props) => html`<!-- ... -->`;
const mapStateToProps = (state) => ({
text: state.router.query.s || '',
});
const mapDispatchToProps = {
changeText: changeTrackSearchInput,
eraseText: eraseTrackSearchInput,
};
customElements.define('search-bar', connect(
mapStateToProps,
mapDispatchToProps,
SearchBar,
));
```
Note:
- Production code von Festify
---
# 🔀
## Mixin-based
Note:
- Nur laden, was auch nötig
---
```javascript
const Base = connect(
mapStateToProps,
mapDispatchToProps,
PartyTrack,
);
const PartyTrack = withProps(Base, {
playing: Boolean,
trackid: String,
});
customElements.define('party-track', PartyTrack);
```
Note:
- Attribute playing, trackid deklarieren für Browser
---
# That's it.
# Ask us anything!
**Follow along:**
- github.com/Festify/fit-html
- npmjs.com/package/fit-html
- twitter.com/GetFestify