Skip to content

ProjectEvergreen/greenwood-demo-adapter-netlify

Repository files navigation

greenwood-demo-adapter-netlify

Netlify Status

A demonstration repo for deploying a full-stack Greenwood app with Netlify static hosting and Serverless + Edge functions.

Setup

To run locally

  1. Clone the repo
  2. Run npm ci

You can now run these npm scripts

  • npm run dev - Start the demo with Greenwood local dev server
  • npm run serve - Start the demo with a production Greenwood build
  • npm run serve:netlify - Start the Netlify CLI server for testing production Greenwood builds locally (see caveats section in the plugin's README)

πŸ‘‰ Note: If deploying to your own Netlify instance, make sure you set the AWS_LAMBDA_JS_RUNTIME environment variable in your Netlify UI to the value of nodejs18.x.

Demo

This repo aims to demonstrate a couple of Greenwood's features (API Routes and SSR pages) leveraging Netlify's serverless and edge function capabilities, focused on using Web Components (WCC) and Web Standards to deliver the content for the demo.

Status

Feature Greenwood Serverless Edge
API Routes βœ… βœ… ❓
SSR Pages βœ… βœ… ❓

Serverless

The serverless demos include the following examples:

API Routes

  • βœ… /api/greeting?name{xxx} - An API that returns a JSON response and optionally uses the name query param for customization. Otherwise returns a default message.
  • βœ… /api/fragment - An API for returning fragments of server rendered Web Components as HTML, that are then appended to the DOM. The same card component used in SSR also runs on the client to provide interactivity, like event handling.

SSR Pages

  • βœ… /products/ - SSR page for rendering Greenwood pages.

Known Issues

All known issue resolved! Information left here for posterity

βœ… import.meta.url

Note: Solved by bypassing Netlify's bundling and just creating a zip file custom build output

Seeing this issue when creating an "idiomatic" example of a custom element using WCC's renderFromHTML because Netlify / esbuild does not support import.meta.url, though hopefully it is coming soon? πŸ₯Ί

Netlify invalid URL

import { renderFromHTML } from 'wc-compiler';
import { getArtists } from '../services/artists.js';

export async function handler(request) {
  const params = new URLSearchParams(request.url.slice(request.url.indexOf('?')));
  const offset = params.has('offset') ? parseInt(params.get('offset'), 10) : null;
  const headers = new Headers();
  const artists = await getArtists(offset);
  const { html } = await renderFromHTML(`
    ${
      artists.map((item, idx) => {
        const { name, imageUrl } = item;

        return `
          <app-card
            title="${offset + idx + 1}) ${name}"
            thumbnail="${imageUrl}"
          ></app-card>
        `;
      }).join('')
    }
  `, [
    new URL('../components/card.js', import.meta.url)
  ]);

  headers.append('Content-Type', 'text/html');

  return new Response(html, {
    headers
  });
}

The above would be the ideal implementation, so instead have to do something more "manual" for now.

import '../../node_modules/wc-compiler/src/dom-shim.js';
import { getArtists } from '../services/artists.js';
import Card from '../components/card.manual.js';

export async function handler(request) {
  const params = new URLSearchParams(request.url.slice(request.url.indexOf('?')));
  const offset = params.has('offset') ? parseInt(params.get('offset'), 10) : null;
  const headers = new Headers();
  const artists = await getArtists(offset);
  const card = new Card();
  
  card.connectedCallback();

  const html = artists.map((artist) => {
    const { name, imageUrl } = artist;
    return `
      <app-card-manual>
        ${card.getInnerHTML({ includeShadowRoots: true })}

        <h2 slot="title">${name}</h2>
        <img slot="thumbnail" src="${imageUrl}" alt="${name}"/>
      </app-card-manual>
    `;
  }).join('');

  headers.append('Content-Type', 'text/html');

  return new Response(html, {
    headers
  });
}

βœ… ERR_REQUIRE_ESM

Note: Solved by pointing to Greenwood's bundled output in the public/ directory

So although this runs fine locally for /api/fragment-manual, when run on Netlify, the ERR_REQUIRE_ESM message is seen.

Netlify ERR_REQUIRE_ESM

Edge

TODO

API Routes

TODO

SSR page

TODO

Adapter Implementation Thoughts / Questions

  1. Do we even need workers for build output?
    • if not, how to make a generic solution? (make a pure executeModule function and run the worker ourselves when needed in dev mode?)
  2. Will need to generate the .netlify/functions folder on-demand / as part of the build instead of hardcoding, likely from manifest.json
  3. For SSR pages, manual is the only option? That will impact how pages can be built, e.g. manual card, shadow dom, etc and might to be configurable based on if the platform supports import.meta.url or not it seems.
  4. How to best manage local dev (runtime "compliance") - mixed support, see caveats section of the plugin's README
    • proxy netlify cli dev option?
    • should use src/ or public/? depends on dev vs production mode? Interestingly, the manual way only worked deployed when using public/
    • if esbuild worked w/ import.meta.url, we could probably ship unzipped bundles, and then dev would also work?
  5. Need to provide custom netlify.toml?
  6. Make sure to spread all headers / response properties in netlify functions adapter output
  7. SSR pages are bundling into public/api/ directory ?
  8. Keep it as an experimental feature for 1.0 (or per platform?)