Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Modern Web Development with Web Components

Modern Web Development with Web Components

Presentation Summary
The software development of the web is being transformed by the introduction of Web Components. Web sites such as YouTube, Facebook, Bloomberg, EA Sports, Dominos Pizza, and many more are using web components to serve billions of consumers worldwide. Originally started as a way to extend HTML with custom tags, the web components spec. has grown to encompass a collection of features that make development of modern software easier to create, manage, and deploy. In this talk we will explore several web component libraries and frameworks such as Polymer 3, lit-html, and lit-element, and also tools for creating Progressive Web Apps. We will also cover scalability techniques for building large scale Single Page Applications and Web Components, plus the need for real-time updates and data streaming.

Speaker Biography
Simon Gauvin is an expert in the field of UX, mobile and cloud computing with 25 years of experience developing several startup software companies. He was former VP of Applications Technology at Plazmic Inc. (acquired by RIM in 2002) where he led the development of a mobile media platform for Warner Brothers and Disney in the Japanese mobile market. He completed Ph.D. research in Computer Science at Dalhousie University and has been published in several leading academic journals. Simon has authored software patents, invented and developed several new programming languages, and used this work to co-found Vizwik.com, a visual programming mobile app development platform. He recently co-founded another startup, Safelii Inc., which is using mobile and AI technology to help improve health and safety services for employees in corporations. He is also a freelance Chief Technology Officer consulting for small to medium sized companies in Atlantic Canada.

Register on Eventbrite

Moncton Developer User Group

November 19, 2019
Tweet

More Decks by Moncton Developer User Group

Other Decks in Technology

