Building at Augmend: Web Components for a Scrappy Startup


In this blog post, we'll explore how Augmend utilizes web components to create a user-friendly interface and iterate without the bulk of a third-party framework.

Vanilla JavaScript as a Framework

Augmend is tailored for the modern web, and as a web application, JavaScript powers the User Interface (UI). As the application grows, the complexity of the UI increases, necessitating a framework to manage it. The chosen framework should be lightweight, easy to use, and capable of handling the UI's complexity.

Early on, we decided to avoid TypeScript and other technologies that would introduce an extra step between authoring code and running the app in the browser. This led us to opt for vanilla JavaScript without a bundler or transpiler, keeping the development process straightforward.

This decision eventually led to the creation of a lightweight framework built on vanilla web components.

Introduction to the Augmend Icon Component

Let's dive into the Augmend Component Framework (AG) by examining the ag-icon web component in Augmend. This component is designed to display icons on the screen, and it can work with various resource types, including JPEG, PNG, and SVG.

The Augmend Icon Web component is defined in a single file with different sections for the template and styles. The template section contains the HTML markup for the web component, while the styles section contains the CSS styles controlling the component's appearance. Lastly, component behavior is defined in the AGIcon class.

To create an Augmend Component, follow these steps:

  1. Define a template string containing the HTML markup for the web component. This template string is passed to the html helper function, which returns an HTMLTemplateElement.
  2. Define a styles string containing the CSS styles for the web component. This string is passed to the css helper function, which returns a CSSStyleSheet.
  3. Define an optional props object. This object specifies the API interface for the web component via attributes and properties.
  4. Create an AG function by invoking AGFactory with the template, styles, and props.
  5. Define the web component class that extends an HTMLElement type wrapped in the AG function.
  6. Within the component class, define render functions, which are invoked whenever attributes or properties change.
  7. Access the properties and attribute values inside the render functions to perform actions based on their values, such as updating the DOM.
  8. At the end of the file, use the customElements.define function to register the web component class with its tag name.
Helper Functions
Helper Functions

DOM Access

Accessing HTML elements inside a web component's DOM tree is typically done during the connectedCallback or attributeChangedCallback lifecycle methods. This is achieved using the getElementById and is so common that Augmend provides a shorthand for it called el, invoked when defining the props object.

The el function takes in an HTML element type and then adds it to the this instance of the web component under the key used in props. For example { img: el(HTMLImageElement) } will add an this.img property with the type HTMLImageElement. Under the hood, this is equivalent to calling this.getElementById('img') and casting the result to the type HTMLImageElement.

Data Binding

Data binding is a typical pattern in front-end web development, allowing you to bind a property to an attribute on an HTML element. When the property changes, the attribute is updated, and vice versa, enabling easy UI updates based on data changes.

In Augmend Web Components, data binding is achieved by defining a render function, invoked whenever attributes or properties change. Inside the render function, you can access properties, attributes, and DOM APIs to perform actions based on their values.

To implement a render function, add a method that starts with the word render followed by the PascalCase property name in props, for example, renderIcon. This method will be called whenever the icon property changes.

The AG Framework

The AG framework is a lightweight framework built on vanilla web components. It consists of only 300 lines of code and is designed to be easy to use, extend, and adapt to our needs.

The framework provides three advantages over using vanilla web components directly:

  • Removes boilerplate code for attaching a ShadowRoot with HTML elements defined in a template.
  • Includes API for data-binding props and intelligently rendering the view.
  • Provides type safety during property access.

Optimized Render Function

As mentioned, the renderIcon function is called whenever the icon property or attribute changes. It's optimized to update the DOM only if the property value has changed. This optimization is achieved by comparing the property's previous value to the new value. If they are the same, the render function is not invoked.

For the developer, idempotence is crucial in implementing the render function. Implement it declaratively, assigning a state to the DOM based on the application's state. This approach ensures that calling the render function multiple times consistently sets the DOM to the same state, similar to how React components are expected to be implemented.

Type Definitions
Type Definitions

Type Safety

Augmend leverages TypeScript to bring strong typing to web development. Since we do not use the TypeScript language, this is achieved through JSDOC comments to define types, offering the speed of JavaScript development with optional type-checking support.

The props object is defined using JSDOC comments or type inference of the default value to declare the types of the keys in props. This allows the TypeScript compiler to provide type safety when accessing the properties after they are added to the component instance.

Wrapping Things Up

Augmend's framework is impressively lightweight, consisting of approximately 300 lines of code built on top of vanilla web components. It provides a simple API for creating web components and rendering them based on their properties and attribute changes. Additionally, it offers type safety for property access and intelligently renders the view only when the property value changes.

The Next Phase

Augmend's framework is designed to be easy to use, extend, and adapt to changing needs. So far, it has met those needs well. The framework also supports interoperability with more advanced frameworks like React. The Augmend product will eventually mature to require a more advanced framework, and we will be in an excellent position to make that transition.