Micro-library

You'll abstract common patterns intro reusable code in a micro-library powered by TypeScript Decorators.

Table of Contents

Introduction#

In Part One, we relied heavily on the set of specifications that make up Web Components: Custom Elements, HTML Templates, Shadow DOM, and CustomElementRegistry. The main benefits Web Components bring to the table: reuse, interoperable, accessible, having a long lifespan, are due to their reliance on browser specifications. Had we adopted a library or framework, we may have lost some or all of these characteristics in the user interfaces built with it. UI components coded with some libraries aren't interoperable with other JavaScript libraries or frameworks, which puts a hard limit on reuse.

Even though we gain these benefits from Web Components, we have lost any benefit JavaScript libraries have to offer. Frameworks and libraries like React, Vue, Angular and Svelte often provide an abstraction around browser specifications. Under the hood, frameworks are built with TypeScript or JavaScript, but behind the wheel engineers have an interface for component-level development. React, for example, famously opted for a purely functional approach, giving engineers "hooks" to manage side effects in user interfaces. JavaScript libraries and frameworks exist mostly to provide architectural patterns that make life easier on the part of the web developer, offering features not available with browser specifications alone, like data binding and state management. As browser standards evolve over time, the HTML living standard may gain some of the most popular features from JavaScript libraries. Web Components may gain more specifications that make styling components easier or enable data to be passed more efficiently and securely between components, for example.

What if we could retain the benefits of Web Components while also gaining the architectural design of a JavaScript framework? Indeed, many have tried. webcomponents.dev tracks over fifty implementations of custom elements coded with a JavaScript library. This service provides performance and scalability metrics on a semi-annual basis to help inform the decision making process when selecting a library. With so many options for Web Component development, it may seem daunting to pick a library or futile to develop one on your own. It must be really difficult, right?

In this chapter we'll demystify the inner workings of Web Component libraries. Using TypeScript decorators, we'll develop a new interface that simplifies development, but doesn't compromise performance. The micro-library we'll code optimizes to less than 1Kb of minified JavaScript (optimized with brotli). Components we've already developed will transform from something like this:

to instead use a TypeScript decorator named Component like this:

Decorators are denoted by the @ symbol, followed by the name of the decorator function, in this case Component. In the above example, the Component function is called with a single argument: an Object where the developer can declare the tag name, CSS style, and HTML template.

The side effect of using a class decorator to handle template and styling is readily apparent. Not only have we abstracted the creation of Shadow DOM, we've removed the line of code responsible for registering the class definition with the CustomElementRegistry. The constructor is much cleaner now. Component logic that should instantiate in the constructor is no longer aside template specific code. That's because all of that code has been abstracted to reusable function named attachShadow.

Decorators are found in other programming languages but currently not in the ECMAScript standard, although a proposal exists. You can't use a decorator and expect it to run in the browser, at least not yet. TypeScript has supported a decorator pattern for quite some time, anticipating the time when decorators land in ECMAScript. We can use decorators in TypeScript now by configuring the tsconfig.json with the experimentalDecorators property. You don't need to change the tsconfig.json because the repository is already setup to work with decorators.

Don't let that property spook you just because "experimental" is in the name. Several applications in production right now use TypeScript decorators. The entire Angular ecosystem has relied on TypeScript decorators since Angular 2. When TypeScript compiles down to JavaScript, a minimal amount of code is injected that polyfills the decorator pattern for ES2015 class.

In addition to making a class decorator that allows you declare tag name, styling and template with a cleaner interface than before, you'll also code a method decorator that simplifies binding event listeners to custom elements. Instead of typing this.addEventListener('click', this.onClick), what if you could decorate the onClick method and still provide the same functionality? It could look something like this: