Web components en 2018, on en est où?

A presentation at Bordeaux JUG in December 2018 in Bordeaux, France by Horacio Gonzalez

Slide 1

Slide 1

Web components en 2018 On en est où ? Horacio Gonzalez @LostInBrittany #webcomponents @LostInBrittany

Slide 2

Slide 2

Who are we? Introducing myself and introducing OVH #webcomponents @LostInBrittany

Slide 3

Slide 3

Horacio Gonzalez @LostInBrittany Spaniard lost in Brittany, developer, dreamer and all-around geek #webcomponents @LostInBrittany

Slide 4

Slide 4

OVH : Key Figures 1.3M Customers worldwide in 138 Countries 1.5 Billions euros investment over five years 30 Datacenters (growing) 350k Dedicated Servers 200k Private cloud VMs running 650k Public cloud Instances created in a month 15TB bandwidth capacity

  • 2 500 Employees in 19 countries 18 Years of Innovation 35 Points of presence 4TB Anti DDoS capacity Hosting capacity : 1.3M Physical Servers #webcomponents @LostInBrittany

Slide 5

Slide 5

OVH: A Global Leader on Cloud 200k Private cloud VMs running 1 Dedicated IaaS Europe 2018 27 Datacenters Own 15 Tbps Hosting capacity : 1.3M Physical Servers 360k Servers already deployed 2020 50 Datacenters Netwok with 35 PoPs

1.3M Customers in 138 Countries #webcomponents @LostInBrittany

Slide 6

Slide 6

OVH: Our solutions Cloud Web Hosting Mobile Hosting Telecom VPS Containers ▪ Dedicated Server Domain names VoIP Public Cloud Compute ▪ Data Storage Email SMS/Fax Private Cloud ▪ Network and Database CDN Virtual desktop Serveur dédié Security Object Storage Web hosting Cloud HubiC Over theBox ▪ Licences Cloud Desktop Securities MS Office Hybrid Cloud Messaging MS solutions #webcomponents @LostInBrittany

Slide 7

Slide 7

Sometimes I feel a bit grumpy The stories of the grumpy old speaker... #webcomponents @LostInBrittany

Slide 8

Slide 8

On Polymer tour since 2014 #webcomponents @LostInBrittany

Slide 9

Slide 9

Image: bu.edu Web components == Revolution #webcomponents @LostInBrittany

Slide 10

Slide 10

Images: BitRebels & Brickset Building a world brick by brick #webcomponents @LostInBrittany

Slide 11

Slide 11

Is the promise unfulfilled? It's almost 2019, where is your revolution, dude? #webcomponents @LostInBrittany

Slide 12

Slide 12

Is it a conspiracy? #webcomponents @LostInBrittany

Slide 13

Slide 13

Am I only a dreamer? #webcomponents @LostInBrittany

Slide 14

Slide 14

Well, revolution IS there But it's a silent one... #webcomponents @LostInBrittany

Slide 15

Slide 15

They are there, in everyday sites More than you can imagine #webcomponents @LostInBrittany

Slide 16

Slide 16

The components architecture won Components, components everywhere #webcomponents @LostInBrittany

Slide 17

Slide 17

Web Components #webcomponents @LostInBrittany

Slide 18

Slide 18

We want the code! https://github.com/LostInBrittany/web-components-interop #webcomponents @LostInBrittany

Slide 19

Slide 19

