From 5415d0076aefe86f3c4b2c177d60dcfdc22fe6e3 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Wed, 6 Nov 2024 19:38:48 -0500 Subject: [PATCH] content/issue 75 final content and site review (#127) --- CONTRIBUTING.md | 11 ++ link-checker.js | 65 ++++++++ package-lock.json | 143 ++++++++++++++++-- package.json | 2 + .../edit-on-github/edit-on-github.module.css | 11 +- src/components/run-anywhere/run-anywhere.js | 2 +- .../table-of-contents.module.css | 4 +- src/pages/blog/release/v0-27-0.md | 2 +- src/pages/blog/state-of-greenwood-2022.md | 2 +- .../content-as-data/active-frontmatter.md | 20 +-- src/pages/docs/content-as-data/collections.md | 17 ++- src/pages/docs/content-as-data/data-client.md | 6 +- src/pages/docs/content-as-data/index.md | 4 +- src/pages/docs/content-as-data/pages-data.md | 4 +- src/pages/docs/index.md | 8 +- src/pages/docs/introduction/about.md | 4 +- src/pages/docs/introduction/index.md | 4 +- src/pages/docs/introduction/setup.md | 2 +- src/pages/docs/introduction/web-standards.md | 43 ++++-- src/pages/docs/pages/api-routes.md | 13 +- src/pages/docs/pages/index.md | 2 +- src/pages/docs/pages/layouts.md | 6 +- src/pages/docs/pages/routing.md | 8 +- src/pages/docs/pages/server-rendering.md | 22 +-- src/pages/docs/plugins/css-modules.md | 4 +- src/pages/docs/plugins/index.md | 6 +- src/pages/docs/plugins/lit-ssr.md | 4 +- src/pages/docs/plugins/typescript.md | 2 +- src/pages/docs/reference/appendix.md | 26 +++- src/pages/docs/reference/configuration.md | 32 ++-- src/pages/docs/reference/plugins-api.md | 56 +++---- .../docs/reference/rendering-strategies.md | 24 +-- src/pages/docs/resources/assets.md | 8 +- src/pages/docs/resources/index.md | 2 +- src/pages/docs/resources/markdown.md | 4 +- src/pages/docs/resources/scripts.md | 9 +- src/pages/guides/ecosystem/storybook.md | 2 +- src/pages/guides/ecosystem/tailwind.md | 2 +- src/pages/guides/ecosystem/web-test-runner.md | 4 +- .../guides/getting-started/going-further.md | 4 +- .../guides/getting-started/key-concepts.md | 8 +- .../guides/getting-started/walkthrough.md | 4 +- src/pages/guides/hosting/aws.md | 2 +- src/pages/guides/hosting/github.md | 8 +- .../tutorials/full-stack-web-components.md | 4 +- src/pages/guides/tutorials/theme-packs.md | 11 +- src/styles/docs.css | 8 +- src/styles/theme.css | 2 +- 48 files changed, 433 insertions(+), 208 deletions(-) create mode 100644 link-checker.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ea50fb26..7233926d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,6 +35,17 @@ Where `issue-xxx` is the corresponding issue in the GreenwoodJS project. General changes to the website can be made by submitting a PR directly to the main branch. This includes typos, style changes, and general enhancements to the website as a whole. +### Link Checker + +There is a **npm** script that you can run that will check all relative links and hashes (except for blog pages) to check that links aren't broken. Running the command will build the site for production automatically and generate a report. + +```sh +$ npm run lint:links +#... + +✅ all links checked successfully and no broken links found +``` + ## Development ### Styling diff --git a/link-checker.js b/link-checker.js new file mode 100644 index 00000000..e5f9cd09 --- /dev/null +++ b/link-checker.js @@ -0,0 +1,65 @@ +import graph from "./public/graph.json" with { type: "json" }; +import { parse } from "node-html-parser"; +import fs from "fs/promises"; + +const report = {}; + +for (const page of graph) { + const { outputHref, route } = page; + const html = await fs.readFile(new URL(outputHref), "utf-8"); + const root = parse(html); + const links = root.querySelectorAll("a"); + + links.forEach((link) => { + if (!route.startsWith("/blog/") && link.getAttribute("href").startsWith("/")) { + const linkUrl = new URL(`https://www.greenwoodjs.dev${link.getAttribute("href")}`); + const { pathname, hash } = linkUrl; + const matchingRoute = graph.find((page) => page.route === pathname); + + if (!matchingRoute) { + if (!report[route]) { + report[route] = { + violations: [], + }; + } + + report[route].violations.push({ + link: pathname, + }); + } + + if (matchingRoute && hash !== "") { + const { tableOfContents } = matchingRoute.data; + const match = tableOfContents.find((toc) => toc.slug === hash.replace("#", "")); + + if (!match) { + if (!report[route]) { + report[route] = { + violations: [], + }; + } + + report[route].violations.push({ + hash, + }); + } + } + } + }); +} + +if (Object.keys(report).length === 0) { + console.log("✅ all links checked successfully and no broken links found"); +} else { + for (const r of Object.keys(report)) { + console.log("---------------------------------"); + console.log(`🚨 reporting violations for route ${r}...`); + report[r].violations.forEach((violation, idx) => { + if (violation.link) { + console.error(`${idx + 1}) Could not find matching route for href => ${violation.link}`); + } else if (violation.hash) { + console.error(`${idx + 1}) Could not find matching heading for hash => ${violation.hash}`); + } + }); + } +} diff --git a/package-lock.json b/package-lock.json index aea0b64a..e87b4e21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "husky": "^9.0.11", "lint-staged": "^15.2.2", "lit": "^3.1.2", + "node-html-parser": "^6.1.13", "prettier": "^3.2.5", "rehype-autolink-headings": "^4.0.0", "rehype-slug": "^3.0.0", @@ -2363,6 +2364,15 @@ "integrity": "sha512-qq7C3EtK3yJXMwz1zAab65pjl+UhohqMOctTgcqjLOWABqmwj+me02LSsCuEUxnst9X1lCBpoE0WArGKgdGDzw==", "dev": true }, + "node_modules/@greenwood/cli/node_modules/node-html-parser": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.4.9.tgz", + "integrity": "sha512-UVcirFD1Bn0O+TSmloHeHqZZCxHjvtIeGdVdGMhyZ8/PWlEiZaZ5iJzR189yKZr8p0FXN58BUeC7RHRkf/KYGw==", + "dev": true, + "dependencies": { + "he": "1.2.0" + } + }, "node_modules/@greenwood/plugin-css-modules": { "version": "0.30.0-alpha.7", "resolved": "https://registry.npmjs.org/@greenwood/plugin-css-modules/-/plugin-css-modules-0.30.0-alpha.7.tgz", @@ -2399,6 +2409,15 @@ "integrity": "sha512-qq7C3EtK3yJXMwz1zAab65pjl+UhohqMOctTgcqjLOWABqmwj+me02LSsCuEUxnst9X1lCBpoE0WArGKgdGDzw==", "dev": true }, + "node_modules/@greenwood/plugin-css-modules/node_modules/node-html-parser": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.4.9.tgz", + "integrity": "sha512-UVcirFD1Bn0O+TSmloHeHqZZCxHjvtIeGdVdGMhyZ8/PWlEiZaZ5iJzR189yKZr8p0FXN58BUeC7RHRkf/KYGw==", + "dev": true, + "dependencies": { + "he": "1.2.0" + } + }, "node_modules/@greenwood/plugin-import-raw": { "version": "0.30.0-alpha.7", "resolved": "https://registry.npmjs.org/@greenwood/plugin-import-raw/-/plugin-import-raw-0.30.0-alpha.7.tgz", @@ -6173,6 +6192,12 @@ "node": ">= 0.8" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, "node_modules/bplist-parser": { "version": "0.2.0", "dev": true, @@ -7272,16 +7297,32 @@ "node": ">=12 || >=16" } }, - "node_modules/css-tree": { - "version": "2.3.1", + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", "dev": true, - "license": "MIT", "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, "node_modules/css.escape": { @@ -7665,6 +7706,61 @@ "dev": true, "license": "MIT" }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.4.5", "dev": true, @@ -7745,6 +7841,18 @@ "once": "^1.4.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "dev": true, @@ -12284,11 +12392,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdn-data": { - "version": "2.0.30", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/mdurl": { "version": "1.0.1", "dev": true, @@ -13163,10 +13266,12 @@ "license": "MIT" }, "node_modules/node-html-parser": { - "version": "1.4.9", + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", + "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==", "dev": true, - "license": "MIT", "dependencies": { + "css-select": "^5.1.0", "he": "1.2.0" } }, @@ -13213,6 +13318,18 @@ "node": ">=8" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nypm": { "version": "0.3.8", "dev": true, diff --git a/package.json b/package.json index 27a4a072..bb684b6d 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "lint:ls": "ls-lint", "lint:js": "eslint", "lint:css": "stylelint \"./src/**/*.css\"", + "lint:links": "npm run build && node ./link-checker.js", "format": "prettier . --write", "format:check": "prettier . --check", "prepare": "husky install" @@ -67,6 +68,7 @@ "husky": "^9.0.11", "lint-staged": "^15.2.2", "lit": "^3.1.2", + "node-html-parser": "^6.1.13", "prettier": "^3.2.5", "rehype-autolink-headings": "^4.0.0", "rehype-slug": "^3.0.0", diff --git a/src/components/edit-on-github/edit-on-github.module.css b/src/components/edit-on-github/edit-on-github.module.css index 22c8ce15..8e1cefdd 100644 --- a/src/components/edit-on-github/edit-on-github.module.css +++ b/src/components/edit-on-github/edit-on-github.module.css @@ -7,7 +7,7 @@ .container a:active, .container a:focus, .container a:visited { - padding: calc(var(--size-1) + var(--size-2)); + padding: calc(var(--size-1) + var(--size-1)); border-radius: var(--size-px-2); background-color: var(--color-prism-bg); color: var(--color-accent); @@ -18,3 +18,12 @@ .container a:hover { text-decoration: underline; } + +@media (min-width: 1200px) { + .container a, + .container a:active, + .container a:focus, + .container a:visited { + padding: calc(var(--size-1) + var(--size-2)); + } +} diff --git a/src/components/run-anywhere/run-anywhere.js b/src/components/run-anywhere/run-anywhere.js index 0ebef917..55588689 100644 --- a/src/components/run-anywhere/run-anywhere.js +++ b/src/components/run-anywhere/run-anywhere.js @@ -17,7 +17,7 @@ export default class RunAnywhere extends HTMLElement { this.innerHTML = `

Run anywhere the web can run

-

Greenwood helps you take your application to production by embracing platforms that embrace web standards.

+

Greenwood helps you take your application to production by embracing platforms that embrace web standards.

${platforms diff --git a/src/components/table-of-contents/table-of-contents.module.css b/src/components/table-of-contents/table-of-contents.module.css index fc28b01c..57fc5467 100644 --- a/src/components/table-of-contents/table-of-contents.module.css +++ b/src/components/table-of-contents/table-of-contents.module.css @@ -23,10 +23,10 @@ .compactMenuPopover { top: 150px; width: auto; - min-height: 150px; + min-height: 250px; margin: 0 var(--size-2); border: 1px solid var(--color-gray); - box-shadow: var(--shadow-2); + box-shadow: var(--shadow-4); padding: var(--size-2); } diff --git a/src/pages/blog/release/v0-27-0.md b/src/pages/blog/release/v0-27-0.md index 39e07d27..a40af8ae 100644 --- a/src/pages/blog/release/v0-27-0.md +++ b/src/pages/blog/release/v0-27-0.md @@ -72,7 +72,7 @@ So that is what we did! From this release forward, all CSS minification and bund ### Build Capacity -The last highlight we would like to feature from this release was the introduction of thread pooling for static builds that rely on SSR based page generation, like when using the [`prerender` configuration option](/docs/configuration/#prerender). In adopting this [SSG benchmark](https://github.com/thescientist13/bench-framework-markdown), it was clear that without some work, Greenwood would not be able to build thousands of pages in this way, let alone quickly. +The last highlight we would like to feature from this release was the introduction of thread pooling for static builds that rely on SSR based page generation, like when using the [`prerender` configuration option](/docs/reference/configuration/#prerender). In adopting this [SSG benchmark](https://github.com/thescientist13/bench-framework-markdown), it was clear that without some work, Greenwood would not be able to build thousands of pages in this way, let alone quickly. So under the hood, Greenwood now introduces thread pooling to avoid crashing NodeJS through the spawning of too many Worker threads, based on our [_Getting Started_ repo](https://github.com/ProjectEvergreen/greenwood-getting-started). While it might not be the fastest, at least Greenwood will now be able handle the [thousands of pages](https://github.com/thescientist13/bench-framework-markdown) you may throw at it! 😅 diff --git a/src/pages/blog/state-of-greenwood-2022.md b/src/pages/blog/state-of-greenwood-2022.md index b12e91d2..56822afb 100644 --- a/src/pages/blog/state-of-greenwood-2022.md +++ b/src/pages/blog/state-of-greenwood-2022.md @@ -154,7 +154,7 @@ And Greenwood will statically generate this ### Interpolate Frontmatter -When setting the [`interpolateFrontmatter`](/docs/configuration/#interpolate-frontmatter) flag in your _greenwood.config.js_, frontmatter in your markdown will be available in your HTML or markdown similar to how variable interpolation works in JavaScript. Great for `` tags! +When setting the [`interpolateFrontmatter`](/docs/reference/configuration/#interpolate-frontmatter) flag in your _greenwood.config.js_, frontmatter in your markdown will be available in your HTML or markdown similar to how variable interpolation works in JavaScript. Great for `` tags! #### How It Works diff --git a/src/pages/docs/content-as-data/active-frontmatter.md b/src/pages/docs/content-as-data/active-frontmatter.md index 7c3ea8f7..c768577a 100644 --- a/src/pages/docs/content-as-data/active-frontmatter.md +++ b/src/pages/docs/content-as-data/active-frontmatter.md @@ -12,23 +12,7 @@ Really useful for passing page content or collections as attributes to a custom ## Usage -Say for instance we want to - -```html - - - - Home Page - - - - - - - -``` - -Or given some frontmatter in a markdown file: +Given some frontmatter in a markdown file: ```md --- @@ -72,7 +56,7 @@ Or HTML: ## Data Client -You can also access this content using our data client. +You can also access this content using our data client: ```js import { getContentByCollection } from "@greenwood/cli/src/data/client.js"; diff --git a/src/pages/docs/content-as-data/collections.md b/src/pages/docs/content-as-data/collections.md index 321b9513..d8083a1f 100644 --- a/src/pages/docs/content-as-data/collections.md +++ b/src/pages/docs/content-as-data/collections.md @@ -6,24 +6,24 @@ tocHeading: 2 # Collections -Collections are a feature in Greenwood by which you can use [frontmatter](/docs/resources/markdown#frontmatter) to group pages that can the be referenced through JavaScript or [`activeFrontmatter`](/docs/configuration/#active-frontmatter). +Collections are a feature in Greenwood by which you can use [frontmatter](/docs/resources/markdown/#frontmatter) to group pages that can then be referenced through [JavaScript](/docs/content-as-data/data-client/) or [active frontmatter](/docs/content-as-data/active-frontmatter/). This can be a useful way to group pages for things like navigation menus based on the content in your pages directory. ## Usage -To define a collections, just add a **collection** property to the frontmatter of any static file: +To define a collection, just add a **collection** property to the frontmatter of any static file: ```md --- -collection: nav +collection: navigation order: 2 --- # About Page ``` -You can now a reference to that collection either in HTML using [`activeFrontmatter`](/docs/content-as-data/active-frontmatter/): +You can now a reference to that collection either in HTML using [**activeFrontmatter**](/docs/content-as-data/active-frontmatter/): ```html @@ -33,7 +33,7 @@ You can now a reference to that collection either in HTML using [`activeFrontmat - + ``` @@ -41,12 +41,13 @@ You can now a reference to that collection either in HTML using [`activeFrontmat Or programmatically in your JavaScript using our [**Data Client**](/docs/content-as-data/data-client/): ```js +// src/components/navigation.js import { getContentByCollection } from "@greenwood/cli/src/data/client.js"; -export default class Nav extends HTMLElement { +export default class Navigation extends HTMLElement { async connectedCallback() { // sort based on frontmatter order set in your markdown - const navItems = (await getContentByCollection("nav")).sort((a, b) => + const navItems = (await getContentByCollection("navigation")).sort((a, b) => a.data.order > b.data.order ? 1 : -1, ); @@ -68,5 +69,5 @@ export default class Nav extends HTMLElement { } } -customElements.define("x-nav", Nav); +customElements.define("x-navigation", Navigation); ``` diff --git a/src/pages/docs/content-as-data/data-client.md b/src/pages/docs/content-as-data/data-client.md index b3c6050a..fcdd69c8 100644 --- a/src/pages/docs/content-as-data/data-client.md +++ b/src/pages/docs/content-as-data/data-client.md @@ -6,15 +6,15 @@ tocHeading: 2 # Data Client -To access your content as data with Greenwood, there are three pre-made APIs you can use, based on your use case. These are isomorphic in that they will consume live data during development, and statically build out each query at build time to its own JSON file that can be fetched client side independently. +To access your content as data with Greenwood, there are three pre-made APIs you can use, based on your use case. These are isomorphic in that they will consume live data during development, and statically build out each query at build time to its own JSON file that can be fetched client side. This way, you can serialize and / or hydrate from this data as needed based on your application's needs. -> These features works best when used for build time templating combining our [**prerender**](/docs/configuration/#prerender) and [**static** optimization](/docs/configuration/#optimization) configurations. +> These features works best when used for build time templating combining our [**prerender**](/docs/reference/configuration/#prerender) and [**static** optimization](/docs/reference/configuration/#optimization) configurations. ## Content -To get every page back in one array, simple call `getContent` +To get every page back in one array, simple call `getContent`: ```js // get turn the entire set of pages as an array diff --git a/src/pages/docs/content-as-data/index.md b/src/pages/docs/content-as-data/index.md index fc532ac3..6495141c 100644 --- a/src/pages/docs/content-as-data/index.md +++ b/src/pages/docs/content-as-data/index.md @@ -20,6 +20,6 @@ But what happens over time, when that list grows to 10, 50, 100+ posts? Imagine To assist with this, Greenwood provides a set of "content as data" capabilities on the left sidebar you can take advantage of. -> First thing though, make sure you've set the [`activeContent`](/docs/configuration/#active-content) flag to `true` in your _greenwood.config.js_. +> First thing though, make sure you've set the [`activeContent`](/docs/reference/configuration/#active-content) flag to `true` in your _greenwood.config.js_. > -> These features works best when used for build time templating combining our [**prerender**](/docs/configuration/#prerender) and [**static** optimization](/docs/configuration/#optimization) configurations. +> These features works best when used for build time templating combining our [**prerender**](/docs/reference/configuration/#prerender) and [**static** optimization](/docs/reference/configuration/#optimization) configurations. diff --git a/src/pages/docs/content-as-data/pages-data.md b/src/pages/docs/content-as-data/pages-data.md index d6f5a56a..f6bf2255 100644 --- a/src/pages/docs/content-as-data/pages-data.md +++ b/src/pages/docs/content-as-data/pages-data.md @@ -16,7 +16,7 @@ Each page will return data in the following schema: - **title** (customizable) - inferred title based on the filename - **label** (customizable) - inferred from the **title** if not configured - **route** - the filename converted into a path as per file based routing -- **data** (customizable) - any custom frontmatter keys you've added to your page +- **data** (customizable) - any custom frontmatter keys you've defined for your page So for a page at _src/pages/blog/first-post.md_, this is the data you would get back: @@ -46,7 +46,7 @@ This is my first post. ## Table of Contents -Additionally for markdown pages, you can add a frontmatter property called `tocHeading` that will read all the HTML heading tags that match that number, and provide a subset of data, useful for generated a table of contents. +Additionally for markdown pages, you can add a frontmatter property called `tocHeading` that will read all the HTML heading tags that match that number, and provide that as a subset of the data object. This is most useful for generating the table of contents for a page. Taking our previous example, if we were to configure this for `

` tags: diff --git a/src/pages/docs/index.md b/src/pages/docs/index.md index 918e82f0..d8cf162d 100644 --- a/src/pages/docs/index.md +++ b/src/pages/docs/index.md @@ -6,14 +6,14 @@ order: 1 --- -

Welcome to our docs, we're excited to help you get the most out of Greenwood and the web!

+

Welcome to our documentation, we're excited to help you get the most out of Greenwood and the web!

The content is broken down across these sections: -- [Introduction](/docs/introduction/) - An intro to Greenwood, its philosophy, and how it install it -- [Pages](/docs/hosting/) - Learn about file-based routing and how to leverage SSR pages, API routes, and more +- [Introduction](/docs/introduction/) - An intro to Greenwood, its philosophy, and how to install it +- [Pages](/docs/pages/) - Learn about file-based routing and how to leverage SSR pages, API routes, and more - [Resources](/docs/resources/) - Scripts? Styles? Fonts and images? This section has you covered - [Plugins](/docs/plugins/) - Check out all plugins created by the Greenwood team - [Content as Data](/docs/content-as-data/) - See Greenwood's capabilities for leveraging your content programmatically -- [Reference](/docs/reference/) - Configuration options, Plugin API docs, and other useful content for help you to build your project +- [Reference](/docs/reference/) - Configuration options, Plugin API docs, and other useful content for helping you extend Greenwood diff --git a/src/pages/docs/introduction/about.md b/src/pages/docs/introduction/about.md index 51edc839..12f96b40 100644 --- a/src/pages/docs/introduction/about.md +++ b/src/pages/docs/introduction/about.md @@ -14,7 +14,7 @@ Greenwood's goal is to bring the power of the modern web right to your fingertip Some of Greenwood's feature include: -- Unbundled local development workflow, using `E-Tags` headers for efficient caching and live reloads +- Unbundled local development workflow, using `ETag` headers for efficient caching and live reloads - Out of the box support for ESM and Web APIs, on both the client and server - Server Side Rendering of Web Components (Light and Shadow DOM) - File-based routing, including API Routes @@ -28,7 +28,7 @@ Greenwood aims to be a low point of friction as part of a general purpose web de > We would be remiss if we didn't call out what are probably some obvious influences; in particular **Next.js**, **SvelteKit**, and of course, **Gatsby**. We are in debt to amazing tools like **Rollup**, **parse5**, and **acorn**. 🙇 > -> The JavaScript ecosystem is great, and we're very happy to be a part of it. 💚 +> The JavaScript ecosystem is amazing, and we're very happy to be a part of it. 💚 ## Goals diff --git a/src/pages/docs/introduction/index.md b/src/pages/docs/introduction/index.md index 654400c5..64bd2101 100644 --- a/src/pages/docs/introduction/index.md +++ b/src/pages/docs/introduction/index.md @@ -4,11 +4,11 @@ order: 1 --- -

As enthusiasts of the web, we hope to provide an authentic, hands-on, and unbiased option for web development, inspired by the web itself. Whether you roll your own or build with friends, we can all win when we bet on the web.

+

As enthusiasts of the web, we hope Greenwood can provide an authentic, predictable, and pragmatic experience for web development, inspired by the web itself. Whether you prefer to roll your own or want build with friends, we can all win together when we bet on the web.

This introductory section is great for first time users of Greenwood, and covers the following topics: - [About](/docs/introduction/about/) - Learn about the project and its motivations and goals - [Setup](/docs/introduction/setup/) - Get your first Greenwood project started with a single command -- [Web Standards](/docs/introduction/web-standards/) - Web APIs promoted by Greenwood and featured throughout the docs +- [Web Standards](/docs/introduction/web-standards/) - Web APIs promoted by Greenwood and featured throughout the docs and guides diff --git a/src/pages/docs/introduction/setup.md b/src/pages/docs/introduction/setup.md index 819a9276..1ef355ae 100644 --- a/src/pages/docs/introduction/setup.md +++ b/src/pages/docs/introduction/setup.md @@ -10,7 +10,7 @@ Greenwood has a few options for getting a new project started. You can also chec ## Init -The recommended way to start a new Greenwood project, our **init** CLI will scaffold out a starter project for you. Just run a single command, and then just follow the prompts. +The recommended way to start a new Greenwood project, our **init** CLI will scaffold out a starter project for you. Just run a single command and then follow the prompts. To scaffold into the _current_ directory, run: diff --git a/src/pages/docs/introduction/web-standards.md b/src/pages/docs/introduction/web-standards.md index 7ab9c554..93b60d87 100644 --- a/src/pages/docs/introduction/web-standards.md +++ b/src/pages/docs/introduction/web-standards.md @@ -6,7 +6,7 @@ tocHeading: 2 # Web Standards -Throughout our docs we make heavy use of, and reference, some of the following Web APIs, either indirectly or as part of the core surface area of Greenwood itself. This section is an general introduction to them with relevant links and resources. +Throughout our docs we make heavy use of, and reference to, some of the following Web APIs, either indirectly or as part of the core surface area of Greenwood itself. This section is a general introduction to them with relevant links and resources. ## Import Attributes @@ -42,10 +42,13 @@ A simple example putting it all together might look like this: ```js import sheet from "./card.css" with { type: "css" }; +// create a template element +// to be populated with dynamic HTML const template = document.createElement("template"); export default class Card extends HTMLElement { connectedCallback() { + // this block can be SSR'd and thus wont need to be re-run on the client if (!this.shadowRoot) { const thumbnail = this.getAttribute("thumbnail"); const title = this.getAttribute("title"); @@ -57,34 +60,43 @@ export default class Card extends HTMLElement {

`; + // attach our template to our Shadow root this.attachShadow({ mode: "open" }); this.shadowRoot.appendChild(template.content.cloneNode(true)); } + // adopt our CSS Module Script this.shadowRoot.adoptedStyleSheets = [sheet]; } } +// defining the HTML tag that will invoke this definition customElements.define("x-card", Card); ``` +And would be used like this: + +```html + +``` + > Greenwood promotes Web Components not only as a great way to add sprinkles of JavaScript to an otherwise static site, but also for [static templating through prerendering](docs/reference/rendering-strategies/#prerendering) with all the power and expressiveness of JavaScript as well as completely [full-stack web components](/guides/tutorials/full-stack-web-components/). ## Fetch (and Friends) -[**Fetch**](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is a web standard for making HTTP requests and is supported both on the client and the server. It also bring along "companion" APIs like [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), and [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers). +[**Fetch**](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is a web standard for making HTTP requests and is supported both on the client and the server. It also brings along "companion" APIs like [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response), and [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers). -This suite of APIs is featured prominently in our API Route handlers: +This suite of APIs is featured prominently in our API Route examples: ```js +// a standard request object is passed in +// and a standard response object should be returned export async function handler(request) { - const params = new URLSearchParams(request.url.slice(request.url.indexOf("?"))); - const name = params.has("name") ? params.get("name") : "World"; - const body = { message: `Hello ${name}! 👋` }; + console.log("endpoint visited", request.url); - return new Response(JSON.stringify(body), { + return new Response("...", { headers: new Headers({ - "Content-Type": "application/json", + /* ... */ }), }); } @@ -92,9 +104,7 @@ export async function handler(request) { ## Import Maps -During local development, Greenwood loads all assets from your browser unbundled, serving the content right off disk, or through any additional plugins defined for the project in a _greenwood.config.js_. Combined with live reloading and `E-Tag` cache tag headers, [**import maps**](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) allow bare specifiers typically found when referencing packages from npm, to work natively in the browser without having to load all of _node_modules_ up front. All pages and assets are only requested on load. - -When installing a package as a `dependency` in your _package.json_, Greenwood will walk your dependencies and all their transitive dependencies, to build up a map to be injected in the `` of your HTML. +During local development, Greenwood loads all assets from your browser unbundled, serving the content right off disk. [**Import maps**](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) allow bare specifiers typically found when referencing packages from npm, to work natively in the browser. When installing a package as a **dependency** in your _package.json_, Greenwood will walk your dependencies and all their dependencies, to build up a map to be injected into the `` of your HTML. This is a sample of an import map that would be generated after having installed the **lit** package: @@ -122,16 +132,19 @@ This is a sample of an import map that would be generated after having installed The [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) constructor provides an elegant way for referencing [static assets](/docs/resources/assets/) on the client and on the server, and it works great when combined with [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) for easily interacting with search params in a request. -Here is an example of some of these APIs in action in an API Route handler: +Below is an example used in an API Route handler: ```js export async function handler(request) { const params = new URLSearchParams(request.url.slice(request.url.indexOf("?"))); const name = params.has("name") ? params.get("name") : "World"; + const msg = `Hello, ${name}! `; - console.log({ name }); - - // ... + return new Response(JSON.stringify({ msg }), { + headers: new Headers({ + "Content-Type": "application/json", + }), + }); } ``` diff --git a/src/pages/docs/pages/api-routes.md b/src/pages/docs/pages/api-routes.md index 2e8c30be..9b9e6373 100644 --- a/src/pages/docs/pages/api-routes.md +++ b/src/pages/docs/pages/api-routes.md @@ -8,7 +8,7 @@ tocHeading: 2 # API Routes -Greenwood has support for API routes, which are just functions that run on the server, and take in a [**Request**](https://developer.mozilla.org/en-US/docs/Web/API/Request) and return a [**Response**](https://developer.mozilla.org/en-US/docs/Web/API/Response). Each API route must export an `async` function called **handler**. +Greenwood has support for API routes, which are just functions that run on the server, and take in a [**Request**](https://developer.mozilla.org/en-US/docs/Web/API/Request) and return a [**Response**](https://developer.mozilla.org/en-US/docs/Web/API/Response). Each API route must export an async function called **handler**. ## Usage @@ -47,7 +47,7 @@ Inspired by [**Doug Parker's**](https://blog.dwac.dev/) blog post [_A Simpler HT An example of rendering a "card" component in an API Route might look like look this: ```js -// component/card.js +// src/component/card.js export default class Card extends HTMLElement { connectedCallback() { if (!this.shadowRoot) { @@ -74,7 +74,10 @@ export default class Card extends HTMLElement { customElements.define("x-card", Card); ``` +And here is it being used in an API Route handler: + ```js +// src/pages/api/search.js import { renderFromHTML } from "wc-compiler"; import { getProducts } from "../../db/products.js"; @@ -98,7 +101,7 @@ export async function handler(request) { }) .join("")} `, - [new URL("../path/to/card.js", import.meta.url)], + [new URL("../../components/card.js", import.meta.url)], ); return new Response(html, { @@ -113,10 +116,10 @@ export async function handler(request) { ## Isolation Mode -To execute an API route in its own isolated request context when running `greenwood serve`, you can export an **isolation** option from your page, set to `true`. +To execute an API route in its own isolated rendering context, you can export an **isolation** option from your page, set to `true`. ```js export const isolation = true; ``` -> For more information and how you can enable this for all pages, please see the [isolation configuration](/docs/reference/configuration/#isolation) docs. +> For more information and how you can enable this for all pages, please see the [isolation configuration](/docs/reference/configuration/#isolation-mode) docs. diff --git a/src/pages/docs/pages/index.md b/src/pages/docs/pages/index.md index b2112029..f7565f49 100644 --- a/src/pages/docs/pages/index.md +++ b/src/pages/docs/pages/index.md @@ -7,7 +7,7 @@ order: 2

Greenwood supports hybrid, file-based routing, to easily create routes for your project based on the file contents of your pages directory.

-This section will cover the various aspects of creating static and dynamic content for your project, covering: +This section will cover the various aspects of creating static and dynamic content for your project: - [Routing Conventions](/docs/pages/routing/) - [Server Side Rendering (SSR)](/docs/pages/server-rendering/) diff --git a/src/pages/docs/pages/layouts.md b/src/pages/docs/pages/layouts.md index 5be35a68..420f6a77 100644 --- a/src/pages/docs/pages/layouts.md +++ b/src/pages/docs/pages/layouts.md @@ -6,10 +6,10 @@ tocHeading: 2 # Layouts -Greenwood defines two types of layouts to help layout your pages with common HTML +Greenwood defines two types of layouts that can be used to wrap your pages with common HTML - _App Layout_: The ["app shell"](https://developers.google.com/web/fundamentals/architecture/app-shell) that will wrap all pages. -- _Page Layouts_: Layouts that can be re-used across multiple pages using [frontmatter](/docs/resources/markdown/#frontmatter). +- _Page Layouts_: Layouts that can be re-used across multiple pages and defined using [frontmatter](/docs/resources/markdown/#frontmatter). Greenwood will handle merging the `` and `` tag contents when building up your pages and layouts. @@ -33,7 +33,7 @@ src/ ## Pages -Pages in your project will generally want a layout so you can control the output of the HTML and include all your own custom components and styles to wrap the content. By default all pages will default to looking for a _page.html_ in _layouts/_ directory within your workspace. A placeholder of `` can be used to position where the processed content from the incoming page will go. +Pages in your project will generally want a layout so you can control the output of the HTML and include all your own custom components and styles to wrap the content. By default all pages will default to looking for a _page.html_ in the _layouts/_ directory. A placeholder of `` can be used to position where the processed content from the incoming page will go. Below is an example of a _page.html_ layout: diff --git a/src/pages/docs/pages/routing.md b/src/pages/docs/pages/routing.md index ec9b26c0..88fe999a 100644 --- a/src/pages/docs/pages/routing.md +++ b/src/pages/docs/pages/routing.md @@ -33,7 +33,7 @@ The following routes will be accessible from the browser: ## SSR -Greenwood supports the intermingling of static pages like HTML and markdown with dynamic pages. Taking the example above, if we wanted a server rendered route, like a "Products" page, we can simply create a JavaScript file following the same naming convention. +Greenwood supports the intermingling of static pages with dynamic pages. Taking the example above, if we wanted a server rendered route, like a "Products" page, we can simply create a JavaScript file following the same naming convention. ```shell src/ @@ -67,9 +67,9 @@ Now the route _/api/search_ will be available to return a Web API `Response`. ## SPA -If you would like to opt-out of all file-based routing, like a **Single Page Application (SPA)**, you can opt-out of pages routing entirely and go full client-side only mode by just putting an _index.html_ at the root of your workspace. +You can opt-out of all file-based routing, like for a **Single Page Application (SPA)**, and go full client-side only mode by just putting an _index.html_ at the root of your workspace. (e.g. **no** _pages/_ directory). -Below is an example project structure of a typical SPA (no _pages/_ directory): +Below is an example project structure for a typical SPA: ```shell src/ @@ -86,7 +86,7 @@ src/ ## Not Found -As is a [common convention with most hosting providers](https://docs.netlify.com/routing/redirects/redirect-options/#custom-404-page-handling) and web servers, you can create a `404` page in your _pages/_ directory which will be used as the default Not Found page for your site. +As is a [common convention with most hosting providers](https://docs.netlify.com/routing/redirects/redirect-options/#custom-404-page-handling) and web servers, you can create a `404` page in your _pages/_ directory which will be used as the default **Not Found** page for your site. ```shell src/ diff --git a/src/pages/docs/pages/server-rendering.md b/src/pages/docs/pages/server-rendering.md index 496e001e..d2737c9c 100644 --- a/src/pages/docs/pages/server-rendering.md +++ b/src/pages/docs/pages/server-rendering.md @@ -6,7 +6,7 @@ tocHeading: 2 # Server Rendering -Greenwood provides a couple of mechanisms for server-side rendering, building on top of our [file-based routing](/docs/routing/) convention. +Greenwood provides a couple of mechanisms for server-side rendering, building on top of our [file-based routing](/docs/pages/) convention. To create a dynamic server route, just create a JavaScript file in the _pages/_ directory, and that's it! @@ -16,7 +16,7 @@ src/ users.js ``` -The above would serve content in a browser at the path `/users/`. +The above would serve content in a browser at the path _/users/_. ## Usage @@ -24,7 +24,7 @@ In your page file, Greenwood supports the following functions that you can `expo - **default** (recommended): Use the custom elements API to render out your page content, aka **Web (Server) Components** - **getBody**: Return a string of HTML for the contents of the page -- **getLayout**: Return a string of HTML to act as the [page's layout](/docs/pages/layouts/#page-layouts) +- **getLayout**: Return a string of HTML to act as the [page's layout](/docs/pages/layouts/#pages) - **getFrontmatter**: Provide an object of [frontmatter](/docs/resources/markdown/#frontmatter) properties. Useful in conjunction with [content as data](/docs/content-as-data/), or otherwise setting static configuration / metadata through SSR. @@ -59,7 +59,7 @@ export { getFrontmatter, getBody, getLayout }; ### Web (Server) Components -Everyone else gets to use their component model for authoring pages, so why not Web Components! When using `export default`, Greenwood supports providing a custom element as the export for your page content, which Greenwood refers to as **Web Server Components (WSCs)** and uses [**WCC**](https://github.com/ProjectEvergreen/wcc) as the default renderer. +Everyone else gets to use their component model for authoring pages, so why not Web Components!? When using `export default`, Greenwood supports providing a custom element as the export for your page content, which Greenwood refers to as **Web Server Components (WSCs)** and uses [**WCC**](https://github.com/ProjectEvergreen/wcc) as the default renderer. This is the recommended pattern for SSR in Greenwood: @@ -89,7 +89,7 @@ export default class UsersPage extends HTMLElement { A couple of notes: - WSCs run only on the server, thus you have full access to any APIs of the runtime, with the ability to perform one time `async` operations for [data loading](/docs/pages/server-rendering/#request-data) in `connectedCallback`. -- Keep in mind that for these "page" components, you will likely want to _avoid_ rendering into a shadow root so as to avoid wrapping your static content in a `