From 200a40fc1f21d5a93e7931fa33e54b1882793ce9 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Tue, 27 Feb 2024 18:15:32 -0500 Subject: [PATCH] update Lit renderer README and apply conditional Lit script hydration --- packages/cli/src/config/rollup.config.js | 6 +- packages/cli/src/lifecycles/graph.js | 8 +- packages/plugin-renderer-lit/README.md | 90 +++++++++---------- .../src/execute-route-module.js | 19 ++-- packages/plugin-renderer-lit/src/index.js | 35 ++------ .../cases/serve.default/serve.default.spec.js | 16 ++-- .../cases/serve.default/src/pages/users.js | 9 +- 7 files changed, 74 insertions(+), 109 deletions(-) diff --git a/packages/cli/src/config/rollup.config.js b/packages/cli/src/config/rollup.config.js index 3154bbcf4..8aeecc586 100644 --- a/packages/cli/src/config/rollup.config.js +++ b/packages/cli/src/config/rollup.config.js @@ -358,7 +358,8 @@ const getRollupConfigForApis = async (compilation) => { // support ESM favorable export conditions // https://github.com/ProjectEvergreen/greenwood/issues/1118 nodeResolve({ - exportConditions: ['default', 'module', 'import', 'node'] + exportConditions: ['default', 'module', 'import', 'node'], + preferBuiltins: true }), commonjs(), greenwoodImportMetaUrl(compilation) @@ -383,7 +384,8 @@ const getRollupConfigForSsr = async (compilation, input) => { // support ESM favorable export conditions // https://github.com/ProjectEvergreen/greenwood/issues/1118 nodeResolve({ - exportConditions: ['default', 'module', 'import', 'node'] + exportConditions: ['default', 'module', 'import', 'node'], + preferBuiltins: true }), commonjs(), greenwoodImportMetaUrl(compilation), diff --git a/packages/cli/src/lifecycles/graph.js b/packages/cli/src/lifecycles/graph.js index ae82285e6..57e2dcbcd 100644 --- a/packages/cli/src/lifecycles/graph.js +++ b/packages/cli/src/lifecycles/graph.js @@ -49,6 +49,7 @@ const generateGraph = async (compilation) => { let customData = {}; let filePath; let prerender = true; + let hydration = false; /* * check if additional nested directories exist to correctly determine route (minus filename) @@ -130,7 +131,8 @@ const generateGraph = async (compilation) => { const request = await requestAsObject(new Request(filenameUrl)); worker.on('message', async (result) => { - prerender = result.prerender; + prerender = result.prerender ?? false; + hydration = result.hydration ?? false; if (result.frontmatter) { result.frontmatter.imports = result.frontmatter.imports || []; @@ -201,6 +203,7 @@ const generateGraph = async (compilation) => { * title: a default value that can be used for * isSSR: if this is a server side route * prerednder: if this should be statically exported + * prerednder: if this page needs hydration support */ pages.push({ data: customData || {}, @@ -220,7 +223,8 @@ const generateGraph = async (compilation) => { template, title, isSSR: !isStatic, - prerender + prerender, + hydration }); } } diff --git a/packages/plugin-renderer-lit/README.md b/packages/plugin-renderer-lit/README.md index 0bdae3e78..860c62c0f 100644 --- a/packages/plugin-renderer-lit/README.md +++ b/packages/plugin-renderer-lit/README.md @@ -2,7 +2,7 @@ ## Overview -A Greenwood plugin for using [**Lit**'s SSR capabilities](https://github.com/lit/lit/tree/main/packages/labs/ssr) as a custom server-side renderer. Although support is experimental at this time, this plugin also gives the ability to statically render entire pages and templates (instead of puppeteer) to output completely static sites. +A Greenwood plugin for using [**Lit**'s SSR capabilities](https://github.com/lit/lit/tree/main/packages/labs/ssr) as a custom server-side renderer. Although support is experimental at this time, this plugin also gives the ability to statically render entire pages and templates to output completely static sites. _We are still actively working on SSR features and enhancements for Greenwood [as part of our 1.0 release](https://github.com/ProjectEvergreen/greenwood/issues?q=is%3Aissue+is%3Aopen+label%3Assr+milestone%3A1.0) so please feel free to test it out and report your feedback._ 🙏 @@ -11,7 +11,7 @@ _We are still actively working on SSR features and enhancements for Greenwood [a ## Prerequisite -This packages depends on the Lit package as a `peerDependency`. This means you must have Lit already installed in your project. You can install anything following the `2.x` release line. +This packages depends on the Lit package as a `peerDependency`. This means you must have Lit already installed in your project. You can install anything following the `3.x` release line. ```sh # npm @@ -33,7 +33,18 @@ npm install @greenwood/plugin-renderer-lit --save-dev yarn add @greenwood/plugin-renderer-lit --dev ``` +## Caveats + +1. Please familiarize yourself with some of the [caveats](https://lit.dev/docs/ssr/overview/#library-status) called out in the Lit docs, like: + - 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. 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 only 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. Full hydration support is not available yet. See [this Greenwood issue](https://github.com/ProjectEvergreen/greenwood/issues/880) to follow along when it will land + +> 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. + ## Usage + Add this plugin to your _greenwood.config.js_. ```javascript @@ -48,58 +59,45 @@ export default { } ``` -Now, you can write some [SSR routes](/docs/server-rendering/) using Lit including all the [available APIs](docs/server-rendering/#api). The below example uses the standard [SimpleGreeting](https://lit.dev/playground/) component from the Lit docs by also using a LitElement as the `default export`! -```js -import { html, LitElement } from 'lit'; -import './path/to/greeting.js'; - -export default class ArtistsPage extends LitElement { - - constructor() { - super(); - this.artists = [{ /* ... */ }]; - } - - render() { - const { artists } = this; - - return html` - ${ - artists.map((artist) => { - const { id, name, imageUrl } = artist; +Now, you can author [SSR pages](/docs/server-rendering/) using Lit templates and components using Greenwood's [`getBody` API](https://www.greenwoodjs.io/docs/server-rendering/#usage). The below is an example of generating a template of LitElement based `` web components. - return html` - - - - - - -
- `; - }) - } - `; - } +```js +// src/pages/products.js +import { html } from 'lit'; +import '../components/card.js'; + +export async function getBody() { + const products = await getProducts(); + + return html` + ${ + products.map((product, idx) => { + const { title, thumbnail } = product; + + return html` + + `; + }) + } + `; } - -// for now these are needed for the Lit specific implementations -customElements.define('artists-page', ArtistsPage); -export const tagName = 'artists-page'; ``` -## Caveats +## Options -There are a few considerations to take into account when using a `LitElement` as your page component: -- 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. -- Depending on your use case, SSR bundling may break due to bundle chunking and code splitting by Rollup, which we are [hoping to correct ASAP](https://github.com/ProjectEvergreen/greenwood/issues/1118). -- At this time, `LitElement` does [not support `async` work](https://lit.dev/docs/ssr/overview/#library-status) which makes data fetching in pages a bit of challenge. You can follow along with this issue [in the Lit repo](https://github.com/lit/lit/issues/2469). +### Hydration -> _You can see a work (in progress) demo of using Lit SSR (with Serverless!) [here](https://github.com/thescientist13/greenwood-demo-adapter-vercel-lit/)._ +In order for server-rendered components to become interactive on the client side, Lit's [client-side hydration script](https://lit.dev/docs/ssr/client-usage/#loading-@lit-labsssr-clientlit-element-hydrate-support.js) must be included on the page. For any page that would need this script added, you can simply `export` the **hydration** option from your page. -## Options +```js +// src/pages/products.js +export const hydration = true; +``` -### Prerender (experimental) +### Prerender The plugin provides a setting that can be used to override Greenwood's [default _prerender_](/docs/configuration/#prerender) implementation which uses [WCC](https://github.com/ProjectEvergreen/wcc), to use Lit instead. diff --git a/packages/plugin-renderer-lit/src/execute-route-module.js b/packages/plugin-renderer-lit/src/execute-route-module.js index 150c2a4f5..4633447e5 100644 --- a/packages/plugin-renderer-lit/src/execute-route-module.js +++ b/packages/plugin-renderer-lit/src/execute-route-module.js @@ -8,9 +8,8 @@ async function executeRouteModule({ moduleUrl, compilation, page, prerender, htm template: null, body: null, frontmatter: null, - html: null - // hydrate: false, - // pageData: {} + html: null, + hydration: false }; // prerender static content @@ -24,17 +23,11 @@ async function executeRouteModule({ moduleUrl, compilation, page, prerender, htm data.html = await collectResult(render(templateResult)); } else { const module = await import(moduleUrl).then(module => module); - // const { getTemplate = null, getBody = null, getFrontmatter = null, hydration = false, loader } = module; - const { getTemplate = null, getBody = null, getFrontmatter = null } = module; + const { getTemplate = null, getBody = null, getFrontmatter = null, hydration = false } = module; - // if (hydration) { - // data.hydrate = true; - // } - - // if (loader) { - // data.pageData = await loader(); // request, compilation, etc can go here - // console.log(data.pageData); - // } + if (hydration) { + data.hydration = true; + } if (getBody) { const templateResult = await getBody(compilation, page, data.pageData); diff --git a/packages/plugin-renderer-lit/src/index.js b/packages/plugin-renderer-lit/src/index.js index 56d013de3..82325e945 100755 --- a/packages/plugin-renderer-lit/src/index.js +++ b/packages/plugin-renderer-lit/src/index.js @@ -1,58 +1,35 @@ -// import { checkResourceExists } from '../../lib/resource-utils.js'; -// import fs from 'fs/promises'; import { ResourceInterface } from '@greenwood/cli/src/lib/resource-interface.js'; class LitHydrationResource extends ResourceInterface { constructor(compilation, options) { super(compilation, options); - // this.extensions = ['html']; - // this.contentType = 'text/html'; - // this.libPath = '@greenwood/router/router.js'; } - // assumes Greenwood's standard-html plugin has tracked this metadata - // during resource serve lifecycle async shouldIntercept(url) { const { pathname } = url; const matchingRoute = this.compilation.graph.find((node) => node.route === pathname) || {}; - // const { hydrate, pageData } = matchingRoute; - return matchingRoute.isSSR; // hydrate && pageData; + return matchingRoute.isSSR && matchingRoute.hydration; } async intercept(url, request, response) { let body = await response.text(); + const { pathname } = url; + const matchingRoute = this.compilation.graph.find((node) => node.route === pathname) || {}; + console.log('LIT intercept =>', { url, matchingRoute }); - // TODO would be nice not to have to do this, but - // this hydrate lib is not showing up in greenwood build / serve + // TODO would be nice not have to manually set module-shim + // when we drop support for import-map shim - https://github.com/ProjectEvergreen/greenwood/pull/1115 const type = process.env.__GWD_COMMAND__ === 'develop' // eslint-disable-line no-underscore-dangle ? 'module-shim' : 'module'; - // TODO have to manually set module-shim? body = body.replace('', ` `); - // TODO full hydration implementation? - // - return new Response(body); } } diff --git a/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js b/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js index 8b8352c2c..985751fec 100644 --- a/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js +++ b/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js @@ -211,15 +211,6 @@ describe('Serve Greenwood With: ', function() { expect(styles.length).to.equal(1); }); - // TODO this should be managed via a plugin, not in core - // https://github.com/ProjectEvergreen/greenwood/issues/728 - // it('should have one