A very basic web component class MyElement extends HTMLElement { // This gets called when the HTML parser sees your tag constructor() { super(); // always call super() first in the ctor. this.msg = 'Hello, Bordeaux JUG!'; } // Called when your element is inserted in the DOM or // immediately after the constructor if it’s already in the DOM connectedCallback() { this.innerHTML = <p>${this.msg}</p>; } } customElements.define('my-element', MyElement); #webcomponents @LostInBrittany

Slide 20

Slide 20

Custom Elements: ● Let you define your own HTML tag with bundled JS behavior ● Trigger lifecycle callbacks ● Automatically “upgrade” your tag when inserted in the document #webcomponents @LostInBrittany

Slide 21

Slide 21

Custom Elements don’t: ● Scope CSS styles ○ Shadow DOM ● Scope JavaScript ○ ES2015 ● “Reproject” children into <slot> elements ○ Shadow DOM #webcomponents @LostInBrittany

Slide 22

Slide 22

Adding ShadowDOM class MyElementWithShadowDom extends HTMLElement { // This gets called when the HTML parser sees your tag constructor() { super(); // always call super() first in the ctor. this.msg = 'Hello, Bordeaux JUG!'; this.attachShadow({ mode: 'open' }); } // Called when your element is inserted in the DOM or // immediately after the constructor if it’s already in the DOM connectedCallback() { this.shadowRoot.innerHTML = <p>${this.msg}</p>; } } customElements.define('my-element-with-shadowdom', MyElementWithShadowDom); #webcomponents @LostInBrittany

Slide 23

Slide 23

Adding ShadowDOM #webcomponents @LostInBrittany

Slide 24

Slide 24

Lifecycle callbacks class MyElementLifecycle extends HTMLElement { constructor() { // Called when an instance of the element is created or upgraded super(); // always call super() first in the ctor. } // Tells the element which attributes to observer for changes // This is a feature added by Custom Elements static get observedAttributes() { return []; } connectedCallback() { // Called every time the element is inserted into the DOM } disconnectedCallback() { // Called every time the element is removed from the DOM. } attributeChangedCallback(attrName, oldVal, newVal) { // Called when an attribute was added, removed, or updated } adoptedCallback() { // Called if the element has been moved into a new document } } #webcomponents @LostInBrittany

Slide 25

Slide 25

my-counter custom element class MyCounter extends HTMLElement { constructor() { super(); this._counter = 0; this.attachShadow({ mode: 'open' }); } connectedCallback() { this.render() } static get observedAttributes() { return [ 'counter' ] } attributeChangedCallback(attr, oldVal, newVal) { if (oldVal !== newVal) { this[attr] = newVal; } } #webcomponents @LostInBrittany

Slide 26

Slide 26

my-counter custom element get counter() { return this._counter; } set counter(value) { if (value != this._counter) { this._counter = Number.parseInt(value); this.setAttribute('counter', value); this.display(); } } increment() { this.counter = this.counter + 1; this.dispatchEvent(new CustomEvent( 'increased', {detail: {counter: this.counter}})); } #webcomponents @LostInBrittany

Slide 27

Slide 27

my-counter custom element render() { let button = document.createElement('button'); button.innerHTML = '+'; button.addEventListener('click', this.increment.bind(this)); this.shadowRoot.appendChild(button); this.output = document.createElement('span'); this.shadowRoot.appendChild(this.output); this.style.display = 'block'; this.style.fontSize = '5rem'; button.style.fontSize = '5rem'; button.style.borderRadius = '1rem'; button.style.padding = '0.5rem 2rem'; this.output.style.marginLeft = '2rem'; } display() { this.output.innerHTML = ${this.counter}; } #webcomponents @LostInBrittany

Slide 28

Slide 28

my-counter custom element #webcomponents @LostInBrittany

Slide 29

Slide 29

Polymer Adding syntactic sugar to the standard #webcomponents @LostInBrittany

Slide 30

Slide 30

Polymer has evolved in 2018 Image: © Nintendo Polymer 3 is here! #webcomponents @LostInBrittany

Slide 31

Slide 31

Classes, JavaScript modules... import {html, PolymerElement} from '/node_modules/@polymer/polymer/polymer-element.js'; class MyPolymerCounter extends PolymerElement { static get template() { <style> :host { font-size: 5rem; } button { font-size: 5rem; border-radius: 1rem; padding: 0.5rem 2rem; } </style> <button on-click="increment">+</button> <span>[[counter]]</span> `; } #webcomponents @LostInBrittany

Slide 32

Slide 32

But it's still mostly syntactic sugar static get properties() { return { counter: { type: Number, reflectToAttribute:true, value: 0 } }; } increment() { this.counter = Number.parseInt(this.counter) + 1; this.dispatchEvent(new CustomEvent('increased', {detail: {counter: this.counter}})); } } window.customElements.define('my-polymer-counter', MyPolymerCounter); #webcomponents @LostInBrittany

Slide 33

Slide 33

And they are still custom elements 100% interoperable #webcomponents @LostInBrittany

Slide 34

Slide 34

Interoperation pattern <div class="container"> <my-polymer-counter counter="[[value]]" on-increased="_onCounterChanged"></my-polymer-counter> <my-counter counter="[[value]]" on-increased="_onCounterChanged"></my-counter> </div> <div class="container"> <div class="value">Shared value: [[value]]</div> </div> Attributes for data in Events for data out #webcomponents @LostInBrittany

Slide 35

Slide 35

To infinity and beyond! There is a world outside Polymer #webcomponents @LostInBrittany

Slide 36

Slide 36

Lots of web components libraries For different need and sensibilities #webcomponents @LostInBrittany

Slide 37

Slide 37

Lots of web components libraries Angular Elements Vue Web Component Wrapper And the frameworks work on it too! #webcomponents @LostInBrittany

Slide 38

Slide 38

Slim.js #webcomponents @LostInBrittany

Slide 39

Slide 39

Slim.js #webcomponents @LostInBrittany

Slide 40

Slide 40

Slim.js ● Lightweight web component library ● Extended capabilities for components ○ data binding ● Using es6 native classes and modules ● Without Shadow DOM by default Like a lighter and lesser-featured Polymer #webcomponents @LostInBrittany

Slide 41

Slide 41

Slim.js import {Slim} from '/node_modules/slim-js/Slim.js' let template = <style> ... </style> <div class="container"> <div class="button" slim-id="button" click='increment'> <img src="./img/slim.png"> </div> <div class="value" bind> {{toString(counter)}} </div> </div>; Slim.tag('my-slim-counter', template, class extends Slim { ... }); #webcomponents @LostInBrittany

Slide 42

Slide 42

Slim.js class extends Slim { // native API static get observedAttributes () { return ['counter']; } // bind attributes to properties get autoBoundAttributes() { return ['counter']; } increment() { this.counter = this.counter + 1; this.dispatchEvent(new CustomEvent('increased', {detail: {counter: this.counter}})); } toString(value) { return value.toString(); } }); #webcomponents @LostInBrittany

Slide 43

Slide 43

Skatejs #webcomponents @LostInBrittany

Slide 44

Slide 44

Skatejs #webcomponents @LostInBrittany

Slide 45

Slide 45

Skatejs ● ● ● ● Lightweight web component library Abstracts away attribute / property semantics Very very fast Can use many renderers ○ Basic innerHTML (default) ○ preact ○ lit-html #webcomponents @LostInBrittany

Slide 46

Slide 46

Skatejs import { props, withComponent } from '/node_modules/skatejs/dist/es/index.js'; import withLitHtml from '/node_modules/@skatejs/renderer-lit-html/dist/es/index.js'; import { html } from '/node_modules/lit-html/lit-html.js'; class MySkateCounter extends withComponent(withLitHtml()){ static get props () { return { // By declaring the property an attribute, we can now pass an initial value // for the count as part of the HTML. counter: props.number({ attribute: true }) }; } #webcomponents @LostInBrittany

Slide 47

Slide 47

Skatejs render({ counter }) { return html<style> … <div class="container"> <button class="button" @click="${() => this.increment()}"> <img src="./img/skate.png" alt="Polymer"> </button> <div class="value">${counter}</div> </div>; } increment() { console.log('sdgws', this.counter) this.counter = Number.parseInt(this.counter) + 1; this.dispatchEvent(new CustomEvent('increased', {detail: {counter: this.counter}})); } } window.customElements.define('my-skate-counter', MySkateCounter); #webcomponents @LostInBrittany

Slide 48

Slide 48

Stencil A new breed of Web Components #webcomponents @LostInBrittany

Slide 49

Slide 49

Next generation Ionic Ionic 4 will be fully based on web components using a new toolkit: Stencil @LostInBrittany #webcomponents

Slide 50

Slide 50

New kid on the block Announced during Polymer Summit #webcomponents @LostInBrittany

Slide 51

Slide 51

Not another library A Web Component compiler #webcomponents @LostInBrittany

Slide 52

Slide 52

A build time tool To generate standard web components #webcomponents @LostInBrittany

Slide 53

Slide 53

Fully featured ● Virtual DOM ● Async rendering ● Reactive data-binding ● TypeScript ● JSX #webcomponents @LostInBrittany

Slide 54

Slide 54

And the cherry on the cake Server-Side Rendering #webcomponents @LostInBrittany

Slide 55

Slide 55

Hands on Stencil Clone the starter project git clone https://github.com/ionic-team/stencil-app-starter my-app cd my-app git remote rm origin npm install Start a live-reload server npm start #webcomponents @LostInBrittany

Slide 56

Slide 56

Hands on Stencil #webcomponents @LostInBrittany

Slide 57

Slide 57

Hands on Stencil #webcomponents @LostInBrittany

Slide 58

Slide 58

Some concepts render() { return ( <div>Hello {this.name}</div> ) } render() { return ( <div>{this.name ? <p>Hello {this.name}</p> : <p>Hello World</p>}</div> ); } JSX declarative template syntax #webcomponents @LostInBrittany

Slide 59

Slide 59

Some concepts import { Component } from '@stencil/core'; @Component({ tag: 'todo-list', styleUrl: 'todo-list.scss' }) export class TodoList { @Prop() color: string; @Prop() favoriteNumber: number; @Prop() isSelected: boolean; @Prop() myHttpService: MyHttpService; } Decorators #webcomponents @LostInBrittany

Slide 60

Slide 60

Some concepts import { Event, EventEmitter } from '@stencil/core'; ... export class TodoList { @Event() todoCompleted: EventEmitter; someAction(todo: Todo) { this.todoCompleted.emit(todo); } @Listen('todoCompleted') todoCompletedHandler(event: CustomEvent) { console.log('Received the custom todoCompleted event: ', event.detail); } } Events #webcomponents @LostInBrittany

Slide 61

Slide 61

Some concepts @Component({ tag: 'shadow-component', styleUrl: 'shadow-component.scss', shadow: true }) export class ShadowComponent { } Optional Shadow DOM #webcomponents @LostInBrittany

Slide 62

Slide 62

Some concepts stencil.config.js exports.config = { namespace: 'myname', generateDistribution: true, generateWWW: false, ... }; Generate distribution #webcomponents @LostInBrittany

Slide 63

Slide 63

Stencil import { Component, Prop, PropWillChange, State, Event, EventEmitter } from '@stencil/core'; @Component({ tag: 'stencil-counter', styleUrl: 'stencil-counter.scss', shadow: true }) export class StencilCounter { @Prop() counter: number; @State() currentCount: number; @Event() currentCountChanged: EventEmitter; @Watch('counter') counterChanged(newValue: number) { this.currentCount = newValue; } componentWillLoad() { this.currentCount = this.counter; } increase() { this.currentCount++; this.currentCountChanged.emit({ counter: this.currentCount }); } render() { return ( <div class="container"> <div class="button" onClick={() => this.increase()}> <img src="./img/stencil.png" /> </div> <div class="value" > {this.currentCount} </div> </div> ); } } #webcomponents @LostInBrittany

Slide 64

Slide 64

Conclusion That's all folks! #webcomponents @LostInBrittany