Web Componenty

jako lingua franca front-endu

Image Image Image

  • Pesymista widzi tunel
  • Optymista widzi światło w tunelu
  • Realista widzi pociąg
  • Maszynista widzi 3 kretynów na torach
Perspektywa ma znaczenie!
Image Image
Janek śnieg
Image
Image
Image Image Image Image Image Image Image Image Image Image
Image Image
Image
Image Image Image
Image Image
Image Image
Image Image
Image Image
Image Image
Image Image
Image Image
Image Image
Image
Image
Image
Image
Image
Image
Image
Image
Robert Agnieszka
Image
Image
Image
Image
Image
Image
Vitaliy Janek
Image

Web Componenty

jako lingua franca front-endu

Komponent?

Image

					import React from 'react';
					import ReactDOM from 'react-dom';

					const Counter = ({initial}) => {
					  const [count, setCount] = React.useState(initial);
					  return (
					    <div className="centered">
					      <button onClick={() => setCount(count - 1)}>
					    	-
					      </button>
					      <span>{count}</span>
					      <button onClick={() => setCount(count + 1)}>
					        +
					      </button>
					    </div>
					  );
					}

					ReactDOM.render(
					  <Counter initial={10} />,  
					  document.getElementById('react-app')
					);
					
#react-app
Image

					import 'zone.js'; import 'core-js/es/reflect'; import 'core-js/features/reflect';
					import { BrowserModule } from '@angular/platform-browser';
					import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
					import { NgModule, Component, Input } from '@angular/core';

					@Component({
					  selector: 'angular-counter',
					  template: `
					    <div class="centered">
					      <button (click)="setCount(count - 1)">
					        -
					      </button>
					      <span>{{count}}</span>
					      <button (click)="setCount(count + 1)">
					        +
					      </button>
					    </div>
					  `
					})
					class Counter {
					  @Input() count: number;

					  setCount(count: number) {
					    this.count = count;
					  }
					}

					@Component({ selector: 'angular-app', template: '<angular-counter [count]="10">' }) class App { }

					@NgModule({
					  imports: [ BrowserModule ],
					  declarations: [ App, Counter ],
					  bootstrap: [ App ]
					})
					class AppModule { }

					platformBrowserDynamic().bootstrapModule(AppModule);
					
<angular-app>
Image

					import Vue from 'vue';

					const Counter = Vue.component('vue-counter', {
					  props: { initial: Number },
					  data: function() {
					    return {
					      count: this.initial
					    }
					  },
					  template: `
					    <div class="centered">
					      <button @click="--count">
					        -
					      </button>
					      <span>{{count}}</span>
					      <button @click="++count">
					        +
					      </button>
					    </div>
					  `
					});

					new Vue({
					  el: '#vue-app',
					  components: { Counter },
					  template: '<vue-counter :initial="10" />'
					})
					
#vue-app
Image
Fajne to pojęcie, takie nie za konkretne
Image Image
Reużywalne powiązanie HTML-a z JS-em

Web Components

Image Image

Jak?


						class FullWebComp extends HTMLElement {
						  constructor() {
						    super();
						    this.attachShadow({mode: 'closed'})
      .appendChild(document        .getElementById('h2-template')
          .content
          .cloneNode(true));  }
}
customElements.define('full-web-comp', FullWebComp);
						
<full-web-comp></full-web-comp>
Image

Attribute vs. property

<input type="checkbox" checked="">

							const input = document.querySelector('[type="checkbox"]');
							const [attr, prop] = [
								input.getAttribute('checked'), 
								input.checked
							];
							verify(attr, prop);
						
Image

Baza


						class CheckboxWrapper extends HTMLElement {
						  constructor() {
						    super();
						    this.input = document.createElement('input');
						    this.input.type = 'checkbox';
						  }  get checked() { return this.isTrue('checked'); }  set checked(value) {
    const bool = !!value;
    if (bool === this.checked) return;    this.input.checked = bool;    bool ? this.setAttribute('checked', '') : this.removeAttribute('checked');    this.dispatchEvent(new Event('change', { bubbles: true }));  }  isTrue(attr) {
    return this.hasAttribute(attr) && this.getAttribute(attr) !== 'false';
  }
						}
					
Image

lvl2


						customElements.define('m3-switch', class extends CheckboxWrapper {
						  constructor() {
						    super();
						    this.attachShadow({ mode: 'open' });
						  }
						  connectedCallback() {
						    this.input.checked = this.checked;
						    this.shadowRoot.innerHTML = `
						      
`; this.shadowRoot.prepend(this.input); this.shadowRoot.appendChild(document.getElementById('style-template') .content .cloneNode(true)); this.onclick = (e) => { e.preventDefault(); this.checked = !this.checked; } } get round() { return this.isTrue('round'); } });
<m3-switch round checked /><m3-switch />
Image

ImageDobre do

Image
  • Komponentów UI (design system)
  • Reużycia pomiędzy apkami
    • +3rd party Web Components
  • "Liści" w drzewie DOM
    • Zagnieżdżenie? Kiepsko

Image Wyzwania

Image
  • Accessibility
  • SEO
    
    								<bad-component></bad-component>
    								<better-component>hello world</better-component> 
    							
  • Progressive enhancement
    
    								<a is="twitter-share"
    								  text="A Twitter share button with progressive enhancement"
    								  url="https://codepen.io/WebReflection/pen/LKWyLB?editors=0010"
    								  via="webreflection" />
    							
  • SSR

Web Components + React

Image Image

