Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add SSR documentation #5969

Merged
merged 11 commits into from
May 16, 2022
151 changes: 147 additions & 4 deletions packages/web-components/fast-ssr/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,150 @@
# FAST SSR
This package contains tools to render FAST components outside the browser. More details to follow...

## Testing
This package uses Playwright and a lightweight web server for running tests. You can run the tests by running `npm run test`.
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![npm version](https://badge.fury.io/js/%40microsoft%2Ffast-ssr.svg)](https://badge.fury.io/js/%40microsoft%2Ffast-ssr)

> Playwright may prompt you to install browsers to run the tests. If so, follow the instructions provided or run `npm run install-playwright-browsers`.
The `@microsoft/fast-ssr` package contains a NodeJS solution for rendering FAST templates and components. While primarily intended for supporting SSR scenarios, it also allows FAST to be used as a general purpose HTML templating solution.
nicholasrice marked this conversation as resolved.
Show resolved Hide resolved

## Requirements
- [NodeJS ^16.0.0](https://nodejs.org)
- [`@microsoft/fast-element@^2.0.0`](https://www.npmjs.com/package/@microsoft/fast-element)

## Important Notes
`@microsoft/fast-ssr` is built using ES modules. This documentation assumes all JavaScript is run as ES module scripts. If that is not the case in your project, you can force ES module format by converting `.js` extensions to `.mjs`. [Details on this here.](https://nodejs.org/api/packages.html)

### Design Philosophy
Performance is a core tenant of this package. To help achieve that, it avoids needing a comprehensive DOM implementation to render components, instead relying on a minimal set of DOM globals provided by the packaged DOM shim. Doing so comes with an important idea to understand; non-custom elements (`<div>`, `<p>`, etc) are never instantiated as JavaScript objects and you cannot gain references to these elements declared in a template. The parts of a template containing these elements are emitted as-is to the SSR string result (with any bindings evaluated). It is for this reason [certain directives are not no-ops](#directive-support-matrix). Additionally, components are rendered using only their template. Imperative code that adds elements or DOM content to a component will be invisible to the template renderer and will not be yielded in the final string result.
nicholasrice marked this conversation as resolved.
Show resolved Hide resolved

## Community Alignment
`@microsoft/fast-ssr` internally implements `ElementRenderer` interface proposed [here](https://github.com/webcomponents-cg/community-protocols/issues/7#issuecomment-825151215) and implemented in [`@lit-labs/ssr`](https://github.com/lit/lit/tree/main/packages/labs/ssr). The `ElementRenderer` interface allows Custom Elements created by different component authoring libraries to be rendered side-by-side, maximizing the portability and interoperability of Web Components. For more on how to do that, see the section on [configuring the RenderInfo object](#configuring-the-renderinfo-object).
nicholasrice marked this conversation as resolved.
Show resolved Hide resolved

## Installation
Install `@microsoft/fast-ssr` and `@microsoft/fast-element` using your package manager of choice:

```shell
npm install --save @microsoft/fast-ssr @microsoft/fast-element
```
## Usage
### Installing the DOM Shim
`@microsoft/fast-ssr` requires a minimal DOM implementation to function. Install the shim by importing it. Doing so will ensure availability of certain DOM globals like `HTMLElement`, `Document`, etc.

```js
import "@microsoft/fast-ssr/install-dom-shim";
```

Alternatively, a full DOM implementation such as [`jsdom`](https://github.com/jsdom/jsdom) or [`happy-dom`](https://github.com/capricorn86/happy-dom) can be used.

### Construct the Renderer
Import the renderer factory and construct a `TemplateRenderer`. You will also need a `RenderInfo` object and a default is provided, more on this [here](#configuring-the-renderinfo-object).
nicholasrice marked this conversation as resolved.
Show resolved Hide resolved
```js
import fastSSR from "@microsoft/fast-ssr";

const { templateRenderer, defaultRenderInfo } = fastSSR();
```

### Define Custom Elements
Ensure that the custom elements used in the template you are rendering are defined in the `customElements` registry. This example defines a component directly, but you can also import any components being used:
```js
import { customElement, html, FASTElement } from "@microsoft/fast-element":

@customElement({name: "my-element", template: html`<h1>${x => x.message}</h1>`})
class MyElement extends FASTElement {
@attr
public message = "Hello World";
}
```

### Rendering
With the `TemplateRenderer` created and a custom element defined, the `TemplateRenderer` can now render a template with that element. The result will be an `iterableIterator<string>`, which allows the result to be streamed to the client before the entire template has been rendered.

```js
const result = templateRenderer.render(html`
<!DOCTYPE HTML>
<html>
<body>
<my-element></my-element>
</body>
</html>
`, defaultRenderInfo);
```

The template being rendered can also be a fragment of HTML, it does not need to be a valid document:

```js
const result = templateRenderer.render(html`<my-element></my-element>`, defaultRenderInfo);
```

#### Rendering Strings
The template renderer can also render `string` types just as it would a template:

```js
const result = templateRenderer.render("<!DOCTYPE HTML><html><body><my-element></my-element></body></html>", defaultRenderInfo);
```

#### Rendering Templates with Bindings
A template can be rendered with arbitrary source data by providing that source as the third argument to `.render()`. When done so, bindings are invoked with that source data:
nicholasrice marked this conversation as resolved.
Show resolved Hide resolved

```ts
const result = templateRenderer.render(html`
<h1>${x => x.message}</h1>
`, defaultRenderInfo, { message: "hello world" });
```

#### Rendering Templates with Directives
The `TemplateRenderer` can render templates with directives such as `when` and `repeat`. Some directives are no-ops, see the [directive support matrix](#directive-support-matrix) for details.

```ts
import { when, repeat } from "@microsoft/fast-element";

const result = templateRenderer.render(html`
${when(x => x.shouldRender, html`
<h1>Colors of a pixel</h1>
${repeat(x => x.data, html`<li>${color => color}</li>`)}
`)}
`, defaultRenderInfo, { shouldRender: true, data: ["red", "green", "blue"] });
```

##### Directive Support Matrix

|Directive Name|Support|
|-|-|
|[`children`](https://www.fast.design/docs/fast-element/using-directives#the-children-directive)|[![](https://img.shields.io/badge/-Unsupported-red)]()|
|[`ref`](https://www.fast.design/docs/fast-element/using-directives#the-ref-directive)|[![](https://img.shields.io/badge/-Unsupported-red)]()|
|[`repeat`](https://www.fast.design/docs/fast-element/using-directives#the-repeat-directive)|[![](https://img.shields.io/badge/-Supported-brightgreen)]()|
|[`slotted`](https://www.fast.design/docs/fast-element/using-directives#the-slotted-directive)|[![](https://img.shields.io/badge/-Unsupported-red)]()|
|[`when`](https://www.fast.design/docs/fast-element/using-directives#the-when-directive)|[![](https://img.shields.io/badge/-Supported-brightgreen)]()|

> Unsupported directives are no-ops. To understand more about why, see the [Design Philosophy.](#design-philosophy)

### Configuring the RenderInfo Object
`TemplateRenderer.render()` must be invoked with a `RenderInfo` object. It's purpose is to provide different element renderers to the process, as well as metadata about the rendering process. It can be used to render custom elements from different templating libraries in the same process. A pre-generated object is created for you by the factory function, but you can also easily construct your own:
nicholasrice marked this conversation as resolved.
Show resolved Hide resolved

```js
const { templateRenderer, elementRenderer } = fastSSR();
templateRenderer.render(html`<some-fast-element></some-fast-element>`, {
elementRenderers: [elementRenderer],
customElementHostStack: [],
customElementInstanceStack: [],
});
```

To render elements built with another library, that library will need to provide an `ElementRenderer` for the library. For example, to render a Lit element along-side a FAST element, provide the FAST `ElementRenderer` *and* the Lit `ElementRenderer` to the `RenderInfo` object:

```js
import fastSSR from "@microsoft/fast-ssr";
import { html } from "@microsoft/fast-element";
import { LitElementRenderer } from "@lit-labs/ssr/lib/lit-element-renderer.js"

const { templateRenderer, elementRenderer } = fastSSR();
// Some implementation that defines both FAST and Lit components
defineComponents();

const result = templateRenderer.render(html`
<my-fast-element></my-fast-element>
<my-lit-element></my-lit-element>
`, {
elementRenderers: [elementRenderer, LitElementRenderer],
customElementHostStack: [],
customElementInstanceStack: []
});
```
8 changes: 6 additions & 2 deletions packages/web-components/fast-ssr/docs/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,16 @@ export class TemplateRenderer {
//
// @internal
renderOpCodes(codes: Op[], renderInfo: RenderInfo, source: unknown, context: ExecutionContext): IterableIterator<string>;
// Warning: (ae-forgotten-export) The symbol "ViewBehaviorFactoryRenderer" needs to be exported by the entry point exports.d.ts
//
// @internal
withViewBehaviorFactoryRenderers(...renderers: ViewBehaviorFactoryRenderer<any>[]): void;
}

// @beta
export interface ViewBehaviorFactoryRenderer<T extends ViewBehaviorFactory> {
matcher: Constructable<T>;
render(behavior: T, renderInfo: RenderInfo, source: any, renderer: TemplateRenderer, context: ExecutionContext): IterableIterator<string>;
}

// (No @packageDocumentation comment for this package)

```
10 changes: 7 additions & 3 deletions packages/web-components/fast-ssr/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {
import { FASTElementRenderer } from "./element-renderer/element-renderer.js";
import { FASTSSRStyleStrategy } from "./element-renderer/style-strategy.js";
import { StyleElementStyleRenderer, StyleRenderer } from "./styles/style-renderer.js";
import { defaultViewBehaviorFactoryRenderers } from "./template-renderer/directives.js";
import {
defaultViewBehaviorFactoryRenderers,
ViewBehaviorFactoryRenderer,
} from "./template-renderer/directives.js";
import {
ComponentDOMEmissionMode,
TemplateRenderer,
Expand Down Expand Up @@ -74,8 +77,9 @@ export default function fastSSR(): {
}

export type {
TemplateRenderer,
ComponentDOMEmissionMode,
FASTElementRenderer,
StyleRenderer,
ComponentDOMEmissionMode,
TemplateRenderer,
ViewBehaviorFactoryRenderer,
};
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,7 @@ <h2>Heading</h2>
fast-id="button1"
data-fast-buffer-events="click"
>
<span>
Button
</span>
<span>Button</span>
</button>
<fake-hydrate-button class="fakeHydrateBtn">
Fake hydrate
Expand Down Expand Up @@ -244,9 +242,7 @@ <h2>Heading</h2>
fast-id="button2"
data-fast-buffer-events="click,mouseover"
>
<span>
Button
</span>
<span>Button</span>
</button>
<fake-hydrate-button class="fakeHydrateBtn">
Fake hydrate
Expand Down
40 changes: 10 additions & 30 deletions packages/web-components/fast-ssr/src/styles/fast-style.fixture.html
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,7 @@ <h2>Heading</h2>
}"
></fast-style>
<button class="control">
<span>
Button
</span>
<span>Button</span>
</button>
</template>
</fast-button>
Expand All @@ -215,9 +213,7 @@ <h2>Heading</h2>
<template shadowroot="open">
<fast-style style-id="fast-button"></fast-style>
<button class="control">
<span>
Button
</span>
<span>Button</span>
</button>
</template>
</fast-button>
Expand All @@ -241,9 +237,7 @@ <h2>Heading</h2>
<template shadowroot="open">
<fast-style style-id="fast-button"></fast-style>
<button class="control">
<span>
Button
</span>
<span>Button</span>
</button>
</template>
</fast-button>
Expand All @@ -267,9 +261,7 @@ <h2>Heading</h2>
<template shadowroot="open">
<fast-style style-id="fast-button"></fast-style>
<button class="control">
<span>
Button
</span>
<span>Button</span>
</button>
</template>
</fast-button>
Expand All @@ -293,9 +285,7 @@ <h2>Heading</h2>
<template shadowroot="open">
<fast-style style-id="fast-button"></fast-style>
<button class="control">
<span>
Button
</span>
<span>Button</span>
</button>
</template>
</fast-button>
Expand All @@ -319,9 +309,7 @@ <h2>Heading</h2>
<template shadowroot="open">
<fast-style style-id="fast-button"></fast-style>
<button class="control">
<span>
Button
</span>
<span>Button</span>
</button>
</template>
</fast-button>
Expand All @@ -345,9 +333,7 @@ <h2>Heading</h2>
<template shadowroot="open">
<fast-style style-id="fast-button"></fast-style>
<button class="control">
<span>
Button
</span>
<span>Button</span>
</button>
</template>
</fast-button>
Expand All @@ -371,9 +357,7 @@ <h2>Heading</h2>
<template shadowroot="open">
<fast-style style-id="fast-button"></fast-style>
<button class="control">
<span>
Button
</span>
<span>Button</span>
</button>
</template>
</fast-button>
Expand All @@ -397,9 +381,7 @@ <h2>Heading</h2>
<template shadowroot="open">
<fast-style style-id="fast-button"></fast-style>
<button class="control">
<span>
Button
</span>
<span>Button</span>
</button>
</template>
</fast-button>
Expand All @@ -423,9 +405,7 @@ <h2>Heading</h2>
<template shadowroot="open">
<fast-style style-id="fast-button"></fast-style>
<button class="control">
<span>
Button
</span>
<span>Button</span>
</button>
</template>
</fast-button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,30 @@ import {
import { TemplateRenderer } from "./template-renderer.js";

/**
* Describes an implementation that can render a directive
* Describes an implementation that can render a directive.
*
* @beta
*/
export interface ViewBehaviorFactoryRenderer<T extends ViewBehaviorFactory> {
/**
* Renders a ViewBehaviorFactory
* @param behavior - The behavior to render
* @param renderInfo - The current RenderInfo context
* @param source - Source data
* @param renderer - The current TemplateRenderer
* @param context - The ExecutionContext
*/
render(
behavior: T,
renderInfo: RenderInfo,
source: any,
renderer: TemplateRenderer,
context: ExecutionContext
): IterableIterator<string>;

/**
* The behavior constructor to use this renderer for.
*/
matcher: Constructable<T>;
}

Expand Down
Loading