We hebben allemaal wel een paar projecten waar we liever niet aan werken. De code is onbeheersbaar geworden, de scope te groot, er zijn noodoplossingen gemaakt voor noodoplossingen, en de structuur is volledig verdwenen in een spaghetti van code. Programmeren kan soms ook erg rommelig zijn.

Projecten kunnen dan ook profiteren van eenvoudige, onafhankelijke modules die allemaal maar één verantwoordelijkheid hebben. Deze modulaire code staat in feite los van elkaar, dus zelfvoorzienend, en de implementatie is daardoor veel makkelijker. Zolang je maar weet wat de module als output geeft op basis van een aantal inputs, hoef je niet per se precies te weten hoe dat resultaat geproduceerd wordt.

Het toepassen van modulaire concepten op een enkele programmeertaal is vrij helder, maar webdevelopment vereist meestal een brede mix van technologieën. Browsers verwerken HTML, CSS en JavaScript om de content, stijlen en functionaliteit van een pagina te kunnen tonen.

Dit betekent dat je niet zomaar alles kan mixen, omdat:

  • Bij elkaar horende code kan zomaar over drie of meer bestanden verdeeld zijn, en
  • Globale stijlen en JavaScript objecten kunnen op onverwachte manieren met elkaar reageren.

Deze problemen komen nog bovenop de uitdagingen door verschillende runtimes van talen, frameworks, databases en andere afhankelijkheden op de server.

Check onze videohandleiding over webcomponenten

Wat zijn Web Components?

Een Web Component is een manier om een encapsulated, op zichzelf staand stuk code te maken met één enkele verantwoordelijkheid, die je kan hergebruiken op elke willekeurige pagina.

Denk eens aan de HTML <video> tag. Als je die implementeert op een willekeurige URL, dan kan een bezoeker meteen allerlei dingen doen, zoals de video afspelen, pauzeren, het volume aanpassen en vooruit spoelen.

Styling en functionaliteit zitten al ingebakken, alhoewel je ook aanpassingen kan maken via verschillende attributen en JavaScript API calls. Een willekeurig aantal <video> elementen kan in de tags geplaatst worden, zonder conflicten.

Wat als je een eigen custom functie nodig hebt? Stel een element met daarin het aantal woorden op een bepaalde pagina? Er bestaat (nog) geen HTML <wordcount> tag.

Frameworks zoals React of Vue.js bieden developers de mogelijkheid om onderdelen te maken waarbij de content, stijl, en functionaliteit binnen één JavaScript bestand aangemaakt kunnen worden. Dit kan een aantal ingewikkelde problemen bij het programmeren oplossen, maar bedenk daarbij wel dat:

  • Je eerst dat framework moet leren gebruiken, en je code zal moeten updaten wanneer het framework verandert.
  • Een onderdeel voor het ene framework zelden perfect compatibel is met een ander framework.
  • Frameworks komen en gaan. Je bent volledig afhankelijk van de prioriteiten en plannen van het developmentteam en de gebruikers.
  • Standaard Web Components kunnen functionaliteit aan de browser toevoegen, wat lastig te bereiken is in alleen JavaScript (zoals de Shadow DOM).

Gelukkig worden populaire concepten binnen libraries en frameworks meestal snel overgenomen door webstandaarden. Het heeft even geduurd, maar Web Components zijn er inmiddels inderdaad.

Het ontstaan van Web Components

Follo

Na diverse vendor-specifieke mislukking, is het concept van standaard Web Components voor het eerst geïntroduceerd door Alex Russell op de Fronteers Conference in 2011. De Polymer library van Google (een polyfill op basis van huidige voorstellen) kwam twee jaar later, maar de echte eerste implementaties verschenen pas in 2016 in Chrome en Safari.

Browserproviders namen hun tijd om de details uit te werken, maar Web Components werden uiteindelijk toegevoegd aan Firefox in 2018 en aan Edge in 2020 (toen Microsoft overstapte naar Chromium).

Het is begrijpelijk dat weinig webdevelopers stonden te springen om met Web Components aan de slag te gaan, maar inmiddels is er redelijk wat ondersteuning van browsers, met stabiele API’s. Niet alles is al helemaal perfect, maar het is een steeds beter alternatief voor framework-specifieke componenten.

