Web Componenty

jako lingua franca front-endu

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

Web Componenty

jako lingua franca front-endu

Komponent?


					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

					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>

					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
Fajne to pojęcie, takie nie za konkretne
Reużywalne powiązanie HTML-a z JS-em

Web Components

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>

Attribute vs. property

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

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

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';
  }
						}
					

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 />

Dobre do

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

Wyzwania

  • 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

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

out?

reactjs.org/docs/web-components.html

custom-elements-everywhere.com

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

Web Components + Angular

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>

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);
					

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

custom-elements-everywhere.com

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

Web Components + Vue

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

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

custom-elements-everywhere.com

Vitaliy też jest OK z Web Componentami!

Idziemy w to?

GitHub, Salesforce, YouTube

Janek poleca