Transcript

  1. function showMessage() { console.log(“Hello World”); } .my-class { font-size: 20px;

    color: #217FF9; } <div id="header"> <span>Header</span> </div> Markup Declarative Functional 3 different language types!
  2. function showMessage(msg) { var txt = document.getElementByID(“text”); txt.innerHTML = msg;

    txt.classList.add(“my-class”); } .my-class { font-size: 20px; color: #217FF9; } <div id="header" class=”my-class”> <span id=”text”></span> </div>
  3. Loose links between CSS/JS/HTML CSS leakage into DOM Code loading

    dependencies OOP/MVC hard to achieve No way to extend HTML
  4. <!DOCTYPE html> <html> <body> <cool-heading> <h1>Hello world!</h1> </cool-heading> <script> class

    CoolHeading extends HTMLElement { constructor() { super(); this.addEventListener('click', () => { this.style.color = 'red'; }); } connectedCallback() { this.style.color = 'blue'; } } customElements.define('cool-heading', CoolHeading); </script> </body> </html> JavaScript HTML CSS
  5. <link rel="import" href="../polymer/polymer-element.html"> <dom-module id="x-custom"> <!-- Optional shadow DOM template

    --> <template> <style> /* CSS rules for your element */ </style> <!-- shadow DOM for your element --> <div>{{greeting}}</div> <!-- data bindings in local DOM --> </template> <script> // Define the element's API using an ES2015 class class XCustom extends Polymer.Element { static get is() { return 'x-custom'; } // Declare properties for the element's public API static get properties() { return { greeting: { type: String, value: "Hello!" } } } // Add methods to the element's public API greetMe() { console.log(this.greeting); } } // Register the x-custom element with the browser customElements.define(XCustom.is, XCustom); </script> </dom-module> HTML Imports Custom Elements Shadow DOM Dataflow System ES5 Classes Utility Functions Events Bower package manager
  6. import {PolymerElement, html} from '@polymer/polymer/polymer-element.js'; // Define the element's API

    using an ES2015 class class XCustom extends PolymerElement { // Define optional shadow DOM template static get template() { return html` <style> /* CSS rules for your element */ </style> <!-- shadow DOM for your element --> <div>[[greeting]]</div> <!-- data bindings in shadow DOM --> `; } // Declare properties for the element's public API static get properties() { return { greeting: { type: String } } } constructor() { super(); this.greeting = 'Hello!'; } // Add methods to the element's public API greetMe() { console.log(this.greeting); } } // Register the x-custom element with the browser customElements.define('x-custom', XCustom); ES6 Modules - import ES5 Classes HTML/CSS template Events Dataflow System Custom Elements
  7. import { LitElement, html, css} from 'lit-element'; class WebCom extends

    LitElement { static get styles() { return css` :host { display: block; } `;} render() { return html` <div id="header" class=”my-class”> <span id=”text”></span> </div> `;} constructor() { super(); this.greeting = 'Hello!'; } // Add methods to the element's public API greetMe() { console.log(this.greeting); } } customElements.define('web-com', WebCom); ES6 Modules - import ES5 Classes HTML/CSS template Events Dataflow System Custom Elements
  8. import { LitElement, html, css } from 'lit-element'; class HelloWorld

    extends LitElement { static get properties() { return { name { type: String } }; } render() { return html`<div>Hello ${this.name}</div>`; } } customElements.define('hello-world', HelloWorld);
  9. import { LitElement, html, css } from 'lit-element'; class HelloWorld

    extends LitElement { static get properties() { return { name { type: String } }; } render() { return html`<div>Hello ${this.name}</div>`; } } customElements.define('hello-world', HelloWorld);
  10. import { LitElement, html, css } from 'lit-element'; class HelloWorld

    extends LitElement { static get properties() { return { name { type: String } }; } render() { return html`<div>Hello ${this.name}</div>`; } } customElements.define('hello-world', HelloWorld);
  11. import { LitElement, html, css } from 'lit-element'; class HelloWorld

    extends LitElement { static get properties() { return { name { type: String } }; } render() { return html`<div>Hello ${this.name}</div>`; } } customElements.define('hello-world', HelloWorld);
  12. import { LitElement, html, css } from 'lit-element'; class HelloWorld

    extends LitElement { static get properties() { return { name { type: String } }; } render() { return html`<div>Hello ${this.name}</div>`; } } customElements.define('hello-world', HelloWorld);
  13. import { LitElement, html, css } from 'lit-element'; class HelloWorld

    extends LitElement { static get properties() { return { name { type: String } }; } render() { return html`<div>Hello ${this.name}</div>`; } } customElements.define('hello-world', HelloWorld);
  14. import { LitElement, html, css } from 'lit-element'; class HelloWorld

    extends LitElement { static get properties() { return { name { type: String } }; } static get styles() { return css` :host { display: block; } div { color: red; } `;} render() { return html`<div>Hello ${this.name}</div>`; } } customElements.define('hello-world', HelloWorld);
  15. // Create Template const template = document.createElement(“hello-world”); template.innerHTML = ‘<hello-world></hello-world>’;

    // Clone Template const content = document.importNode(template.content, true); const element = content.querySelector(“hello-world”);
  16. import {Component} from ‘@angular/core’; import {HelloWorld} from ‘./hello-world’; @Component({ selector:

    ‘my-app’, template: ` <div>This is a LitElement in an Angular component</div> <hello-world name=”{{ name }}”></hello-world> ` }) export class AppComponent { name = “Angular and LitElement”; }
  17. import {Component} from ‘@angular/core’; import {HelloWorld} from ‘./hello-world’; @Component({ selector:

    ‘my-app’, template: ` <div>This is a LitElement in an Angular component</div> <hello-world name=”{{ name }}”></hello-world> ` }) export class AppComponent { name = “Angular and LitElement”; }
  18. import { LitElement, html, css } from 'lit-element'; class HelloWorld

    extends LitElement { static get properties() { return { name { type: String } }; } static get styles() { return css` :host { display: block; } div { color: red; } `;} render() { return html`<div>Hello ${this.name}</div>`; } } customElements.define('hello-world', HelloWorld); Declarative What vs. How
  19. import { LitElement, html, css } from 'lit-element'; class HelloWorld

    extends LitElement { static get properties() { return { firstName { type: String }, lastName { type: String } }; } render() { return html` <div>Hello ${this.firstName} ${this.lastName}</div> `; } } customElements.define('hello-world', HelloWorld); //.. element.firstName = “James”; element.lastName = “Bond”; Reactive Async Rendering
  20. • Uses Standard Web Components API/lifecycle • Minimal Feature Set

    • Full Typescript support • 6.7K including lit-html • Just JavaScript, no compilers required
  21. • Batches multiple state changes into one render • Allows

    object-oriented interface with functional behavior • Rendering can now be scheduled and prioritized • No VDOM Diffing • Preserves static vs dynamic structure • Standard JavaScript • 100% compatible with Custom Elements
  22. // Text html`<h1>Hello ${name}</h1>` // Attribute html`<div id=${id}></div>` // Boolean

    Attribute html`<input type="checkbox" ?checked=${checked}>` // Property html`<input .value=${value}>` // Event Handler html`<button @click=${(e) => console.log('clicked')}>Click Me</button>`
  23. const listener = { handleEvent(e) { console.log('clicked'); }, capture: true,

    }; html`<button @click=${listener}>Click Me</button>`
  24. const items = [1, 2, 3]; const list = ()

    => html`items = ${items.map((i) => `item: ${i}`)}`; const items = { a: 1, b: 23, c: 456, }; const list = () => html`items = ${Object.entries(items)}`;
  25. getUserMessage() { if (user.isloggedIn) { return html`Welcome ${user.name}`; } else

    { return html`Please log in`; } } html` ${getUserMessage()} `;
  26. const itemTemplates = []; for (const i of items) {

    itemTemplates.push(html`<li>${i}</li>`); } html` <ul> ${itemTemplates} </ul> `;
  27. const employeeList = (employees) => html` <ul> ${repeat(employees, (employee) =>

    employee.id, (employee, index) => html` <li>${index}: ${employee.firstName}, ${employee.lastName}</li> `)} </ul> `;
  28. import {cache} from 'lit-html/directives/cache.js'; const detailView = (data) => html`<div>...</div>`;

    const summaryView = (data) => html`<div>...</div>`; html`${cache(data.showDetails ? detailView(data) : summaryView(data))} `;
  29. Markup Tags Shadow DOM Custom Elements Events Promises Functions Native

    API Styles Classes Properties/Attributes Mixins Decorators Templates Worker Threads
  30. Application Singletons Routes Localization Forms Security Async Tasks Templates Caching

    Components Dependency Injection State Management Services Reactivity Scheduling Module Loading
  31. DOM

  32. <html> <body> <app-toolbar></app-toolbar> <app-nav></app-nav> <main> <app-home></app-home> </main> </body> </html> <html>

    <body> <my-app></my-app> </body> </html> Many components in a page The App is a component
  33. Properties can trigger code when value is changed. class Component

    extends LitElement { static get properties() { return { userName: { type: String, value: "" } } updated(change) { if (change && change.get('userName')) { this.shadowRoot.getElementById('userName'); // do something... } } render() { return html` <input id="userName"></input> `; } }
  34. DOM

  35. Data can flow into, or out of a component. Values

    of properties are read in the DOM with double square brackets [[x]] for 1-way data flow. <dom-module id="child"> <template> <div>[[name.first]]</div> </template> <script> class Child extends Polymer.Element { static get is() { return "child"; } proporties() { name { Type: String } } } customElements.define(Child.is, Child); </script> </dom-module> <dom-module id="parent"> <template> <child name=”[[value]]”></child> </template> ... </dom-module>
  36. DOM

  37. Components can also communicate up the DOM by using Event

    system in browser. <dom-module id="x-custom"> <template> <button onclick="handleClick">Kick Me</button> </template> <script> class XCustom extends Polymer.Element { static get is() {return 'x-custom'} handleClick() { this.dispatchEvent( new CustomEvent('sign-out', {detail:{count:2}}, { bubbles: true, composed: true })); } } customElements.define(XCustom.is, XCustom); </script> </dom-module>
  38. Components can also listen to Events dispatched by other components.

    <dom-module id="x-custom"> <script> class XCustom extends Polymer.Element { static get is() {return 'x-custom'} constructor() { super(); this.addEventListener('sign-out', function(event) { console.log(“event received”); }); } } customElements.define(XCustom.is, XCustom); </script> </dom-module>
  39. Web Components philosophy “Use the Platform” Fixes the CSS/JS/HTML stack

    with integrated <template> model Fixes CSS leakage with Shadow DOM Fixes code loading JavaScript with imports Fixes OOP/MVC with Components and Dataflow
  40. Not a native app Quick deploy via URL, avoid the

    App Store Feels Like an App - Add to Homescreen support Offline use & sync Immediate update for users No corporate data on users device Cross platform & one code base
  41. Create Firebase account Create firebase project Create polymer project Use

    firebase API directly in Polymer /or polymerfire $firebase init $firebase login $firebase serve $firebase deploy
  42. Transfer the credentials into your app using polymerfire firebase-app component.

    <link rel="import" href="../polymerfire/firebase-app.html"> <dom-module id="my-app"> <template> <firebase-app project-id="myapp" api-key="AIzaSyDXLvla2maDj1BOJvvwLWvHWqaL4650CPM" auth-domain="myapp.firebaseapp.com" database-url="https://myapp.firebaseio.com" storage-bucket="myapp.appspot.com" messaging-sender-id="1064751089005"> </firebase-app> <my-login></my-login> </template> <script> class MyApp extends Polymer.Element { static get is() {return 'x-custom'} } customElements.define(VennChatApp.is, VennChatApp); </script> </dom-module> bower install --save FirebaseExtended/polymerfire
  43. Login will use the <firebase-auth> to validate your Google account.

    Add login() and logout() methods to call auth object API. “dom-if” template used to show/hide components based on existence of user. <link rel="import" href="../polymerfire/firebase-auth.html"> <dom-module id="my-login"> <template> <firebase-auth id="auth" user="{{user}}" provider="google"> </firebase-auth> <template is="dom-if" if="{{user}}"> <my-app user=[[user]]></my-app> </template> <template is="dom-if" if="{{!user}}"> <h1>Venn Chat App</h1> <paper-button raised on-click="login">Login with Google</paper-button> </template> </template> <script> class MyLogin extends Polymer.Element { static get is() {return 'my-login'} login() { return this.$.auth.signInWithPopup(); } logout() { return this.$.auth.signOut(); } } customElements.define(MyLogin.is, MyLogin); </script> </dom-module> bower install --save PolymerElements/paper-button#^2
  44. Firebase Realtime Solve Problem of mobile client updates, push vs

    pull Client 1 Write HTTP RealTime DB Client n Update HTTP
  45. Firebase Realtime Structure : Big JSON Tree Named nodes Auto-gen

    nodes (timestamped) Mostly Typeless - Text, Numbers, Array, Objects
  46. Firestore Solve Scaling problems of Realtime Database, slow queries, data

    size, user count Client 1 Write HTTP Firestore Client n Update HTTP
  47. Firestore Data Collections Documents Collections contain Documents Documents contain data

    AND collections Nesting to 100 levels deep Each doc limited to 1MB in size.
  48. Documents Documents have a name, or auto-gen id Fields are

    a data type... Map is JSON object and can be any depth. Array is limited by doc size.
  49. exports.makeUppercase = functions.database.ref('/messages/{pushId}/text') .onCreate((snapshot, context) => { const text =

    snapshot.val(); const uppercase = text.toUpperCase(); return snapshot.ref.parent.child('text').set(uppercase); });
  50. const express = require('express'); const cors = require('cors'); const app

    = express(); app.use(cors({ origin: true })); app.use(myMiddleware); app.get('/:id', (req, res) => res.send(Widgets.getById(req.params.id))); app.post('/', (req, res) => res.send(Widgets.create())); app.put('/:id', (req, res) => res.send(Widgets.update(req.params.id, req.body))); app.delete('/:id', (req, res) => res.send(Widgets.delete(req.params.id))); app.get('/', (req, res) => res.send(Widgets.list()));
  51. RxFire Update List from subscribing to collection in Firebase import

    { Component, h } from 'preact'; import firebase from 'firebase/app'; import 'firebase/firestore'; import { collectionData } from 'rxfire/firestore'; class AppComponent extends LitElement { constructor() { this.app = firebase.initializeApp({ /* config */ }); this.todosRef = app.firestore().collection('todos'); collectionData(todosRef, 'id').subscribe(todos => { // re-render on each change this.setState({ todos }); }); } setState(...) render() { const lis = this.state.todos.map(t => Html `<li key=${t.id}>${t.title}</li>` ); return html ` <div> <ul> ${lis} </ul> </div> `;} }
  52. Design Mobile Web App (Polymer) Quick Deployment in Field Social

    Sharing of Hazzard Observations Real-time updates Inspections, Audits, Checklists Manage Task Lifecycle
  53. News Version 1 Multiple Lists - Single Model Each post

    type stored in single collection. Each read, then merge on client. Problems: 1. Query twice per view 2. Merge not scalable 3. Slow updates on merge 4. Can’t filter on state /posts/todo/{todoId}/ { Text: “This is a post” Author: userId Comments: {} Pics: [] CreatedAt: timestamp } /posts/news/{newsId}/ { Text: “This is a post” Author: userId Comments: {} Pics: [] CreatedAt: timestamp } this.todo = [] db.get(‘posts/todo’) .then(posts => { this.todo = posts; }) this.posts = [] db.get(‘posts/todo’) .then(posts => { this.todo = posts; }) this.posts.join(this.todo)
  54. News Version 2 Single Lists - Single Model Each post

    type stored in doc. Each read, then filtered on client. Problems: 1. Query not able to support multiple fields, site, user, state… at once. 2. Slow updates on filter /posts/{postId}/ { Text: “This is a post” Author: userId Comments: {} Pics: [] CreatedAt: timestamp Type: post || todo State: “new” } this.posts = [] db.get(‘posts/’) .then(posts => { this.posts = posts; }) posts = this.posts.filter( item.type == post) tasks = this.posts.filter( item.type == todo)
  55. News Version 3 Single List - Multiple Models Each post

    type stored in doc. Multiple reads on query returns only docs that match. Tasks can be deferred until needed by user. /posts/{todoId}/ { Text: “This is a post” Author: userId Comments: {} Pics: [] CreatedAt: timestamp Type: post || todo State: “new” } this.posts = [] db.get(‘posts/’) .where(type == post) .then(posts => { this.todo = posts; }) this.todo = [] db.get(‘posts/’) .where(type == todo) .then(posts => { this.todo = posts; })
  56. Doc List Items Create small docs for display in UX

    lists. Implementation: Create a seperate collection of small dos with minimum display fields and id to main doc. Common Use: List of products, inventory, lists with 1000+ items. Variation: for lists below 20K items use a single doc with Objects.
  57. Data Duplication Reduce number of reads by duplicating data for

    1:1 relations. Implemenation: Create copies of fields and data into related doc from doc that would be queried by id. Common Use: User profile data (name, pic, id) attached to UX components, posts, tweets etc. Should be done for data that has low change rate, as changes are expensive.
  58. Data Aggregation Perform aggregate function on a collection of documents:

    Count() Implementation: Run a Cloud Function on collection and write results to doc. Common Use: Count the number of unread docs in a collection. Variation: Parent doc has col of child docs to compute aggregate values stored in parent.
  59. Re-Design Desktop Web App Data Migration from Linux Manage Financial

    Modules Multiple Levels of User Access High Transaction Rate Multi-tenant Rich Queries/Reports
  60. Array Search Maintain multiple states of a doc at the

    same time. Implementation: Strings are added to array field in a doc, used for search. Common uses: Tags for news posts. Variations: arrays can store ids vs strings, to be used on query in other col. /posts/{postId}/ { postId: “SjkSDLKJSD” Title: “My new Pic” Tags: [ ‘Funny’, ‘pic’, ‘selfie’ ] } db.collection(‘posts’) .where(‘Tags’, ‘array-contains’, ‘Funny’)
  61. Parent - Child A parent-child relation between docs Implementation: Subcollection,

    or Object fields. Common Use: Records for an account, Data scoped to parent. Multi-tenancy design. New: Can query across children col “All parents with child.doc.field = x” De-normalize using root collection.
  62. 1:n Doc Relation One doc relates to n many docs.

    Implementation: Doc contains id to a collection that has many other docs. Common use: User has col of messages that belong to them. Variations: n Col can be subcol of user doc. N:1 where many docs refer to a single doc, such as userId. { Field: ID } ID/messages /users/{userId}/ { userId: “SjkSDLKJSD” Name: “Bob Smith” } /messages/{userId}/ { Message: “Message Text” } 1 Doc N messages
  63. n:m Doc Relation Many n docs relate to m many

    docs. Implementation: 2 types of doc contains ids to many other docs of each others type. Common use: Two objects that contain a set of each other Speakers-Sessions. Variation: Single doc points to 2 other kinds of docs. Sessions /speakers/{speakerId}/ { Name: “Bob Smith” Sessions: { S1: sessionId S2: sessionId } } /sessions/{sessionId}/ { Session: “S1” Speakers: { Bob Smith: speakerId Peter Orr: speakerId } } Speakers
  64. Form Docs - single read Forms to display and edit

    data. Implementation: Read a doc into a local data structure, to make modifications locally, then write back to doc. Common Uses: User Settings, Invoices, Accounts, Profiles. Variations: Use Realtime DB to write fields directly.
  65. Doc Cache For data that requires heavy processing that is

    performed periodically. Implementation: Store the results of a complex computation/data structure as a single doc to be read often. Common Use: Chart of Accounts tree computed from accounts. Variations: Statistics, analytics, graphs.
  66. Data Aggregation Firestore does not support native aggregation queries… so

    we us Cloud Functions as substitute. Situations where you have many reads, but few writes. Parent doc has col of child docs to compute aggregate values stored in parent. Updates to child trigger aggregation, without having to read all child col. accounts/ {XYZ} avgRate:24.5 numRate:2 ratings/ {jhadhkasd} rate:34 {jhadhkasd} rate:34