Zelfs als je jouw favoriete framework nog niet los wil laten, zijn Web Components compatibel met elk framework, en zullen alle API’s de komende jaren ook ondersteuning gaan krijgen.

Verzamelingen met kant-en-klare Web Components zijn beschikbaar voor iedereen:

…maar het schrijven van je eigen code is natuurlijk altijd leuker.

Deze tutorial biedt een complete introductie over Web Components, geschreven zonder een JavaScript framework. Je leert precies wat het zijn en hoe je ze kan gebruiken voor je eigen webprojecten. Je hebt wel wat basiskennis van HTML5, CSS en JavaScript nodig om alles goed te kunnen volgen.

Beginnen met Web Components

Web Components zijn custom HTML elementen, zoals <hello-world></hello-world>. De naam moet een koppelteken (dash) bevatten zodat het nooit problemen oplevert met elementen die officieel ondersteund worden in de HTML specificatie.

Je moet een ES2015 class definiëren om het element te controleren. Dit kan elke willekeurige naam zijn, maar HelloWorld wordt veel gebruikt. Het moet een uitbreiding zijn van de HTMLElement interface, die de standaard eigenschappen en methoden van elk HTML element weergeeft.

Opmerking: Firefox biedt de mogelijkheid tot uitbreiding van specifieke HTML elementen, zoals HTMLParagraphElement, HTMLImageElement, en HTMLButtonElement. Dit wordt niet ondersteund in andere browsers, en je kan zo geen Shadow DOM maken.

Om er iets nuttigs mee te kunnen doen, heeft de class een methode nodig met de naam connectedCallback() die aangeroepen wordt wanneer het element wordt toegevoegd aan een document:

class HelloWorld extends HTMLElement {

  // connect component
  connectedCallback() {
    this.textContent = 'Hello World!';
  }

}

In dit voorbeeld is de tekst van het element op “Hello World” ingesteld.

De class moet geregistreerd zijn in de CustomElementRegistry om het te definiëren als een handler voor een specifiek element:

customElements.define( 'hello-world', HelloWorld );

De browsers associeert nu het <hello-world> element met je HelloWorld class wanneer je JavaScript geladen wordt (bijv.  <script type="module" src="./helloworld.js"></script>).

Je hebt nu een eigen element gemaakt!

CodePen demonstratie

Dit onderdeel kan in CSS gestyled worden, net als andere elementen:

hello-world {
  font-weight: bold;
  color: red;
}

Toevoegen van attributen

Dit onderdeel biedt geen toegevoegde waarde aangezien dezelfde tekst al wordt getoond. Net als bij andere elementen, kunnen we HTML attributen toevoegen:

<hello-world name="Craig"></hello-world>

Dit kan de tekst overschrijven, zodat “Hello Craig!” wordt weergegeven. Om dit te doen kan je een constructor() functie toevoegen aan de HelloWorld class, die wordt uitgevoerd wanneer een object wordt aangemaakt. Daarbij moeten:

  1. de super() method gecalld worden om de parent HTMLElement te initialiseren en
  2. andere initialisaties gedaan worden. In dit geval zullen we een name eigenschap definiëren die ingesteld staat op de standaardwaarde “World”:
class HelloWorld extends HTMLElement {

  constructor() {
    super();
    this.name = 'World';
  }

