From fa25b6726ec90bd8cb6de8314aa3d753a9ca94e0 Mon Sep 17 00:00:00 2001 From: nicholasrice Date: Wed, 11 May 2022 16:09:44 -0700 Subject: [PATCH 01/11] adding ssr documentation --- packages/web-components/fast-ssr/README.md | 90 +++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/packages/web-components/fast-ssr/README.md b/packages/web-components/fast-ssr/README.md index c9302b92748..a9c1008f42d 100644 --- a/packages/web-components/fast-ssr/README.md +++ b/packages/web-components/fast-ssr/README.md @@ -1,5 +1,93 @@ # FAST SSR -This package contains tools to render FAST components outside the browser. More details to follow... + +[![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) + +The `@microsoft/fast-ssr` package contains a NodeJS solution for emitting templates and components built using FAST to HTML markup strings. + +## 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`. [Click here for more information.](https://nodejs.org/api/packages.html) + +## How it Works +`@microsoft/fast-ssr` internally implements `ElementRenderer` interface proposed [here](https://github.com/webcomponents-cg/community-protocols/issues/7#issuecomment-825151215) +nd 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. + +- Parses string templates to HTML, depth-first traversal, constructing custom element nodes it finds and yielding out shadow dom. +- Only custom elements are constructed, native elements are yielded directly as strings. This means directives such as `children`, `slotted`, and `ref` are no-ops. +- Elements need to exist in the template to be rendered. Idiomatic operations appending and removing elements will not result in SSR string changes + +## 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`: +```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`

${x => x.message}

`}) +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`, which allows the result to be streamed to the client before the entire template has been rendered. + +```js +const result templateRenderer.render(html` + + + + + + +`, defaultRenderInfo); +``` + +#### Rendering Strings +The template renderer can also render `string` types just as it would a template: + +```js +const result templateRenderer.render("", defaultRenderInfo); +``` + +#### Rendering Templates with Bindings +A template can be rendered with arbitrary source data. When done so, bindings are invoked with that source data: + +```ts +const result = templateRenderer.render(html` +

${x => x.message}

+`, defaultRenderInfo, { message: "hello world" }); +``` + +#### Rendering Templates with Directives +The `TemplateRenderer` can render templates with `@microsoft/fast-element` directives such as `when` and `repeat`. ## Testing This package uses Playwright and a lightweight web server for running tests. You can run the tests by running `npm run test`. From a05444c76745b408f7f1865dc57b0c0212f16fde Mon Sep 17 00:00:00 2001 From: nicholasrice Date: Thu, 12 May 2022 11:26:28 -0700 Subject: [PATCH 02/11] exporting ViewBehaviorFactoryRenderer type --- packages/web-components/fast-ssr/README.md | 99 ++++++++++++++----- .../fast-ssr/docs/api-report.md | 12 ++- .../web-components/fast-ssr/src/exports.ts | 10 +- .../src/template-renderer/directives.ts | 32 +++--- 4 files changed, 109 insertions(+), 44 deletions(-) diff --git a/packages/web-components/fast-ssr/README.md b/packages/web-components/fast-ssr/README.md index a9c1008f42d..951f6a8224b 100644 --- a/packages/web-components/fast-ssr/README.md +++ b/packages/web-components/fast-ssr/README.md @@ -3,22 +3,20 @@ [![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) -The `@microsoft/fast-ssr` package contains a NodeJS solution for emitting templates and components built using FAST to HTML markup strings. +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. ## 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`. [Click here for more information.](https://nodejs.org/api/packages.html) +`@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) -## How it Works -`@microsoft/fast-ssr` internally implements `ElementRenderer` interface proposed [here](https://github.com/webcomponents-cg/community-protocols/issues/7#issuecomment-825151215) -nd 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. +### 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 (`
`, `

