Framework agnostic component libraries with StencilJs and Nx
Tags: stenciljs, nxdevtools, nx, monorepo
Published: 2022-02-07 21:31:00

## Why Stencil and WebComponents?
Web components are a collection of web standards that allow to create modular and reusable HTML elements. Common features of large frameworks like component lifecycles and dynamic are natively supported. Since they are standards-based only, you don't need a framework to run them; a modern browser is enough.

Web components primarily consist of the following 4 APIs from the web standards:

### Custom Elements
The [Custom Elemets](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) standard gives us the possibility to define our own HTML elements.

### Shadow DOM
The [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) allows us to encapsulate stylesheets and markup from the rest of the markup so that they no longer affect each other.

### HTML Templates
With [HTML Templates](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template) you can define reusable snippets. Within an HTML template, no scripts are executed, stylesheets are applied, or images are loaded. As long as it is not applied, it is not a regular part of the current document.

### ES Modules
The [ES Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) specification defines a standard way to import and reuse Javascript files in the most performant way.

## Stencil building blocks
[Stencil](https://stenciljs.com) is a web component compiler developed by the team around the [Ionicframework](https://ionicframework.com). Stencil is not the next framework but a compiler whose output format is standard-compliant web components. There are a few other compilers and frameworks that do this. There is the possibility to work without such a compiler, but this is quite bulky in handling and makes little sense, especially for a larger codebase.

```tsx
import { Component, Prop, h } from '@stencil/core';

@Component({
	tag: 'my-component',
	styleUrl: 'my-component.css',
	shadow: true,
})
export class MyComponent {

	@Prop() name: string;

	render() {
	    return <div>Hello, World! I'm ${this.name}</div>;
	}
}
```

Depending on personal experience with different frontend frameworks, at first glance, a Stencil component looks something like a mix of Angular and React. 
Stencil uses TSX (JSX in Typescript) for templating and Typescript as the leading language for development. Common stylesheet preprocessors like Sass are supported as well as Less and PostCSS.

## Hands-on with Nx

For a small example, we now create an empty Nx monorepo:
```bash
npx create-nx-workspace --preset=empty
```
And install the plugins that are important for us:
```bash
npm install --save-dev @nrwl/angular @nxext/stencil
```

With the Stencil plugin we now create a new library.
```bash
npx nx g @nxext/stencil:lib core --style='css' --buildable
```

## Framework Integration
Stencil has support to generate extra wrappers for frameworks like Angular, React, Vue and Svelte. Although most of these frameworks can handle web components, they still feel like foreign bodies in the application. I'm using Angular as an example in the examples here.

Using the Stencil Nx plugin, you can generate the outputconfig with the corresponding Angular library.

```bash
npx nx g @nxext/stencil:add-outputtarget core --outputType='angular'
```

This adds the following to stencil.config.ts:
```typescript
...
angularOutputTarget({
	componentCorePackage: '@agnostic-library/core',
	directivesProxyFile: '../../../libs/core-angular/src/generated/directives/proxies.ts',
	valueAccessorConfigs: angularValueAccessorBindings,
}),
...
```

After this, we created the following folder structure within the "libs" directory:
```
|
|-libs|-core
|       |-core-angular
|-...
```

With each build of the "core" library, wrapper components are now generated with Angular for each stencil component in the "core-angular" library folder. This is an Angular library without its own components, which only contains an Angular module for the export and the generated wrappers.

So that the new Angular library now also makes the components usable, these must be exported. For this, the new Angular module is extended and the new component is made known and usable in applications:

```typescript
...
import { MyComponent } from '../generated/directives/proxies';

const components = [
   MyComponent
]

@NgModule({
    imports: [CommonModule],
    declarations: components,
    exports: components
})
export class CoreAngularModule {}
```


An Angular application can now import and use the library, but the Web Components must be made known to the browser for everything to work. Before that, we face the problem that the components are not rendered in the browser. For this, we enter the following into the *main.ts* of the Angular application:
```typescript
import { defineCustomElements } from '@agnostic-library/core/loader';

defineCustomElements();
```

The generated Angular components use the original Web Components and make them feel like native Angular components.

This in itself does not sound very spectacular at first. I have observed over the years, especially in large companies, that the frameworks used for the frontend are changed from time to time, or several are used in parallel by different teams. If you now create component libraries via standards such as Web Components, which correspond to the corporate design, for example, these can be adapted quite quickly for all frameworks. This allows efforts to be bundled because a "build once, use everywhere" approach can be used. Not every component has to be rewritten in every framework, but they can be shared. As a small bonus and since Web Components only need a browser as an environment, they can also be used without a framework, e.g., on the company website or landing pages.

On Github is a small [example](https://github.com/conciso/framework-agnostic-libraries) and in the [Nxext Demo](https://github.com/nxext/nxext-demo) a somewhat larger repository with more extensive components, an Angular application with backend.