# Web Component Base [![Package information: NPM version](https://img.shields.io/npm/v/web-component-base)](https://www.npmjs.com/package/web-component-base) [![Package information: NPM license](https://img.shields.io/npm/l/web-component-base)](https://www.npmjs.com/package/web-component-base) [![Package information: NPM downloads](https://img.shields.io/npm/dt/web-component-base)](https://www.npmjs.com/package/web-component-base) [![Bundle Size](https://img.shields.io/bundlephobia/minzip/web-component-base)](https://bundlephobia.com/package/web-component-base) > A zero-dependency, ~600 Bytes (minified & gzipped), JS base class for creating reactive custom elements easily When you extend the `WebComponent` class for your component, you only have to define the `template` and `properties`. Any change in any property value will automatically cause just the component UI to render. The result is a reactive UI on property changes. [View on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/ZEwoNOz?editors=1010)
Table of Contents
  1. Import via unpkg
  2. Installation via npm
  3. Usage
  4. `template` vs `render()`
  5. Prop access
    1. Alternatives
  6. Quick Start Example
  7. Life-Cycle Hooks
    1. `onInit` - the component is connected to the DOM, before view is initialized
    2. `afterViewInit` - after the view is first initialized
    3. `onDestroy` - the component is disconnected from the DOM
    4. `onChanges` - every time an attribute value changes
  8. Library Size
## Import via unpkg Import using [unpkg](https://unpkg.com/web-component-base) in your vanilla JS component. We will use this in the rest of our [usage examples](#usage). ```js import { WebComponent } from "https://unpkg.com/web-component-base@latest/WebComponent.min.js"; ``` ## Installation via npm Usable for projects with bundlers or using import maps. ```bash npm i web-component-base ``` ## Usage In your component class: ```js // HelloWorld.mjs import { WebComponent } from "https://unpkg.com/web-component-base@latest/WebComponent.min.js"; class HelloWorld extends WebComponent { static properties = ["my-name", "emotion"]; get template() { return `

Hello ${this.props.myName}${this.props.emotion === "sad" ? ". 😭" : "! 🙌"}

`; } } customElements.define('hello-world', HelloWorld); ``` In your HTML page: ```html ``` The result is a reactive UI that updates on attribute changes: UI showing feeling toward Web Components changing from SAD to EXCITED ## `template` vs `render()` This mental model attempts to reduce the cognitive complexity of authoring components: 1. The `template` is a read-only property (initialized with a `get` keyword) that represents *how* the component view is rendered. 1. There is a `render()` method that triggers a view render. 1. This `render()` method is *automatically* called under the hood every time an attribute value changed. 1. You can *optionally* call this `render()` method at any point to trigger a render if you need (eg, if you have private unobserved properties that need to manually trigger a render) 1. Overriding the `render()` function for handling a custom `template`--where you can return `string | Node | (string | Node)[]` is also an option. More examples on this to follow. ## Prop Access The `props` property of the `WebComponent` interface is provided for easy read/write access to a camelCase counterpart of *any* observed attribute. ```js class HelloWorld extends WebComponent { static properties = ["my-prop"]; onInit() { let count = 0; this.onclick = () => this.props.myProp = `${++count}` } get template() { return `

Hello ${this.props.myProp}

`; } } ``` Assigning a value to the `props.camelCase` counterpart of an observed attribute will trigger an "attribute change" hook. For example, assigning a value like so: ``` this.props.myName = 'hello' ``` ...is like calling the following: ``` this.setAttribute('my-name','hello'); ``` Therefore, this will tell the browser that the UI needs a render if the attribute is one of the component's observed attributes we explicitly provided with `static properties = ['my-name']`; > [!NOTE] > The `props` property of `WebComponent` works like `HTMLElement.dataset`, except `dataset` is only for attributes prefixed with `data-`. A camelCase counterpart using `props` will give read/write access to any attribute, with or without the `data-` prefix. > Another advantage over `HTMLElement.dataset` is that `WebComponent.props` can hold primitive types 'number', 'boolean', 'object' and 'string'. ### Alternatives The current alternatives are using what `HTMLElement` provides out-of-the-box, which are: 1. `HTMLElement.dataset` for attributes prefixed with `data-*`. Read more about this [on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset). 1. Methods for reading/writing attribute values: `setAttribute(...)` and `getAttribute(...)`; note that managing the attribute names as strings can be difficult as the code grows. ## Quick Start Example Here is an example of using a custom element in a single .html file. ```html WC Base Test ``` ## Life-Cycle Hooks Define behavior when certain events in the component's life cycle is triggered by providing hook methods ### onInit() - Triggered when the component is connected to the DOM - Best for setting up the component ```js import { WebComponent } from "https://unpkg.com/web-component-base@latest/WebComponent.min.js"; class ClickableText extends WebComponent { // gets called when the component is used in an HTML document onInit() { this.onclick = () => console.log(">>> click!"); } get template() { return `Click me!`; } } ``` ### afterViewInit() - Triggered after the view is first initialized ```js class ClickableText extends WebComponent { // gets called when the component's innerHTML is first filled afterViewInit() { const footer = this.querySelector('footer'); // do stuff to footer after view is initialized } get template() { return ``; } } ``` ### onDestroy() - Triggered when the component is disconnected from the DOM - best for undoing any setup done in `onInit()` ```js import { WebComponent } from "https://unpkg.com/web-component-base@latest/WebComponent.min.js"; class ClickableText extends WebComponent { clickCallback() { console.log(">>> click!"); } onInit() { this.onclick = this.clickCallback; } onDestroy() { console.log(">>> removing event listener"); this.removeEventListener("click", this.clickCallback); } get template() { return `Click me!`; } } ``` ### onChanges() - Triggered when an attribute value changed ```js import { WebComponent } from "https://unpkg.com/web-component-base@latest/WebComponent.min.js"; class ClickableText extends WebComponent { // gets called when an attribute value changes onChanges(changes) { const {property, previousValue, currentValue} = changes; console.log('>>> ', {property, previousValue, currentValue}) } get template() { return `Click me!`; } } ``` ## Library Size The bundle size was reported to be 587 Bytes (minified & gzipped) by [bundlephobia](https://bundlephobia.com/package/web-component-base). Running [size-limit](https://npmjs.com/package/@size-limit/preset-small-lib) reports the base class size as around 760 Bytes (minified & brotlied), and using the `WebComponent.min.js` version gets it down to around 400 Bytes.