`, 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. -- Parses string templates to HTML, depth-first traversal, constructing custom element nodes it finds and yielding out shadow dom. -- Only custom elements are constructed, native elements are yielded directly as strings. This means directives such as `children`, `slotted`, and `ref` are no-ops. -- Elements need to exist in the template to be rendered. Idiomatic operations appending and removing elements will not result in SSR string changes +## 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). ## Installation Install `@microsoft/fast-ssr` and `@microsoft/fast-element` using your package manager of choice: @@ -37,7 +35,7 @@ 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`: +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). ```js import fastSSR from "@microsoft/fast-ssr"; @@ -60,25 +58,31 @@ class MyElement extends FASTElement { 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`, which allows the result to be streamed to the client before the entire template has been rendered. ```js -const result templateRenderer.render(html` - - - - - - +const result = templateRenderer.render(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``, defaultRenderInfo); +``` + #### Rendering Strings The template renderer can also render `string` types just as it would a template: ```js -const result templateRenderer.render("", defaultRenderInfo); +const result = templateRenderer.render("", defaultRenderInfo); ``` #### Rendering Templates with Bindings -A template can be rendered with arbitrary source data. When done so, bindings are invoked with that source data: +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: ```ts const result = templateRenderer.render(html` @@ -87,9 +91,60 @@ const result = templateRenderer.render(html` ``` #### Rendering Templates with Directives -The `TemplateRenderer` can render templates with `@microsoft/fast-element` directives such as `when` and `repeat`. +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` +

Colors of a pixel

+ ${repeat(x => x.data, html`
  • ${color => color}
  • `)} + `)} +`, 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)]()| -## Testing -This package uses Playwright and a lightweight web server for running tests. You can run the tests by running `npm run test`. +> Unsupported directives are no-ops. To understand more about why, see the [Design Philosophy.](#design-philosophy) -> Playwright may prompt you to install browsers to run the tests. If so, follow the instructions provided or run `npm run install-playwright-browsers`. \ No newline at end of file +### 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: + +```js +const { templateRenderer, elementRenderer } = fastSSR(); +templateRenderer.render(html``, { + 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` + + +`, { + elementRenderers: [elementRenderer, LitElementRenderer], + customElementHostStack: [], + customElementInstanceStack: [] +}); +``` diff --git a/packages/web-components/fast-ssr/docs/api-report.md b/packages/web-components/fast-ssr/docs/api-report.md index 51c8333c316..208614dfdf7 100644 --- a/packages/web-components/fast-ssr/docs/api-report.md +++ b/packages/web-components/fast-ssr/docs/api-report.md @@ -51,12 +51,20 @@ export class TemplateRenderer { // // @internal renderOpCodes(codes: Op[], renderInfo: RenderInfo, source: unknown, context: ExecutionContext): IterableIterator; - // Warning: (ae-forgotten-export) The symbol "ViewBehaviorFactoryRenderer" needs to be exported by the entry point exports.d.ts - // // @internal withViewBehaviorFactoryRenderers(...renderers: ViewBehaviorFactoryRenderer[]): void; } +// @public +export interface ViewBehaviorFactoryRenderer { + // (undocumented) + matcher: Constructable; + // Warning: (ae-incompatible-release-tags) The symbol "render" is marked as @public, but its signature references "TemplateRenderer" which is marked as @beta + // + // (undocumented) + render(behavior: T, renderInfo: RenderInfo, source: any, renderer: TemplateRenderer, context: ExecutionContext): IterableIterator; +} + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/web-components/fast-ssr/src/exports.ts b/packages/web-components/fast-ssr/src/exports.ts index 5ca3b65dde9..78b653b71e5 100644 --- a/packages/web-components/fast-ssr/src/exports.ts +++ b/packages/web-components/fast-ssr/src/exports.ts @@ -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, @@ -74,8 +77,9 @@ export default function fastSSR(): { } export type { - TemplateRenderer, + ComponentDOMEmissionMode, FASTElementRenderer, StyleRenderer, - ComponentDOMEmissionMode, + TemplateRenderer, + ViewBehaviorFactoryRenderer, }; diff --git a/packages/web-components/fast-ssr/src/template-renderer/directives.ts b/packages/web-components/fast-ssr/src/template-renderer/directives.ts index 59ab38fccba..9bb76f1f3da 100644 --- a/packages/web-components/fast-ssr/src/template-renderer/directives.ts +++ b/packages/web-components/fast-ssr/src/template-renderer/directives.ts @@ -13,7 +13,9 @@ import { import { TemplateRenderer } from "./template-renderer.js"; /** - * Describes an implementation that can render a directive + * Describes an implementation that can render a directive. + * + * @public */ export interface ViewBehaviorFactoryRenderer { render( @@ -26,8 +28,8 @@ export interface ViewBehaviorFactoryRenderer { matcher: Constructable; } -export const RepeatDirectiveRenderer: ViewBehaviorFactoryRenderer = Object.freeze( - { +export const RepeatDirectiveRenderer: ViewBehaviorFactoryRenderer = + Object.freeze({ matcher: RepeatDirective, *render( directive: RepeatDirective, @@ -64,31 +66,27 @@ export const RepeatDirectiveRenderer: ViewBehaviorFactoryRenderer = Object.freeze( - { +export const ChildrenDirectiveRenderer: ViewBehaviorFactoryRenderer = + Object.freeze({ matcher: ChildrenDirective, render: noop, - } -); + }); -export const RefDirectiveRenderer: ViewBehaviorFactoryRenderer = Object.freeze( - { +export const RefDirectiveRenderer: ViewBehaviorFactoryRenderer = + Object.freeze({ matcher: RefDirective, render: noop, - } -); -export const SlottedDirectiveRenderer: ViewBehaviorFactoryRenderer = Object.freeze( - { + }); +export const SlottedDirectiveRenderer: ViewBehaviorFactoryRenderer = + Object.freeze({ matcher: SlottedDirective, render: noop, - } -); + }); export const defaultViewBehaviorFactoryRenderers: ViewBehaviorFactoryRenderer[] = [ RepeatDirectiveRenderer, From 732e39ae9a03403b39cb843dd18011aeda5108c3 Mon Sep 17 00:00:00 2001 From: nicholasrice Date: Thu, 12 May 2022 14:30:48 -0700 Subject: [PATCH 03/11] pretty pretty --- .../web-components/fast-ssr/src/dom-shim.ts | 2 +- .../src/element-renderer/element-renderer.ts | 2 +- .../fast-command-buffer/index.fixture.html | 19 ++--- .../src/styles/fast-style.fixture.html | 80 +++++++------------ .../template-renderer/template-renderer.ts | 6 +- 5 files changed, 41 insertions(+), 68 deletions(-) diff --git a/packages/web-components/fast-ssr/src/dom-shim.ts b/packages/web-components/fast-ssr/src/dom-shim.ts index 95bf68dddab..84ba08e944d 100644 --- a/packages/web-components/fast-ssr/src/dom-shim.ts +++ b/packages/web-components/fast-ssr/src/dom-shim.ts @@ -88,7 +88,7 @@ abstract class HTMLElement extends Element { } public attachShadow(init: ShadowRootInit) { - const shadowRoot = ({ host: this } as unknown) as ShadowRoot; + const shadowRoot = { host: this } as unknown as ShadowRoot; if (init && init.mode === "open") { this.#shadowRoot = shadowRoot; } diff --git a/packages/web-components/fast-ssr/src/element-renderer/element-renderer.ts b/packages/web-components/fast-ssr/src/element-renderer/element-renderer.ts index f5daa06570b..64a77a94f09 100644 --- a/packages/web-components/fast-ssr/src/element-renderer/element-renderer.ts +++ b/packages/web-components/fast-ssr/src/element-renderer/element-renderer.ts @@ -128,7 +128,7 @@ export abstract class FASTElementRenderer extends ElementRenderer { if (view !== null) { yield* this.templateRenderer.renderOpCodes( - ((view as unknown) as SSRView).codes, + (view as unknown as SSRView).codes, renderInfo, this.element, ExecutionContext.default diff --git a/packages/web-components/fast-ssr/src/fast-command-buffer/index.fixture.html b/packages/web-components/fast-ssr/src/fast-command-buffer/index.fixture.html index 641b897e4db..21ba6bded5b 100644 --- a/packages/web-components/fast-ssr/src/fast-command-buffer/index.fixture.html +++ b/packages/web-components/fast-ssr/src/fast-command-buffer/index.fixture.html @@ -123,8 +123,8 @@ -
    - +
    + - + - + - + - + - + - + - + - + - + - +