diff --git a/packages/cli/src/lib/api-route-worker.js b/packages/cli/src/lib/api-route-worker.js index 44a289b87..2388d057e 100644 --- a/packages/cli/src/lib/api-route-worker.js +++ b/packages/cli/src/lib/api-route-worker.js @@ -1,33 +1,6 @@ // https://github.com/nodejs/modules/issues/307#issuecomment-858729422 import { parentPort } from "node:worker_threads"; -import { transformKoaRequestIntoStandardRequest } from "./resource-utils.js"; - -// based on https://stackoverflow.com/questions/57447685/how-can-i-convert-a-request-object-into-a-stringifiable-object-in-javascript -async function responseAsObject(response) { - if (!(response instanceof Response)) { - throw Object.assign(new Error(), { - name: "TypeError", - message: "Argument must be a Response object", - }); - } - response = response.clone(); - - function stringifiableObject(obj) { - const filtered = {}; - for (const key in obj) { - if (["boolean", "number", "string"].includes(typeof obj[key]) || obj[key] === null) { - filtered[key] = obj[key]; - } - } - return filtered; - } - - return { - ...stringifiableObject(response), - headers: Object.fromEntries(response.headers), - body: await response.text(), - }; -} +import { transformKoaRequestIntoStandardRequest, responseAsObject } from "./resource-utils.js"; async function executeRouteModule({ href, request, params }) { const { body, headers = {}, method, url } = request; diff --git a/packages/cli/src/lib/resource-utils.js b/packages/cli/src/lib/resource-utils.js index dee4dc1f7..950f7a5e7 100644 --- a/packages/cli/src/lib/resource-utils.js +++ b/packages/cli/src/lib/resource-utils.js @@ -254,6 +254,16 @@ function transformKoaRequestIntoStandardRequest(url, request) { }); } +function stringifiableObject(obj) { + const filtered = {}; + for (const key in obj) { + if (["boolean", "number", "string"].includes(typeof obj[key]) || obj[key] === null) { + filtered[key] = obj[key]; + } + } + return filtered; +} + // https://stackoverflow.com/questions/57447685/how-can-i-convert-a-request-object-into-a-stringifiable-object-in-javascript async function requestAsObject(_request) { if (!(_request instanceof Request)) { @@ -268,16 +278,6 @@ async function requestAsObject(_request) { let headers = Object.fromEntries(request.headers); let format; - function stringifiableObject(obj) { - const filtered = {}; - for (const key in obj) { - if (["boolean", "number", "string"].includes(typeof obj[key]) || obj[key] === null) { - filtered[key] = obj[key]; - } - } - return filtered; - } - if (contentType.includes("application/x-www-form-urlencoded")) { const formData = await request.formData(); const params = {}; @@ -305,12 +305,30 @@ async function requestAsObject(_request) { }; } +// based on https://stackoverflow.com/questions/57447685/how-can-i-convert-a-request-object-into-a-stringifiable-object-in-javascript +async function responseAsObject(response) { + if (!(response instanceof Response)) { + throw Object.assign(new Error(), { + name: "TypeError", + message: "Argument must be a Response object", + }); + } + response = response.clone(); + + return { + ...stringifiableObject(response), + headers: Object.fromEntries(response.headers), + body: await response.text(), + }; +} + export { checkResourceExists, mergeResponse, modelResource, normalizePathnameForWindows, requestAsObject, + responseAsObject, resolveForRelativeUrl, trackResourcesForRoute, transformKoaRequestIntoStandardRequest, diff --git a/packages/cli/src/plugins/renderer/plugin-renderer-default.js b/packages/cli/src/plugins/renderer/plugin-renderer-default.js index ff806f543..b7d2c7cdc 100644 --- a/packages/cli/src/plugins/renderer/plugin-renderer-default.js +++ b/packages/cli/src/plugins/renderer/plugin-renderer-default.js @@ -4,6 +4,7 @@ const greenwoodPluginRendererDefault = { provider: () => { return { executeModuleUrl: new URL("../../lib/execute-route-module.js", import.meta.url), + apiRouteWorkerUrl: new URL("../../lib/api-route-worker.js", import.meta.url), }; }, }; diff --git a/packages/cli/src/plugins/resource/plugin-api-routes.js b/packages/cli/src/plugins/resource/plugin-api-routes.js index 1d2414cef..4af6a5164 100644 --- a/packages/cli/src/plugins/resource/plugin-api-routes.js +++ b/packages/cli/src/plugins/resource/plugin-api-routes.js @@ -44,11 +44,13 @@ class ApiRoutesResource { : undefined; if (process.env.__GWD_COMMAND__ === "develop") { - const workerUrl = new URL("../../lib/api-route-worker.js", import.meta.url); + const apiRouterWorkerUrl = this.compilation.config.plugins + .find((plugin) => plugin.type === "renderer") + .provider().apiRouteWorkerUrl; const req = await requestAsObject(request); const response = await new Promise((resolve, reject) => { - const worker = new Worker(workerUrl); + const worker = new Worker(apiRouterWorkerUrl); worker.on("message", (result) => { resolve(result); diff --git a/packages/plugin-renderer-lit/README.md b/packages/plugin-renderer-lit/README.md index d4c1b8ba6..651e7da6c 100644 --- a/packages/plugin-renderer-lit/README.md +++ b/packages/plugin-renderer-lit/README.md @@ -4,6 +4,8 @@ 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 instead of Greenwood's default renderer (WCC). This plugin also gives the ability to statically [(pre) render entire pages and layouts](https://greenwoodjs.dev/docs/reference/rendering-strategies/#prerendering) to output completely static sites. For more information and complete docs on Greenwood, please visit [our website](https://www.greenwoodjs.dev). +You can see [this repo](https://github.com/thescientist13/greenwood-lit-ssr) for a full demo of an isomorphic Lit SSR project with SSR pages and API routes, deployed to Vercel. + > This package assumes you already have `@greenwood/cli` installed. ## Prerequisite @@ -70,6 +72,44 @@ Types should automatically be inferred through this package's exports map, but c import type { LitRendererPlugin } from '@greenwood/plugin-renderer-lit'; ``` +## API Routes + +If you're using CSS Module Scripts and [API Routes](https://greenwoodjs.dev/docs/pages/api-routes/), you will need to make sure to import [Lit's CSS Register Hook](https://github.com/lit/lit/tree/main/packages/labs/ssr-dom-shim#css-nodejs-customization-hook) at the top of your function handler. + +```js +// make sure to import this first! +import "@lit-labs/ssr-dom-shim/register-css-hook.js"; +import { render } from '@lit-labs/ssr'; +import { collectResult } from '@lit-labs/ssr/lib/render-result.js' +import { html } from 'lit'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; +import { getProducts } from '../../services/products.ts'; +import '../../components/card/card.ts'; + +export async function handler() { + const products = (await getProducts()); + const body = await collectResult(render(html` + ${ + unsafeHTML(products.map((item, idx) => { + const { title, thumbnail } = item; + + return ` + + `; + }).join('')) + } + `)); + + return new Response(body, { + headers: new Headers({ + 'Content-Type': 'text/html' + }) + }); +} +``` ## Caveats @@ -82,8 +122,6 @@ import type { LitRendererPlugin } from '@greenwood/plugin-renderer-lit'; _**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 `