Skip to content

Commit

Permalink
Add documentation explaining some architectural concepts (#7033)
Browse files Browse the repository at this point in the history
# Pull Request

## 📖 Description

This PR adds documentation on the internals of `FASTElement` and it's lifecycle events. This is meant to serve as a general overview for contributors looking to understand the architecture of FAST custom elements.

## ✅ Checklist

### General

<!--- Review the list and put an x in the boxes that apply. -->

- [ ] I have included a change request file using `$ npm run change`
- [ ] I have added tests for my changes.
- [ ] I have tested my changes.
- [x] I have updated the project documentation to reflect my changes.
- [x] I have read the [CONTRIBUTING](https://github.com/microsoft/fast/blob/master/CONTRIBUTING.md) documentation and followed the [standards](https://github.com/microsoft/fast/blob/master/CODE_OF_CONDUCT.md#our-standards) for this project.
  • Loading branch information
janechu authored Jan 15, 2025
1 parent f5560fd commit c6b9e8a
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "none",
"comment": "Add documentation explaining some architectural concepts",
"packageName": "@microsoft/fast-element",
"email": "[email protected]",
"dependentChangeType": "none"
}
63 changes: 63 additions & 0 deletions packages/web-components/fast-element/ARCHITECTURE_FASTELEMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# FASTElement

The `FASTElement` is our extension of the `HTMLElement`. As such it leverages the lifecycles but also requires additional setup before before the class `constructor` or the `connectedCallback` method is executed which are explained in the [overview](./ARCHITECTURE_OVERVIEW.md). This document explains specifically what `FASTElement` leverages inside the `HTMLElement`s lifecycle hooks.

For an explanation into `HTMLElement` lifecycle hooks refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks).

## A Custom Element is Detected by the Browser

Because the `FASTElement` is an extension of the `HTMLElement`, it makes use of the same lifecycle hooks and extends them with `$fastController`. It also initializes using another class `ElementController`, the methods this are called during the custom elements native `constructor`, `connectedCallback`, `disconnectedCallback`, and `attributeChangedCallback` lifecycle hooks.

### 🔄 **Lifecycle**: Initialization

```mermaid
flowchart TD
A[A <code>FASTElement</code> web component is added to the <code>DOM</code>] --> B
B[<code>FASTElement</code> initializes the <code>ElementController.forCustomElement</code> passing the Custom Element instance] --> C
B --> F[Observables applied to the <code>FASTElement</code> are updated on the FAST global without values]
C{Is an <code>ElementController</code> available?}
C --> |yes|E
C --> |no|D
D[Initialize a new <code>ElementController</code> referred to as setting an element controller strategy]
D --> F
E[<code>ElementController</code> captures the Custom Element instance and the definition and attaches them to the <code>$fastController</code> which is then attached to the instance]
```

### 🔄 **Lifecycle**: Component is connected

```mermaid
flowchart TD
A[browser <code>HTMLElement</code>'s <code>connectedCallback</code> is called] --> B
B[<code>this.$fastController.connect</code> in FASTElement is called] --> C
B --> D
B --> E
B --> F
C[bind observables by capturing the Custom Elements properties and setting the values from the bound observables on the Custom Element]
D[connect behaviors by call the <code>connectedCallback</code> on all behaviors]
E[render template, execute an <code>ElementViewTemplate</code>'s render method]
F[add styles either as an appended <code>StyleElement</code> node or an <code>adoptedStylesheet</code> which may include and attach behaviors]
```

#### Render Template

The rendering of the template on the `ElementController` is by the `renderTemplate` method which is called during the `ElementController.connect` method which is triggered by `HTMLElement`s `connectedCallback` lifecycle.

The `renderTemplate` identifies the Custom Element, and the shadow root associated with the Custom Element. This then places a rendering of the template (an `ElementView`) onto the internal `view` of the controller. When creating the `ElementView`/`HTMLView` using the `ViewTemplate.render`, the `Compile.compile()` method identifies a `DocumentFragment` either by using an existing `<template>` tag, or creating one to wrap the contents of the shadow root. A new `CompilationContext` is created and the `compileAttributes` function is called, this results in the replacement of the placeholder attributes initally set-up during the pre-render step with their values if a value has been assigned. The factories with the associated nodes identified are then passed to the context. The view then binds all behaviors to the source element. The `CompilationContext.createView` is executed with the `DocumentFragment` as the root, and returns an `HTMLView`. This `HTMLView` includes an `appendTo` method to attach the fragment to the host element, which it then does. It should be noted that the compiled HTML is a `string`, which when set on the `DocumentFragment` as `innerHTML`, this allows the browser to dictate the creation of HTML nodes.

