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:
-
Define a template string containing the HTML markup for the web component. This template string is
passed to the
html
helper function, which returns anHTMLTemplateElement
. -
Define a styles string containing the CSS styles for the web component. This string is passed to the
css
helper function, which returns aCSSStyleSheet
. -
Define an optional
props
object. This object specifies the API interface for the web component via attributes and properties. - Create an
AG
function by invokingAGFactory
with thetemplate
,styles
, andprops.
- Define the web component class that extends an
HTMLElement
type wrapped in the AG function. -
Within the component class, define
render
functions, which are invoked whenever attributes or properties change. -
Access the properties and attribute values inside the
render
functions to perform actions based on their values, such as updating the DOM. -
At the end of the file, use the
customElements.define
function to register the web component class with its tag name.
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 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.