Skip to content

Latest commit

 

History

History
168 lines (136 loc) · 5.35 KB

File metadata and controls

168 lines (136 loc) · 5.35 KB
imports
../components/latest-post/latest-post.js data-gwd-opt="static" type="module"
../components/hero-banner/hero-banner.js data-gwd-opt="static" type="module"
../components/capabilities/capabilities.js type="module"
../components/why-greenwood/why-greenwood.js data-gwd-opt="static" type="module"
../components/run-anywhere/run-anywhere.js data-gwd-opt="static" type="module"
../components/build-with-friends/build-with-friends.js data-gwd-opt="static" type="module"
../components/get-started/get-started.js data-gwd-opt="static" type="module"
../styles/home.css

Hybrid Routing html.svg

Greenwood is HTML first by design. Start from just an index.html file or leverage hybrid, file-system based routing to easily achieve static and dynamic pages side-by-side. Single Page Applications (SPA) and pre-rendering also supported.

src/
  pages/
    api/
      search.js       # API route
    index.html        # Static (SSG)
    products.js       # Dynamic (SSR)
    about.md          # markdown also supported
Server Rendering build-ssg.svg

Web Components are not only a great component model, but also a great templating model for generating static HTML. Below is a dynamic page in Greenwood powered by the Custom Elements API and server-rendering an imported custom element.

// src/pages/products.js
import { getProducts } from "../lib/db.js";
import "../components/card.js";

export default class ProductsPage extends HTMLElement {
  async connectedCallback() {
    const products = await getProducts();
    const html = products
      .map((product) => {
        const { title, thumbnail } = product;

        return `
          <app-card
            title="${title}"
            thumbnail="${thumbnail}"
          >
          </app-card>
        `;
      })
      .join("");

    this.innerHTML = `
      <h2>Product Catalog</h2>
      <div>${html}</div>
    `;
  }
}
Web Components web-components.svg

Greenwood makes it possible to author real isomorphic Web Components, using Light or Shadow DOM, re-using that same definition across the client and the server. Combined with Web APIs like Constructable Stylesheets and Import Attributes, Web Components make for a compelling solution as the web's own component model.

// src/components/card.js
import themeSheet from "../styles/theme.css" with { type: "css" };
import cardSheet from "./card.css" with { type: "css" };

export default class Card extends HTMLElement {
  connectedCallback() {
    if (!this.shadowRoot) {
      const thumbnail = this.getAttribute("thumbnail");
      const title = this.getAttribute("title");
      const template = document.createElement("template");

      template.innerHTML = `
        <div>
          <h3>${title}</h3>
          <img src="${thumbnail}" alt="${title}">
        </div>
      `;

      this.attachShadow({ mode: "open" });
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }

    this.shadowRoot.adoptedStyleSheets = [themeSheet, cardSheet];
  }
}

customElements.define("app-card", Card);
API Routes json.svg

Need client side data fetching or mutations? Greenwood provides API routes out of the box that are fully invested in web standards like Fetch and FormData. Of course it is all fully compatible with server-rendering Web Components; a perfect companion for HTML over the wire solutions!

// src/pages/api/search.js
import { renderFromHTML } from "wc-compiler";
import { getProducts } from "../lib/db.js";

export async function handler(request) {
  const formData = await request.formData();
  const searchTerm = formData.has("term") ? formData.get("term") : "";
  const products = await getProducts(searchTerm);
  const { html } = await renderFromHTML(
    `
    ${products
      .map((item, idx) => {
        const { title, thumbnail } = item;

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

  return new Response(html, {
    headers: new Headers({
      "Content-Type": "text/html",
    }),
  });
}