### 🔄 **Lifecycle**: Component is disconnected

When a component is disconnected, a cleanup step is created to remove associated behaviors.

### 🔄 **Lifecycle**: Attribute has been changed

Attributes have an `AttributeDefinition` which allows for converters, attachment of the `<attributName>Changed` aspect of the `@attr` decorator among other capabilities.

```mermaid
flowchart TD
A[The browser <code>HTMLElement</code>'s <code>attributeChangedCallback</code> is called] --> B
B[<code>this.$fastController.onAttributeChangedCallback</code> in <code>FASTElement</code> is called] --> C
C[calls the attribute definitions <code>onAttributeChangedCallback</code> method with the updated value] --> D
D[An <code>Updates.enqueue</code> is called which places the update in the task queue which is then executed, these are performed async unless otherwise specified]
```

These changes are observed and similar to the way `Observables` work, they utilize an `Accessor` pattern which has a `getValue` and `setValue` in which DOM updates are applied.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# `html` tagged template literal

The `html` export from `@microsoft/fast-element` is used to create the template logic for the custom element.

## Pre-processing the `html` tagged template literal contents

Before the template can be used it goes through a step to convert it into a `ViewTemplate` which it does via the `ViewTemplate.create()` method. This is then used during the `compose` step, before `FASTElement` is instantiated.

During the `Compiler.compile()` method(triggered by `ViewTemplate.create()` method), the following happens for each string:
- Factories with unique IDs are created for each tag template literal argument (or `TemplateValue`) which matches with the corresponding string
- A binding is created from the `TemplateValue`

A resulting string using a `createHTML()` function is produced using the `HTMLDirective`s executed for each factory. The behavior is augmented by the previous string from the `html` tag template which determines the aspect if one exists, these aspects are the `@`, `:`, or other binding aspect attached to attributes.

The `createHTML()` function utilizes a `Markup` attribute which is assigned to a factory's unique ID. The strings are concatenated and passed to a new `ViewTemplate` with all the factories (empty until one is assigned) that act as a dictionary with the unique IDs as the key to look up each factory once it has been created. The string this creates is injected into a `<template>` as `innerHTML`, which allows the browser to create the nodes and placeholder factory IDs, with the only `DOM` node that is explicitly created being the wrapping `<template>` element.

## Directives

The `HTMLBindingDirective` applies bindings to items identified as various `TemplateValue`s within the `html` tagged template. The `createHTML` step uses the factory associated with the binding to create strings in the markup using the factory's ID.

```mermaid
flowchart TD
A[A <code>new HTMLBindingDirective</code> is created with a data binding which has a policy, options, <code>createObserver</code>, and an evaluate method assigned to the passed arrow function such as <pre>x => x.foo</pre>]
B[<code>oneTime</code> binding passes the corresponding tag template argument, an arrow function]
C[<code>oneWay</code> binding passes a copy of the corresponding tag template argument, an arrow function]
D[An already specified binding such as a <code>repeat</code> or <code>when</code> directive is passed]
A --> B
A --> C
A --> D
E[When a <code>createObserver</code> is called, an <code>Observable.binding</code> is created passing the arrow function to be evaluated and the subscriber to be notified]
C --> E
F[When a <code>createObserver</code> is called, the instance of the one time binding is returned which includes a bind method returning the arrow function executed against the controller source and context]
B --> F
```
10 changes: 10 additions & 0 deletions packages/web-components/fast-element/ARCHITECTURE_INTRO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Introduction

This document (and the linked documents) explains how the exports and side effects of `@microsoft/fast-element` are used to create custom elements.

## Glossary