in


						import React, { useState, useEffect } from 'react';
						import ReactDOM from 'react-dom';
	
						const Demo = ({initial}) => {
						  const [checked, setChecked] = React.useState(initial);
						  const refWc = React.createRef();
						  const handleChange = (e) => setChecked(e.target.checked);
						  useEffect(() => {
						    refWc.current.onchange = handleChange;
						  });
						  return (
						    <label>
						      <m3-switch checked={checked} ref={refWc}></m3-switch>
						      {`${checked}`}
						    </label>
						  );
						}
	
						ReactDOM.render(
						  <Demo initial={true} />,  
						  document.getElementById('react-wc-app')
						);
					
#react-wc-app
Image Image

out?

Image
Image
reactjs.org/docs/web-components.html

custom-elements-everywhere.com

Image
Zespół Roberta czasem napisze Web Component, ale raczej skupi się na konsumowaniu.
Image Image

Web Components + Angular

Image Image

in


					import 'zone.js'; import 'core-js/es/reflect'; import 'core-js/features/reflect';
					import { BrowserModule } from '@angular/platform-browser';
					import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
					import { NgModule, Component, Input, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

					@Component({
					  selector: 'angular-wc-app',
					  template: `
					    <m3-switch [checked]="checked" (change)="handleChange($event)"></m3-switch>
					    {{checked}}
					  `
					})
					class Demo {
					  checked = true;

					  handleChange(event: Event) {
					    this.checked = event.target.checked;
					  }
					}

					@NgModule({
					  imports: [ BrowserModule ],
					  declarations: [ Demo ],
					  bootstrap: [ Demo ],
					  schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
					})
					class AppModule { }

					platformBrowserDynamic().bootstrapModule(AppModule);
					
<angular-wc-app>
Image Image

out 1/2


					import 'zone.js'; import 'core-js/es/reflect'; import 'core-js/features/reflect';
					import { createCustomElement } from '@angular/elements';
					import { BrowserModule } from '@angular/platform-browser';
					import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
					import { 
					  NgModule, Input, Output, Component, ViewEncapsulation, EventEmitter, Inject, Injector 
					} from '@angular/core';

					@Component({
					  selector: 'm3-button',
					  template: `<button (click)="handleClick()" style="padding: .5em">{{label}}</button>`,
					  encapsulation: ViewEncapsulation.ShadowDom
					})
					class ButtonComponent {
					  @Input() label = 'default label';
					  @Output() action = new EventEmitter<number>();
					  private count = 0;
					  handleClick() { this.action.emit(++this.count); }
					}

					@NgModule({
					  imports: [ BrowserModule ],
					  declarations: [ ButtonComponent ],
					  entryComponents: [ ButtonComponent ]
					})
					class AppModule {
					  static parameters = [[Injector]];
					  constructor(private injector: Injector) {
					    const customButton = createCustomElement(ButtonComponent, { injector });
					    customElements.define('m3-button', customButton);
					  }
					  ngDoBootstrap() {}
					}
					platformBrowserDynamic().bootstrapModule(AppModule);
					
Image Image

out 2/2


							import React, { useState, useEffect } from 'react';
							import ReactDOM from 'react-dom';
		
							const Demo = ({label}) => {
							  const [count, setCount] = React.useState(0);
							  const refWc = React.createRef();
							  const handleClick = (e) => setCount(e.detail);
							  useEffect(() => {
							    refWc.current.addEventListener('action', handleClick);
							  });
							  return (
							    <>
							      <m3-button label={label} ref={refWc}></m3-button>
							      <small style={{verticalAlign: 'bottom'}}>{`x ${count}`}</small>
							    </>
							  );
							}
		
							ReactDOM.render(
							  <Demo label="test" />,  
							  document.getElementById('react-wc-app2')
							);
						
#react-wc-app2
Image Image Image

custom-elements-everywhere.com

Image
Agnieszka z zespołem ochoczo zaadoptowała Web Componenty!
Image Image

Web Components + Vue

Image Image

in


					import Vue from 'vue';

					const Demo = Vue.component('vue-demo', {
					  template: `<div>
					    <m3-switch :checked="checked" @change="handleChange"></m3-switch> {{checked}}
					    <br>
					    <m3-button :label="label" @action="handleClick"></m3-button>
					  </div>`,
					  methods: {
					    handleChange: function(e) { this.checked = e.target.checked; },
					    handleClick: function(e) { this.label = e.detail }
					  },
					  data: function() {
					    return {
					      checked: this.initial,
					      label: 0
					    }
					  },
					  props: { initial: Boolean }
					});

					new Vue({
					  el: '#vue-wc-app',
					  components: { Demo },
					  template: '<vue-demo :initial="true" />'
					})
					
#vue-wc-app
Image Image

out


					import Vue from 'vue';
					import wrap from '@vue/web-component-wrapper';

					const VueWebComponent = Vue.component('m3-hello', {
					  template: `<div>Hi, {{msg}}!</div>`,
					  props: { msg: String }
					});

					const CustomElement = wrap(Vue, VueWebComponent);
					customElements.define('m3-hello', CustomElement);
					

					document.getElementById('js-wc-app').innerHTML = `
					  <m3-hello msg="world"></m3-hello>
					`;
					
#js-wc-app
Image Image

custom-elements-everywhere.com

Image
Vitaliy też jest OK z Web Componentami!
Image Image

Idziemy w to?

GitHub, Salesforce, YouTube
Image Image Image Image
Image

Janek poleca

Image