  // more code...

Voor je component draait het alleen om de name attribuut. Een statische observedAttributes() eigenschap kan een array van eigenschappen teruggeven als antwoord:

// component attributes
static get observedAttributes() {
  return ['name'];
}

Een attributeChangedCallback() methode wordt gebruikt wanneer een attribuut wordt gedefinieerd in de HTML, of wordt gewijzigd via JavaScript. Daarbij wordt de naam van de eigenschap, de oude en de nieuwe waarde meegegeven:

// attribute change
attributeChangedCallback(property, oldValue, newValue) {

  if (oldValue === newValue) return;
  this[ property ] = newValue;

}

In dit voorbeeld zou alleen de name eigenschap ooit worden bijgewerkt, maar je zou ook meer eigenschappen kunnen toevoegen.

Als laatste moet je het bericht in de connectedCallback() methode aanpassen:

// connect component
connectedCallback() {

  this.textContent = `Hello ${ this.name }!`;

}

CodePen demonstratie

Lifecycle methodes

De browser callt automatisch zes verschillende methodes tijdens de Web Component state. De hele lijst staat hieronder, al heb je inmiddels de eerste vier al gezien in bovenstaande voorbeelden:

constructor()

Dit wordt gecalld wanneer de component voor het eerst geïnitialiseerd wordt. Dit moet de super() aanroepen en kan eventuele standaardwaarden instellen of andere pre-rendering processen uitvoeren.

static observedAttributes()

Stuurt een array van attributen terug die de browser kan zien.

attributeChangedCallback(propertyName, oldValue, newValue)

Wordt gecalld wanneer een geregistreerde attribuut wordt gewijzigd. De attributen die in HTML zijn gedefinieerd worden meteen doorgegeven, maar JavaScript kan ze nog wel aanpassen:

document.querySelector('hello-world').setAttribute('name', 'Everyone');

De methode moet soms een re-render uitvoeren wanneer dit gebeurt.

connectedCallback()

Deze functie wordt gecalld wanneer de Web Component aan een Document Object Model (DOM) wordt toegevoegd. Eventuele rendering zou daarna uitgevoerd moeten worden.

disconnectedCallback()

Dit wordt gecalled wanneer de Web Component wordt verwijderd van een DOM. Dit kan handig zijn als je de boel wat wil opruimen, wanneer je bijvoorbeeld een opgeslagen state wil verwijderen of een Ajax request wil afbreken.

adoptedCallback()

Deze functie wordt gecalld wanneer een Web Component van het ene document naar het andere wordt verplaatst. Wellicht nuttig, al heb ik zelf nog geen toepassing gevonden.

Hoe Web Components samenwerken met andere onderdelen

Web Components bieden een aantal unieke functies die je niet in andere JavaScript frameworks kan vinden.

De Shadow DOM

Alhoewel bovenstaande Web Component al werkt, kan er van buitenaf mee geknoeid worden, en aangepast worden via CSS of JavaScript. Zo zouden ook de styles die je voor je component hebt aangemaakt naar buiten kunnen lekken.

De Shadow DOM lost dit encapsulation probleem op door een apart DOM toe te voegen aan de Web Component, namelijk zo:

const shadow = this.attachShadow({ mode: 'closed' });

De mode kan ingesteld worden op:

  1. “open”— JavaScript kan in de buitenste pagina verbinding maken met Shadow DOM (via Element.shadowRoot), of
  2. “closed”— de Shadow DOM kan alleen geopend worden vanuit de Web Component.

De Shadow DOM kan alleen gemanipuleerd worden zoals andere DOM elementen:

connectedCallback() {

  const shadow = this.attachShadow({ mode: 'closed' });

  shadow.innerHTML = `
    <style>
      p {
        text-align: center;
        font-weight: normal;
        padding: 1em;
        margin: 0 0 2em 0;
        background-color: #eee;
        border: 1px solid #666;
      }
    </style>

    <p>Hello ${ this.name }!</p>`;

}

De component toont nu de tekst “Hello” binnen een <p> element en stijlt het ook zo. Het kan niet aangepast worden door JavaScript of CSS buiten de component, alhoewel sommige styles, zoals font en de kleur, worden geërfd vanaf de pagina aangezien ze niet expliciet gedefinieerd worden.

CodePen demonstratie

De styles die door dit Web Component worden bepaald hebben geen effect op andere paragrafen op de pagina, of zelfs op andere <hello-world> componenten.

Let op dat de CSS :host selector het buitenste <hello-world> element kan stylen vanuit de Web Component:

:host {
  transform: rotate(180deg);
}

Je kan de styles ook instellen om toegepast te worden wanneer het element een bepaalde class gebruikt, bijv. <hello-world class="rotate90">:

:host(.rotate90) {
  transform: rotate(90deg);
}

HTML Templates

Het definiëren van HTML kan al snel onpraktisch worden voor complexere Web Components. Een template maakt het mogelijk om een stuk HTML code in je pagina te definiëren die je Web Component dan kan gebruiken. Dit heeft diverse voordelen:

