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
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>
`;
}
}
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);
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",
}),
});
}
Build With Friends
Greenwood and Web Components work great with all of your favorite tools in the web ecosystem.
Why Greenwood?
With the web now reaching an impressive ubiquity of full-stack APIs, Greenwood's vision is to be a thin layer of developer experience on top of the web platform.
Less is More
We believe a tech stack aligned to web standards can benefit from needing fewer dependencies and toolchains, lowered design and decision fatigue, and reduced layers of abstraction. Magic is a zero interest rate phenomenon.
Ship Your Source
We strive to ensure that you can ship exactly the code you wrote and, combined with a fast, unbundled, local development experience, Greenwood will have you shipping in no time. Greenwood aims to externalize the framework.
Build to the Future
Having benefited so much from the web, we feel compelled and motivated to participate in its future growth and community groups, to help fill in the gaps so the web can be an even better platform out of the box for everyone.
Run anywhere the web can run
Greenwood helps you take your application to production by embracing platforms that embrace web standards.