Cross Team

Frontend

Architecture

<Majid-Hajian />

<Jakob-lind />

Rapidshop B2B

First: Cross team frontend architecture today in Telia

Telia Styleguide

Telia.no

Webshop

App

Webshop B2B

Many apps and teams

One look-and-feel

Min Side

Min Bedrift

1. Styling: CSS, icons and fonts

The styleguide is two things

2. A react library

The styleguide has increased collaboration, and decreased time for developing UI

But there are some limitations

It's an empty skeleton

CSS

React presentational components

React container components

Javascript logic / unit tests

Hosting and opsing

CI/CD pipeline

A complete app

Styleguide

Every channel 

have to code

CSS

React presentational components

React container components

Javascript logic / unit tests

Hosting and opsing

CI/CD pipeline

Still lots of duplicated work

More examples of duplication

When every channel is responsible for hosting, it's easy to get out of sync

Web components 

Why we started to look into this

Selling to existing customers from min bedrift

webshop

Requirements:

  • End user should think he is in min bedrift
  • Not tightly coupled to min bedrift. Should be easy to move if min bedrift would be shut down

Min bedrift

Our options

1. Separate web app. Redirect to webshop and then back again to min bedrift

2. Develop in min bedrift code base

4. Web components

  • Sharing authentication is tricky
  • Not seamless user experience

3. "React library"

  • Large code base, slow to work with
  • With Get/TDC migration, we dont know how long min bedrift will live
  • Requires that min bedrift uses React
  • Versions inconsistencies

Winner!

Web components

With web components we can code everything in a component and ship it 

  • Styling (css, fonts, icon)
  • Interactivity with JS
  • Data fetching
  • Hosting

The components are new native DOM elements

<telia-b2b-shop name="Minbedrift netbuttik"></telia-b2b-shop>

Web components is a set of w3c standards

Supported by all major browsers

Using it is as easy as using a script tag

<body>
<telia-b2b-shop name="Minbedrift netbuttik"></telia-b2b-shop>
<script src="/telia-b2b-shop.js" type="module"></script>
</body>

It can be bundled in an NPM library too

You can think of web components as a modern version of iframes that works

With web components every component is a complete self hosted mini app

CSS

React presentational components

React container components

Javascript logic / unit tests

Hosting and opsing

CI/CD pipeline

CSS

React presentational components

React container components

Javascript logic / unit tests

Hosting and opsing

CI/CD pipeline

CSS

React presentational components

React container components

Javascript logic / unit tests

Hosting and opsing

CI/CD pipeline

Styleguide

https://micro-frontends.org/

Organisation in Verticals

Monolithic Frontends

let's dive into the details

HTML Template

Custom Element

Shadow DOM

ES Modules

Previously, HTML imports were also a part of it, but with ES6 modules, HTML imports became deprecated.

Web components consist of:

  <template id="template">
    <div id="container">
      <img class="webcomponents" src="logo.svg">
    </div>
  </template>

HTML Template Tag

var template = document.querySelector('#template');
var clone = template.content.cloneNode(true);
var host = document.querySelector('#host');
host.appendChild(clone);
  <template id="template">
    <div id="container">
      <img class="webcomponents" src="logo.svg">
    </div>
  </template>

HTML Template Tag

var template = document.querySelector('#template');
var clone = template.content.cloneNode(true);
var host = document.querySelector('#host');
host.appendChild(clone);

Will not render until it is activated

Has no effect on other parts of the page - scripts won’t run, images won’t load, audio won’t play - until activated

Will not appear in the DOM

Shadow DOM

Shadow DOM

const header = document.createElement('header');
const shadowRoot = header.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<p>Hello world!</p>'; 

Custom Elements

class CustomButton extends HTMLElement {...}
window.customElements.define('custom-button', CustomButton);
<custom-button></custom-button>

Acts like a div

Custom Elements

class CustomButton extends HTMLButtonElement {...}
window.customElements.define('custom-button', CustomButton, {extends: 'button'});
<custom-button></custom-button>

Acts like a real button

Custom Elements Lifecycle

connectedCallback()

disconnectedCallback()

attributeChangedCallback()

adoptedCallback()

class Custom-component extends HTMLElement {
  constructor() {
    super(); // always call super() first in the ctor.
    ...
  }
  connectedCallback() {
    this.addEventListener("mouseover", e => {
      this.setAttribute("style", "color:pink; font-style: bold;");
    });

    this.addEventListener("mouseout", e => {
      this.setAttribute("style", "color:black; font-style: bold;");
    });

    this.addEventListener("click", e => {
      this.target="_blank";
    });
  }
  disconnectedCallback() {
    ...
  }
  attributeChangedCallback(attrName, oldVal, newVal) {
    ...
  }

}

Custom Elements / ES6 Class

ES  MODULE

<script type="module" src="telia-custom-component.js"></script>
<telia-custom-component></telia-custom-component>

Communication

between Components on a page

 
<script>
    document.querySelector('custom-input').dispatchEvent(new CustomEvent('customevent', {
        detail: { prop: true }
    }));
    
    customElements.whenDefined('progress-bar').then(() => {
        document.querySelector('progress-bar').addEventListener(
            'customevent', function () {
                 // do something
             }
        });
    });
</script>

Text


class TeliaContactElement extends HTMLElement {
  constructor() {
    super();
    const template = document.querySelector('#contact-card-template');

    // Shadow DOM
    this.attachShadow({ "mode": "open" });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
  }

  static get observedAttributes() {
    return ["name", "email"];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    this[name] = newValue;
  }

  connectedCallback() {
    this._$name = this.shadowRoot.querySelector("#name");
    this._$email = this.shadowRoot.querySelector("#email");
    this._render(this);
  }

  _render({ name, email, avatar }) {
    this._$name.textContent = name || 'N/A';
    this._$email.textContent = email || 'N/A';
    const figcaption = document.createElement("figcaption");
    figcaption.textContent = name;
    figcaption["aria-label"] = "contact name";
  }
}

customElements.define("telia-contact", TeliaContactElement);

<template id="contact-card-template">
	<style>
		.card {
			height: auto;
			max-height: 299px;
			width: 200px;
			overflow: hidden;
			background-color: #FFFFFF;
			border-radius: 10px;
			box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
			text-align: center;
			-webkit-transition: .1s ease-in-out;
			transition: .1s ease-in-out;
			margin: 30px;
			padding: 10px;
		}
		.card:hover {
			box-shadow: 0 15px 20px rgba(0, 0, 0, 0.1);
		}
	</style>

	<div class="card">
		<h3 id="name"></h3>
		<span id="email"></a>
  </div>
</template>
<h1>⚡️ Telia Contact ⚡️</h1>
<telia-contact 
  name="Majid" 
  email="majid@telia.no">
  loading...
</telia-contact>
<telia-contact 
  email="Jakob@Lind.com">
  loading...
</telia-contact>

Why use LitElement?

 

 

Delightfully declarative

Fast and light

Seamlessly interoperable

Less footprint

Supports Typescripts

Backed by Polymer team / Google

Lit-Element

import { LitElement, html, property, customElement } from 'lit-element';

@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
  @property() name = 'World';

  render() {
    return html`<p>Hello, ${this.name}!</p>`;
  }
}

Cross Team Frontend Architecture

By Majid Hajian

Cross Team Frontend Architecture

  • 1,187