  1. Je kan HTML code eenvoudig aanpassen zonder dat je strings opnieuw moet schrijven in je JavaScript.
  2. Componenten kunnen aangepast worden zonder dat je voor elk type aparte JavaScript classes moet aanmaken.
  3. Het is eenvoudiger om HTML te definiëren binnen HTML, en het kan aangepast worden op de server of in de client vlak voor de component getoond wordt.

Templates wordt gedefinieerd in een <template> tag, en het is praktisch om die een ID mee te geven zodat je deze kan refereren vanuit de component class. Dit voorbeeld toont drie paragrafen die allemaal “Hello” tonen:

<template id="hello-world">

  <style>
    p {
      text-align: center;
      font-weight: normal;
      padding: 0.5em;
      margin: 1px 0;
      background-color: #eee;
      border: 1px solid #666;
    }
  </style>

  <p class="hw-text"></p>
  <p class="hw-text"></p>
  <p class="hw-text"></p>

</template>

De Web Component class kan deze template openen, de content lezen en de elementen klonen om ervoor te zorgen dat je een uniek DOM fragment maakt overal waar dit wordt gebruikt:

const template = document.getElementById('hello-world').content.cloneNode(true);

De DOM kan aangepast worden en direct toegevoegd worden aan de Shadow DOM:

connectedCallback() {

  const

    shadow = this.attachShadow({ mode: 'closed' }),
    template = document.getElementById('hello-world').content.cloneNode(true),
    hwMsg = `Hello ${ this.name }`;

  Array.from( template.querySelectorAll('.hw-text') )
    .forEach( n => n.textContent = hwMsg );

  shadow.append( template );

}

CodePen demonstratie

Slots in templates

Slots zijn een soort fragmenten waarmee je een template kan aanpassen. Stel dat je het vorige <hello-world> Web Component wil gebruiken, maar het bericht wil plaatsen binnen een <h1> heading in de Shadow DOM. Dan kan je deze code schrijven:

<hello-world name="Craig">

  <h1 slot="msgtext">Hello Default!</h1>

</hello-world>

(Let op het slot attribuut.)

Je zou ook andere elementen kunnen toevoegen, bijvoorbeeld een andere paragraaf:

<hello-world name="Craig">

  <h1 slot="msgtext">Hello Default!</h1>
  <p>This text will become part of the component.</p>

</hello-world>

Slots kunnen nu vanuit je template gebruikt worden:

<template id="hello-world">

  <slot name="msgtext" class="hw-text"></slot>

  <slot></slot>

</template>

Een element slot attribuut dat ingesteld is op “msgtext” (de <h1>) wordt ingevoegd wanneer er een <slot> is met de naam “msgtext.” De <p> tag heeft geen naam van een slot toegewezen gekregen, maar wordt gebruikt in de volgende beschikbare <slot> zonder naam. Hierdoor wordt de template in feite zo:

<template id="hello-world">

  <slot name="msgtext" class="hw-text">
    <h1 slot="msgtext">Hello Default!</h1>
  </slot>

  <slot>
    <p>This text will become part of the component.</p>
  </slot>

</template>

In de werkelijkheid is het natuurlijk niet helemaal zo simpel. Een <slot> element in de Shadow DOM point naar de ingevoegde elementen. Je kan ze alleen openen door een <slot> te vinden en dan de .assignedNodes() methode te gebruiken om een array met de binnenste childs terug te sturen. De bijgewerkte connectedCallback() methode:

connectedCallback() {

  const
    shadow = this.attachShadow({ mode: 'closed' }),
    hwMsg = `Hello ${ this.name }`;

  // append shadow DOM
  shadow.append(
    document.getElementById('hello-world').content.cloneNode(true)
  );

  // find all slots with a hw-text class
  Array.from( shadow.querySelectorAll('slot.hw-text') )

    // update first assignedNode in slot
    .forEach( n => n.assignedNodes()[0].textContent = hwMsg );

}

CodePen demonstratie

Daarnaast kan je niet direct de ingevoegde elementen stylen, alhoewel je wel specifieke slots kan bewerken binnen je Web Component:

<template id="hello-world">

  <style>
    slot[name="msgtext"] { color: green; }
  </style>

  <slot name="msgtext" class="hw-text"></slot>
  <slot></slot>

</template>

Template slots zijn een beetje uitzonderlijk, maar een groot voordeel is dat je content ook getoond wordt wanneer JavaScript niet uitgevoerd kan worden. Onderstaande code toont een standaard heading en paragraph die alleen worden vervangen wanneer de Web Component class goed uitgevoerd kan worden:

<hello-world name="Craig">