- [Overview](./ARCHITECTURE_OVERVIEW.md): How the `@microsoft/fast-element` should be used by a developer and what code is executed during the first render.
- [`FASTElement`](./ARCHITECTURE_FASTELEMENT.md): How the `FASTElement` is architected.
- [`html` tagged template literal](./ARCHITECTURE_HTML_TAGGED_TEMPLATE_LITERAL.md): How the `html` tagged template literal takes and converts the contents into a `VIEWTemplate`.
- [`Updates` queue](./ARCHITECTURE_UPDATES.md): How updates to attributes and observables are processed.
52 changes: 52 additions & 0 deletions packages/web-components/fast-element/ARCHITECTURE_OVERVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# General FAST usage

`FASTElement` is an extension of `HTMLElement` which makes use of Custom Element APIs native to the browser. It also supplies the following methods:

- The `compose` method combines the Custom Element name, template, style, and other options to create the definition for the Custom Element.
- The `define` method makes use of the native Custom Element [`define()`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define) to register the Custom Element with a Custom Element Registry.
- The `from` method allows the use of Customized Built-in elements, which extend from native elements such as `HTMLButtonElement`.

### Creating a Custom Element from FASTElement

A basic developer flow for defining a custom element looks like this:

```mermaid
flowchart TD
A[Create a <code>FASTElement</code> web component by extending the <code>FASTElement</code> class] --> F
F[Compose with <code>FASTElement.compose</code> to include template and styles] --> G[Define the component in the browser with <code>FASTElement.define</code>]
```

Let's take a look at the compose step to see what the FAST architecture is doing at this stage.

### Composing a Custom Element

The `FASTElement.compose()` function creates a new `FASTElementDefinition`, which includes all metadata needed for the element (such as templates, attributes, and styles). The element definition is registered with the global `FAST` for re-use, and the `FASTElementDefinition` is returned. The resulting object can be retrieved via `ElementController.definition`.

## A Custom Element in JavaScript is sent to the Browser

Let's step back from defining the Custom Element and consider what is happening when we import from the `@microsoft/fast-element` package.

First, a global `FAST` property will be created if one does not already exist, typically in browser on the `window`.

Additionally, when Custom Elements are included in a script a few things might happen even before a Custom Element gets detected by the browser. First, there are initial side effects caused by the use of decorators. These include the `attr` and `observable` decorators made available by the `@microsoft/fast-element` package.

Here is a basic flow of what code is executed and when during initial load of a script that contains a FAST defined Custom Element:

```mermaid
flowchart TD
A@{ shape: circle, label: "The browser loads the JavaScript file containing FAST and FAST Custom Element definitions" }
B@{ shape: rect, label: "The FAST global is added to the window" }
A --> B
C@{ shape: rect, label: "A Custom Element executes the <code>compose</code> step"}
B --> C
D@{ shape: procs, label: "<ul style="text-align: left"><li>Any defined observable decorators are added to the FAST global</li><li>An attribute decorator locates the associated Custom Elements constructor which it uses to push itself to the Custom Elements attribute collection</li></ul>"}
C --> D
F@{ shape: rect, label: "An HTML tagged template literal is executed for the FAST Custom Element and applied to the definition" }
D --> F
G@{ shape: rect, label: "The <code>define</code> step is hit and the <code>customElement</code> is registered with the Custom Element Registry" }
F --> G
H@{ shape: rect, label: "A Custom Element is detected on the page and the browser initializes it" }
I@{ shape: dbl-circ, label: "The lifecycle steps for <code>FASTElement</code> are executed" }
G --> H
H --> I
```
11 changes: 11 additions & 0 deletions packages/web-components/fast-element/ARCHITECTURE_UPDATES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# `Updates`

The `Updates` API is part of the `FAST` global. The updates that are queued include updates to attributes, observables, and observed arrays.

## Attributes

An attribute change may be queued when the value of the attribute is updated. This change is triggered by `HTMLElement` lifecycle hook `attributeChangedCallback`. The `Updates` queue is used to try to reflect the attribute with the updated value onto the element using `DOM` APIs.

## Observables

The `Reflect` API is used to override `defineProperty` in order to observe a property on an Custom Element, this overrides the getter and setter, which allows subscribers to be notified of changes. Any changes which `set` the value are passed to the `Updates` queue.

0 comments on commit c6b9e8a

Please sign in to comment.