Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions packages/plugin-renderer-lit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,15 @@ import type { LitRendererPlugin } from '@greenwood/plugin-renderer-lit';
## Caveats

1. Lit SSR [does not support native `HTMLElement`](https://github.com/lit/lit/discussions/2092) which means **_you will need to use `LitElement` as your base class in all instances where you are pre-rendering or using SSR_**.
1. Be aware of the known documented [caveats](https://lit.dev/docs/ssr/overview/#library-status) as called out in the Lit SSR docs, such as:
- Lit SSR [**only** renders into declarative shadow roots](https://github.com/lit/lit/issues/3080#issuecomment-1165158794), so you will have to keep browser support and polyfill usage in mind.
- At this time, `LitElement` does not support `async` work (e.g. for `connectedCallback`). You can follow along with this issue [in the Lit repo](https://github.com/lit/lit/issues/2469).
1. Lit only supports templates on the server side for HTML generated content, thus Greenwood's `getBody` API must be used. We would love for [server only components](https://github.com/lit/lit/issues/2469#issuecomment-1759583861) to be a thing though!
1. Be aware of the known [caveats](https://lit.dev/docs/ssr/overview/#library-status) as called out in the Lit SSR docs, such as:
- Lit SSR [**only** renders into declarative shadow roots](https://github.com/lit/lit/issues/3080#issuecomment-1165158794), so you will have to keep browser support (and polyfill usage) in mind.
- At this time, `LitElement` does not support `async` work (e.g. for `connectedCallback`). You can follow along with [this issue](https://github.com/lit/lit/issues/2469) and [this issue](https://github.com/lit/lit/issues/4866) from the Lit repo.
1. For constructor props support, you will have to [map attributes to properties](https://greenwoodjs.dev/docs/plugins/lit-ssr/#usage)
1. Lit does not support [`CSSStyleSheet` (aka CSS Modules) in their SSR DOM shim](https://github.com/lit/lit/issues/4862). As an alternative, you may consider using Greenwood's [**Raw adapter**](https://greenwoodjs.dev/docs/plugins/raw/) to inline CSS in `<style>` tags into your custom elements.
1. Full hydration support is not available yet. See [this Greenwood issue](https://github.com/ProjectEvergreen/greenwood/issues/880) to follow along with when it will land.

_**Note**: As `LitElement` [only renders into Shadow Roots](https://github.com/lit/lit/issues/3080#issuecomment-1165158794), for pages and layouts this plugin will extract the HTML contents of the SSR'd `<template>` tag and output those content into the **Light DOM** of your page._

> See [this repo](https://github.com/thescientist13/greenwood-lit-ssr) for a full demo of isomorphic Lit SSR with SSR pages and API routes deployed to Vercel serverless functions.

## Usage
Expand Down
8 changes: 4 additions & 4 deletions packages/plugin-renderer-lit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@
},
"peerDependencies": {
"@greenwood/cli": "^0.33.0",
"lit": "^3.1.0"
"lit": "^3.3.3"
},
"dependencies": {
"@lit-labs/ssr": "^3.2.0",
"@lit-labs/ssr-client": "^1.1.6"
"@lit-labs/ssr": "^4.1.0",
"@lit-labs/ssr-client": "^1.1.8"
},
"devDependencies": {
"@greenwood/cli": "^0.34.0-alpha.5",
"lit": "^3.1.0"
"lit": "^3.3.3"
}
}
97 changes: 89 additions & 8 deletions packages/plugin-renderer-lit/src/execute-route-module.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { render } from "@lit-labs/ssr";
import { render, LitElementRenderer } from "@lit-labs/ssr";
import { collectResult } from "@lit-labs/ssr/lib/render-result.js";
import { html } from "lit";
import { html, literal, unsafeStatic } from "lit/static-html.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js";

async function executeRouteModule({
Expand All @@ -10,7 +10,9 @@ async function executeRouteModule({
prerender,
htmlContents,
scripts,
request,
contentOptions = {},
params = {},
}) {
const data = {
layout: null,
Expand Down Expand Up @@ -48,16 +50,95 @@ async function executeRouteModule({
data.hydration = true;
}

if (body && getBody) {
const templateResult = await getBody(compilation, page, data.pageData);
if (body) {
if (module.default) {
// for the Lit implementation, we render the custom element programmatically
// and then extract the contents of the `<template>`
const tagName = `${page.id}-page`;
const tagNameLiteral = literal`${unsafeStatic(tagName)}`;
// since Lit does not support passing data to the `constructor` have to map params to attributes for now
const attributes =
Object.entries(params).length > 0
? Object.entries(params)
.map(([key, value]) => `${key}="${value}"`)
.join(" ")
: "";
const litAttributes = literal`${unsafeStatic(attributes)}`;
const pageTemplate = html`<${tagNameLiteral} ${litAttributes}"></${tagNameLiteral}>`;

data.body = await collectResult(render(templateResult));
customElements.define(tagName, module.default);

// only enable when default export is a LitElement
// TODO: provide options for additional tag names from config
// https://github.com/ProjectEvergreen/greenwood/issues/1687
LitElementRenderer.renderOptions.push((element) =>
element.localName === tagName ? { connectedCallback: true } : undefined,
);

// TODO: support disabling SSR on a per element basis from config
// https://github.com/ProjectEvergreen/greenwood/issues/1687
// LitElementRenderer.renderOptions.push((element) =>
// element.localName === 'my-element' ? {disableSsr: true} : undefined
// );

const ssrResult = render(pageTemplate);
const ssrContent = await collectResult(ssrResult);
const ssrContentsMatch =
/<template shadowroot="open" shadowrootmode="open">(.*.)<\/template>/s;

data.body = ssrContent.match(ssrContentsMatch)[1];
} else if (getBody) {
const templateResult = await getBody(compilation, page, request, params);

data.body = await collectResult(render(templateResult));
}
}

if (layout && getLayout) {
const templateResult = await getLayout(compilation, page);
// TODO: layouts as SSR pages
// TODO: constructor props / dynamic routing
// https://github.com/ProjectEvergreen/greenwood/issues/1248

if (layout) {
// support dynamic layouts that are just custom elements vs calls to getLayout
if (!getLayout && !data.body && !page.isSSR && module.default) {
// for the Lit implementation, we render the custom element programmatically
// and then extract the contents of the `<template>`
const tagName = `${page.id}-layout`;
const tagNameLiteral = literal`${unsafeStatic(tagName)}`;
// since Lit does not support passing data to the `constructor` have to map params to attributes for now
const attributes =
Object.entries(params).length > 0
? Object.entries(params)
.map(([key, value]) => `${key}="${value}"`)
.join(" ")
: "";
const litAttributes = literal`${unsafeStatic(attributes)}`;
const layoutTemplate = html`<${tagNameLiteral} ${litAttributes}"></${tagNameLiteral}>`;

customElements.define(tagName, module.default);

// only enable when default export is a LitElement
// TODO: provide options for additional tag names from config
LitElementRenderer.renderOptions.push((element) =>
element.localName === tagName ? { connectedCallback: true } : undefined,
);

// TODO: support disabling SSR on a per element basis from config
// LitElementRenderer.renderOptions.push((element) =>
// element.localName === 'my-element' ? {disableSsr: true} : undefined
// );

const ssrResult = render(layoutTemplate);
const ssrContent = await collectResult(ssrResult);
const ssrContentsMatch =
/<template shadowroot="open" shadowrootmode="open">(.*.)<\/template>/s;

data.layout = ssrContent.match(ssrContentsMatch)[1];
} else if (getLayout) {
const templateResult = await getLayout(compilation, page, request, params);

data.layout = await collectResult(render(templateResult));
data.layout = await collectResult(render(templateResult));
}
}

if (frontmatter && getFrontmatter) {
Expand Down
Loading
Loading