  <h1 slot="msgtext">Hello Default!</h1>
  <p>This text will become part of the component.</p>

</hello-world>

Daarom kan je een soort progressieve berichten invoegen, zelfs gewoon een bericht met “You need JavaScript”.

De declaratieve Shadow DOM

De bovenstaande voorbeelden construeren een Shadow DOM op basis van JavaScript. Dat is momenteel de enige mogelijkheid, maar er wordt een experimentele Shadow DOM voor Chrome is in ontwikkeling. Dit maakt Server-Side Rendering mogelijk en voorkomt problemen met het verschuiven van de layout of ongestylde content.

De volgende code kan worden gedetecteerd door de HTML parser, die een identieke Shadow DOM maakt als die je in de vorige sectie aangemaakt hebt (je kan eventueel het bericht aanpassen):

<hello-world name="Craig">

  <template shadowroot="closed">
    <slot name="msgtext" class="hw-text"></slot>
    <slot></slot>
  </template>

  <h1 slot="msgtext">Hello Default!</h1>
  <p>This text will become part of the component.</p>

</hello-world>

Deze feature is nog niet in elke browser beschikbaar, en we weten ook nog niet zeker of het in Firefox en Safari geïmplementeerd zal gaan worden. Je kan meer lezen over de declaratieve Shadow DOM en een polyfill is vrij eenvoudig, maar het kan dus wel zijn dat de implementatie nog verandert.

Shadow DOM Events

Je Web Component kan events toevoegen aan elk element in de Shadow DOM, net zoals bij de pagina DOM, bijvoorbeeld het luisteren naar click events op onderliggende elementen:

shadow.addEventListener('click', e => {

  // do something

});

Tenzij je stopPropagation gebruikt, zal het event opborrelen in de pagina DOM, maar het event zal wel een nieuw target krijgen. Daardoor lijkt het van je custom element te komen, niet van de onderliggende elementen.

Web Components gebruiken in andere frameworks

Elk Web Component dat je aanmaakt zal werken in alle JavaScript frameworks. Die kennen allemaal niet het idee van HTML elementen, en je <hello-world> component zal daarom identiek worden behandeld als een normaal <div> element en in de DOM geplaatst worden, waar de class geactiveerd zal worden.

custom-elements-everywhere.com biedt een lijst met frameworks en opmerkingen over het gebruik van Web Components daarin. De meeste frameworks zijn volledig compatibel, al heeft React.js wel wat uitdagingen. Het is bijvoorbeeld mogelijk om <hello-world> te gebruiken in JSX:

import React from 'react';
import ReactDOM from 'react-dom';
import from './hello-world.js';

function MyPage() {

  return (
    <>
      <hello-world name="Craig"></hello-world> 
    </>
  );

}

ReactDOM.render(<MyPage />, document.getElementById('root'));

…maar:

  • React kan alleen primaire datatypen doorsturen naar HTML attributen, dus geen arrays of objecten.
  • React kan ook niet luisteren naar Web Component events, dus je moet handmatig je eigen handlers toevoegen.

Kritiek over en problemen met Web Components

Web Components zijn aanzienlijk verbeterd de laatste tijd, maar er zijn nog enkele aspecten die lastig blijven.

Moeite met styling

Het stylen van Web Components biedt een aantal uitdagingen, zeker als je styles binnen een scope wil overschrijven. Er zijn gelukkig allerlei oplossingen:

