AfrotechFest 2018: A Glimmer of Hope - The Modern State of Web Components

AfrotechFest 2018: A Glimmer of Hope - The Modern State of Web Components

Talk about the current state of web components and how Glimmer.js can be used to build them at Afrotechfest 2018, London, UK.

28f659b33a3d25b174b60e4659ab1bcd?s=128

Jessica Jordan

January 27, 2018
Tweet

Transcript

  1. A GLIMMER OF HOPE Jessica Jordan @jjordan_dev THE MODERN STATE

    OF WEB COMPONENTS
  2. jjordan_dev jessica-jordan https://the-emberjs-times.ongoodbits.com/ https://www.meetup.com/ Ember-js-Berlin/

  3. BREAKING DOWN COMPLEXITY INTO COMPONENTS

  4. BREAKING DOWN COMPLEXITY INTO COMPONENTS

  5. REUSE FUNCTIONALITY THROUGH COMPONENTS

  6. REUSE FUNCTIONALITY THROUGH COMPONENTS

  7. Ember Addons REUSE FUNCTIONALITY THROUGH COMPONENTS Extending apps built upon

    the same JS framework ✅
  8. Ember Addons REUSE FUNCTIONALITY THROUGH COMPONENTS Extending apps built upon

    the same JS framework ✅
  9. Ember Addons ? REUSE FUNCTIONALITY THROUGH COMPONENTS Extending apps built

    upon the same JS framework Extending any JS app? ✅
  10. WEB COMPONENTS REUSABILITY BASED ON OPEN WEB STANDARDS

  11. THE WEB COMPONENT SPEC CUSTOM ELEMENTS HTML IMPORT SHADOW DOM

    TEMPLATE
  12. THE WEB COMPONENT SPEC - CUSTOM ELEMENTS V1 CUSTOM ELEMENTS

    HTML IMPORT SHADOW DOM TEMPLATE See also W3C working draft on custom elements
  13. CUSTOMELEMENTREGISTRY INTERFACE class CustomElementClass extends HTMLElement { // ... }

  14. CUSTOMELEMENTREGISTRY INTERFACE customElements.define(name, constructor, options);

  15. CREATING CUSTOM ELEMENTS More on the MDN Guide on Custom

    Elements class LinkedImage extends HTMLElement { constructor() { super(); var img = document.createElement('img'); img.alt = this.getAttribute('data-name'); img.src = this.getAttribute('data-img'); // …more setup work img.addEventListener('click', () => { window.location = this.getAttribute('data-url'); }); } customElements.define(‘linked-image’, LinkedImage);
  16. CREATING CUSTOM ELEMENTS <linked-image data-name=“Zoey by Lindsey Wilson" data-img="https://emberjs.com/images/zoey.png" data-url=“https://emberjs.com">

    </linked-image> More on the MDN Guide on Custom Elements
  17. THE WEB COMPONENT SPEC - BROWSER SUPPORT CUSTOM ELEMENTS HTML

    IMPORT SHADOW DOM TEMPLATE See more about browser support on webcomponents.org http://slides.com/sara_harkousse/web-components-talk-ruhrjs-2017#/19 It’s all rainbows and unicorns! Is it? Sara Harkousse at RuhrJS 2017
  18. THE WEB COMPONENT SPEC - BROWSER SUPPORT CUSTOM ELEMENTS HTML

    IMPORT SHADOW DOM TEMPLATE ⛔ ⛔ ✴ See more about browser support on webcomponents.org http://slides.com/sara_harkousse/web-components-talk-ruhrjs-2017#/19 It’s all rainbows and unicorns! Is it? Sara Harkousse at RuhrJS 2017
  19. Image from giphy.com

  20. bower install --save webcomponents/webcomponentsjs npm install @webcomponents/webcomponentsjs

  21. Image from https://tenor.com/

  22. “Fast and light-weight UI components for the web” announced March

    2017 Yehuda Katz & Tom Dale, EmberConf 2017, Keynote extracted from Ember’s rendering engine Glimmer
  23. WITH GLIMMER.JS BUILDING WEB COMPONENTS

  24. BUILDING A STREET MAP https://goo.gl/TY4o9t

  25. GETTING STARTED yarn global add ember-cli ember new glimmer-map -b

    @glimmer/blueprint —-web-component
  26. Glimmer-map |── config | |—- environment.js | |—- module-map.ts |

    |—- resolver-configuration.ts | |── dist |── src | |── ui | | |── components | | | └── my-app | | | |── component.ts | | | |── template.hbs | | | | | |── styles | | | └── app.css | | | | | |── index.html | | | |── index.ts | |── main.ts | |── ember-cli-build.js | ... other files ...
  27. INITIALIZING THE CUSTOM ELEMENT function initializeCustomElement(app: Application, name: string): void

    { // creating a GlimmerElement instance from HTMLElement function GlimmerElement() { return Reflect.construct(HTMLElement, [], GlimmerElement); } GlimmerElement.prototype = Object.create(HTMLElement.prototype, { constructor: { value: GlimmerElement }, connectedCallback: { value: function connectedCallback(): void { // ... bring element into the DOM and do setup work // ... } } }); // finally registering the component via customElements v1 API window.customElements.define(name, GlimmerElement); }
  28. INITIALIZING THE CUSTOM ELEMENT function initializeCustomElement(app: Application, name: string): void

    { // creating a GlimmerElement instance from HTMLElement function GlimmerElement() { return Reflect.construct(HTMLElement, [], GlimmerElement); } GlimmerElement.prototype = Object.create(HTMLElement.prototype, { constructor: { value: GlimmerElement }, connectedCallback: { value: function connectedCallback(): void { // ... bring element into the DOM and do setup work // ... } } }); // finally registering the component via customElements v1 API window.customElements.define(name, GlimmerElement); }
  29. INITIALIZING THE CUSTOM ELEMENT function initializeCustomElement(app: Application, name: string): void

    { // creating a GlimmerElement instance from HTMLElement function GlimmerElement() { return Reflect.construct(HTMLElement, [], GlimmerElement); } GlimmerElement.prototype = Object.create(HTMLElement.prototype, { constructor: { value: GlimmerElement }, connectedCallback: { value: function connectedCallback(): void { // ... bring element into the DOM and do setup work // ... } } }); // finally registering the component via customElements v1 API window.customElements.define(name, GlimmerElement); }
  30. INITIALIZING THE CUSTOM ELEMENT function initializeCustomElement(app: Application, name: string): void

    { // creating a GlimmerElement instance from HTMLElement function GlimmerElement() { return Reflect.construct(HTMLElement, [], GlimmerElement); } GlimmerElement.prototype = Object.create(HTMLElement.prototype, { constructor: { value: GlimmerElement }, connectedCallback: { value: function connectedCallback(): void { // ... bring element into the DOM and do setup work // ... } } }); // finally registering the component via customElements v1 API window.customElements.define(name, GlimmerElement); }
  31. DEPENDENCY MANAGEMENT yarn add --dev leaflet

  32. DEPENDENCY MANAGEMENT yarn add --dev leaflet

  33. DEPENDENCY MANAGEMENT yarn add --dev leaflet Modules Syntax

  34. DEPENDENCY MANAGEMENT yarn add --dev leaflet Modules Syntax

  35. DEPENDENCY MANAGEMENT yarn add --dev leaflet Modules Syntax Read more

    about dependency management in the related blog post: https://goo.gl/TY4o9t
  36. DEPENDENCY MANAGEMENT // src/ui/components/glimmer-map/component.ts import Component from '@glimmer/component'; import L

    from 'leaflet'; export default class GlimmerMap extends Component { }
  37. DEPENDENCY MANAGEMENT // src/ui/components/glimmer-map/component.ts import Component from '@glimmer/component'; import L

    from 'leaflet'; export default class GlimmerMap extends Component { }
  38. DEPENDENCY MANAGEMENT // src/ui/components/glimmer-map/component.ts import Component from '@glimmer/component'; import L

    from 'leaflet'; export default class GlimmerMap extends Component { }
  39. DEPENDENCY MANAGEMENT // src/ui/components/glimmer-map/component.ts import Component from '@glimmer/component'; import L

    from 'leaflet'; export default class GlimmerMap extends Component { . . . }
  40. DOM Removal DOM Insertion Re-render

  41. DOM Removal DOM Insertion Re-render didInsertElement(){}

  42. COMPONENT LIFECYCLE HOOKS // src/ui/components/glimmer-map/component.ts import Component from "@glimmer/component"; import

    L from 'leaflet'; export default class GlimmerMap extends Component { didInsertElement() { } }
  43. COMPONENT LIFECYCLE HOOKS // src/ui/components/glimmer-map/component.ts import Component from "@glimmer/component"; import

    L from 'leaflet'; export default class GlimmerMap extends Component { didInsertElement() { } }
  44. COMPONENT LIFECYCLE HOOKS // src/ui/components/glimmer-map/component.ts import Component from "@glimmer/component"; import

    L from 'leaflet'; export default class GlimmerMap extends Component { didInsertElement() { this.createMapInstance(); } }
  45. COMPONENT LIFECYCLE HOOKS // src/ui/components/glimmer-map/component.ts //… import L from 'leaflet';

    export default class GlimmerMap extends Component { didInsertElement() { this.createMapInstance(); } createMapInstance() const element = this.bounds.firstNode.querySelector(‘#map'); this.map = L.map(element).setView([41.08, 11.068], 12); } }
  46. // src/ui/components/glimmer-map/component.ts import Component from "@glimmer/component"; import L from 'leaflet';

    export default class GlimmerMap extends Component { didInsertElement() { this.createMapInstance(); } createMapInstance() const element = this.bounds.firstNode.querySelector(‘#map'); this.map = L.map(element).setView([41.08, 11.068], 12); } } COMPONENT LIFECYCLE HOOKS
  47. // src/ui/components/glimmer-map/component.ts import Component from "@glimmer/component"; import L from 'leaflet';

    export default class GlimmerMap extends Component { didInsertElement() { this.createMapInstance(); } createMapInstance() const element = this.bounds.firstNode.querySelector(‘#map'); this.map = L.map(element).setView([41.08, 11.068], 12);; } } COMPONENT LIFECYCLE HOOKS
  48. // src/ui/components/glimmer-map/component.ts import Component from "@glimmer/component"; import L from 'leaflet';

    export default class GlimmerMap extends Component { didInsertElement() { this.createMapInstance(); this.renderMap(); } createMapInstance() const element = this.bounds.firstNode.querySelector(‘#map'); this.map = L.map(element).setView([41.08, 11.068], 12); } } COMPONENT LIFECYCLE HOOKS
  49. export default class GlimmerMap extends Component { didInsertElement() { this.createMapInstance();

    this.renderMap(); } renderMap() { L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', { attribution: ‘….’, maxZoom: 18, id: 'mapbox.streets', accessToken: 'your.mapbox.access.token' }).addTo(this.map); } } COMPONENT LIFECYCLE HOOKS
  50. None
  51. GLIMMER.JS TRACKING CHANGES IN COMPONENTS

  52. COMPUTED PROPERTIES <script> class XCustom extends Polymer.Element { static get

    properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } </script> <template> My name is <span>{{fullName}}</span> </template> import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ firstName: null, lastName: null, fullName: computed('firstName', 'lastName', function() { return `${this.get('firstName')} {this.get('lastName')}`; }) });
  53. COMPUTED PROPERTIES <script> class XCustom extends Polymer.Element { static get

    properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } </script> <template> My name is <span>{{fullName}}</span> </template> import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ firstName: null, lastName: null, fullName: computed('firstName', 'lastName', function() { return `${this.get('firstName')} {this.get('lastName')}`; }) });
  54. COMPUTED PROPERTIES <script> class XCustom extends Polymer.Element { static get

    properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } </script> <template> My name is <span>{{fullName}}</span> </template> import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ firstName: null, lastName: null, fullName: computed('firstName', 'lastName', function() { return `${this.get('firstName')} {this.get('lastName')}`; }) }); // app/templates/components/x-custom.hbs My name is <span>{{fullName}}</span>
  55. COMPUTED PROPERTIES <script> class XCustom extends Polymer.Element { static get

    properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } </script> <template> My name is <span>{{fullName}}</span> </template> import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ firstName: null, lastName: null, fullName: computed('firstName', 'lastName', function() { return `${this.get('firstName')} {this.get('lastName')}`; }) }); // app/templates/components/x-custom.hbs My name is <span>{{fullName}}</span>
  56. COMPUTED PROPERTIES <script> class XCustom extends Polymer.Element { static get

    properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } </script> <template> My name is <span>{{fullName}}</span> </template> import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ firstName: null, lastName: null, fullName: computed('firstName', 'lastName', function() { return `${this.get('firstName')} {this.get('lastName')}`; }) }); // app/templates/components/x-custom.hbs My name is <span>{{fullName}}</span> Com puted Properties in Em berJS
  57. COMPUTED PROPERTIES <script> class XCustom extends Polymer.Element { static get

    properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } </script> <template> My name is <span>{{fullName}}</span> </template> import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ firstName: null, lastName: null, fullName: computed('firstName', 'lastName', function() { return `${this.get('firstName')} {this.get('lastName')}`; }) }); // app/templates/components/x-custom.hbs My name is <span>{{fullName}}</span> Com puted Properties in Em berJS
  58. GLIMMER = IMMUTABLE PROPERTIES PER DEFAULT

  59. RERENDER ON USER INTERACTION // src/ui/components/glimmer-map/component.ts export default class GlimmerMap

    extends Component { @tracked lon: number = 11.6020; @tracked lat: number = 48.1351; //... }
  60. // src/ui/components/glimmer-map/component.ts export default class GlimmerMap extends Component { @tracked

    lon: number = 11.6020; @tracked lat: number = 48.1351; //… } RERENDER ON USER INTERACTION
  61. // src/ui/components/glimmer-map/component.ts export default class GlimmerMap extends Component { @tracked

    lon: number = 11.6020; @tracked lat: number = 48.1351; //… } <!-- src/ui/components/glimmer-map/template.hbs --> <div class="glimmer-map"> <div id="map"></div> E: <input class="x-coord" type="number" step="0.0001" value={{lon}}/> N: <input class="y-coord" type="number" step="0.0001" value={{lat}}/> </div> RERENDER ON USER INTERACTION
  62. // src/ui/components/glimmer-map/component.ts export default class GlimmerMap extends Component { @tracked

    lon: number = 11.6020; @tracked lat: number = 48.1351; //… } <!-- src/ui/components/glimmer-map/template.hbs --> <div class="glimmer-map"> <div id="map"></div> E: <input class="x-coord" type="number" step="0.0001" value={{lon}} oninput={{action setView}}/> N: <input class="y-coord" type="number" step="0.0001" value={{lat}} oninput={{action setView}} /> </div> RERENDER ON USER INTERACTION
  63. // src/ui/components/glimmer-map/component.ts export default class GlimmerMap extends Component { @tracked

    lon: number = 11.6020; @tracked lat: number = 48.1351; setView() { this.lon = this.element.querySelector('x-coord').value; this.lat = this.element.querySelector('y-coord').value; this.map.setView([this.lat, this.lon], 12); } //… } <!-- src/ui/components/glimmer-map/template.hbs --> <div class="glimmer-map"> <div id="map"></div> E: <input class="x-coord" type="number" step="0.0001" value={{lon}} oninput={{action setView}}/> N: <input class="y-coord" type="number" step="0.0001" value={{lat}} oninput={{action setView}} /> </div> RERENDER ON USER INTERACTION
  64. TRACKING CHANGES FOR USER INTERACTION // src/ui/components/glimmer-map/component.ts export default class

    GlimmerMap extends Component { @tracked lon: number = 11.6020; @tracked lat: number = 48.1351; setView() { this.lon = this.element.querySelector('x-coord').value; this.lat = this.element.querySelector('y-coord').value; this.map.setView([this.lat, this.lon], 12); } //… }
  65. IMPORTING IT INTO EXISTING APP ember build --environment=production

  66. IMPORTING IT INTO EXISTING APP <!DOCTYPE html> <html> <head> <title>My

    Other App</title> <script src="/assets/webcomponentsjs/webcomponents-lite.js"></script> <link rel="stylesheet" href="/assets/glimmer-map/app.css"></link> </head> <body> <glimmer-map></glimmer-map> <script src=“/assets/glimmer-map/app.js"></script> </body> </html>
  67. https://goo.gl/TY4o9t

  68. THE MEANING OF GLIMMER.JS WEB COMPONENTS VS. COMPLEX APPLICATIONS

  69. UPGRADE FROM GLIMMER COMPONENTS TO EMBER APPS Tom Dale: EmberConf

    2017: State of the Union https://www.emberjs.com/blog/2017/04/05/emberconf-2017-state-of-the-union.html
  70. UPGRADE FROM GLIMMER COMPONENTS TO EMBER APPS Request for Comments

    in Final Comment Period: Splitting Ember into Packages https://github.com/emberjs/rfcs/pull/284
  71. Jessica Jordan @jjordan_dev EXCITING TIMES FOR CREATING APPS AND WEB

    COMPONENTS
  72. THANK YOU Jessica Jordan @jjordan_dev