  1. Gebruik niet de Shadow DOM. Je kan de content direct toevoegen aan je custom element, alhoewel een ander JavaScript script dit dan nog wel per ongeluk of opzettelijk kan wijzigen.
  2. Gebruik de :host classes. Zoals we eerder zagen kan scoped CSS specifieke stylen toepassen wanneer een class wordt toegepast op een custom element.
  3. Controleer de CSS custom eigenschappen (variabelen). Custom eigenschappen kunnen naar Web Components doorgezet worden, dus als je element var(--my-color) gebruikt, kan je --my-color in een buitenste container (zoals :root) zetten, en dan zal het gebruikt worden.
  4. Doe je voordeel met ‘shadow’ onderdelen. De nieuwe ::part() selector kan een binnenste component met een ‘part’ attribuut stylen, dus <h1 part="heading"> binnen een <hello-world> component kan gestyled worden met de selector hello-world::part(heading).
  5. Voer een string met styles door. Je kan deze doorgeven als een attribuut die binnen een <style> block toegepast kan worden.

Dergelijke oplossingen zijn niet ideaal, en je moet goed nadenken over hoe andere gebruikers je Web Component kunnen aanpassen.

Genegeerde inputs

De velden <input><textarea>, of <select> in je Shadow DOM worden niet automatisch geassocieerd met het omvattende formulier. Vroege gebruikers van Web Component voegen verborgen velden toe aan de pagina DOM of gebruikten de FormData interface om waarden bij te werken. Beide oplossingen zijn niet erg praktisch, en verbreken de encapsulation van Web Component.

De nieuwe ElementInternals interface biedt een Web Component de mogelijkheid om een hook in het formulier te zetten zodat custom waarden en de validiteit gedefinieerd kunnen worden. Dit is al geïmplementeerd in Chrome, maar een polyfill is ook beschikbaar voor andere browsers.

Ter demonstratie maken we een eenvoudig <input-age name="your-age"></input-age> component. De class moet een statische formAssociated waarde op TRUE hebben staan en optioneel een formAssociatedCallback() methode die gecalld kan worden wanneer het buitenste formulier wordt gebruikt:

// <input-age> web component
class InputAge extends HTMLElement {

  static formAssociated = true;

  formAssociatedCallback(form) {
    console.log('form associated:', form.id);
  }

De constructor moet nu de attachInternals() methode uitvoeren, waarmee het component kan communiceren met het formulier en andere JavaScript code die de waarde of de validatie wil weten:

  constructor() {

    super();
    this.internals = this.attachInternals();
    this.setValue('');

  }

  // set form value

  setValue(v) {

    this.value = v;

    this.internals.setFormValue(v);

  }

De ElementInternal’s setFormValue() methode stelt de waarde van het element in voor het parent formulier met een lege string (het kan ook een FormData object doorgeven met diverse naam/waarde paren). Andere eigenschappen en methodes zijn onder meer:

  • form: het parent formulier
  • labels: een array van elementen met labels voor het component
  • Constraint Validation API met opties zoals willValidate, checkValidity, en validationMessage

De connectedCallback() methode maakt een Shadow DOM, net als eerder, maar moet ook het veld monitoren op wijzigingen, zodat setFormValue()uitgevoerd kan worden:

  connectedCallback() {

    const shadow = this.attachShadow({ mode: 'closed' });

    shadow.innerHTML = `
      <style>input { width: 4em; }</style>
      <input type="number" placeholder="age" min="18" max="120" />`;

    // monitor input values
    shadow.querySelector('input').addEventListener('input', e => {
      this.setValue(e.target.value);
    });

  }

Je kan nu een HTML formulier maken met dit Web Component die op dezelfde manier werkt met andere velden:

<form id="myform">

  <input type="text" name="your-name" placeholder="name" />

  <input-age name="your-age"></input-age>

  <button>submit</button>

</form>

Het werkt wel, maar het is wel een beetje ingewikkeld.

Bekijk het maar eens in de CodePen demonstratie.

Voor meer informatie kan je ook dit artikel lezen over controls voor formulieren.

Samenvatting

Er is moeite geweest met het vinden van consensus over en gebruik van Web Component, terwijl JavaScript frameworks flink zijn gegroeid in capaciteit. Als je van een framework zoals React, Vue, of Angular komt, kunnen Web Components nogal complex en onhandig lijken, zeker wanneer je features zoals data-binding en state beheer moet missen.

Er zijn dan ook nog zeker veel verbeteringen mogelijk, maar de toekomst voor Web Components ziet er erg rooskleurig uit. Ze zijn framework onafhankelijk, lichtgewicht, snel en kunnen functionaliteit bieden die je niet in alle JavaScript zou kunnen gebruiken.

Tien jaar geleden zouden weinig mensen een website aangaan zonder jQuery, maar browserproviders hebben de goede delen genomen en alternatieve ingebouwd (zoals querySelector). Hetzelfde zal gebeuren bij JavaScript frameworks, en Web Components zijn daarin een eerste stap.

Heb je nog vragen over het gebruik van Web Components? Laten we erover praten in de reacties hieronder!

Craig Buckler

Freelance UK web developer, writer, and speaker. Has been around a long time and rants about standards and performance.