From 5ec0777e5acb25e8905466f7a5575be498784f5b Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Mon, 9 Sep 2024 16:10:17 -0400 Subject: [PATCH 01/50] initial guides content stub and side nav from content as data --- patches/@greenwood+cli+0.30.0-alpha.6.patch | 16 ++--- src/components/side-nav/side-nav.js | 65 +++++++++++++++++++ .../table-of-contents/table-of-contents.js | 32 +++++++++ src/layouts/guides.html | 52 +++++++++++++++ src/pages/guides/ecosystem/htmx.md | 7 ++ src/pages/guides/ecosystem/index.md | 8 +++ src/pages/guides/ecosystem/lit.md | 7 ++ src/pages/guides/getting-started/index.md | 8 +++ .../guides/getting-started/quick-start.md | 14 ++++ .../guides/getting-started/scaffolding.md | 8 +++ .../guides/getting-started/walkthrough.md | 4 ++ src/pages/guides/hosting/aws.md | 9 +++ src/pages/guides/hosting/index.md | 8 +++ src/pages/guides/hosting/netlify.md | 7 ++ src/pages/guides/hosting/vercel.md | 7 ++ src/pages/guides/index.md | 6 ++ .../tutorials/full-stack-web-components.md | 8 +++ src/pages/guides/tutorials/index.md | 8 +++ src/pages/guides/tutorials/theme-packs.md | 6 ++ 19 files changed, 271 insertions(+), 9 deletions(-) create mode 100644 src/components/side-nav/side-nav.js create mode 100644 src/components/table-of-contents/table-of-contents.js create mode 100644 src/layouts/guides.html create mode 100644 src/pages/guides/ecosystem/htmx.md create mode 100644 src/pages/guides/ecosystem/index.md create mode 100644 src/pages/guides/ecosystem/lit.md create mode 100644 src/pages/guides/getting-started/index.md create mode 100644 src/pages/guides/getting-started/quick-start.md create mode 100644 src/pages/guides/getting-started/scaffolding.md create mode 100644 src/pages/guides/getting-started/walkthrough.md create mode 100644 src/pages/guides/hosting/aws.md create mode 100644 src/pages/guides/hosting/index.md create mode 100644 src/pages/guides/hosting/netlify.md create mode 100644 src/pages/guides/hosting/vercel.md create mode 100644 src/pages/guides/index.md create mode 100644 src/pages/guides/tutorials/full-stack-web-components.md create mode 100644 src/pages/guides/tutorials/index.md create mode 100644 src/pages/guides/tutorials/theme-packs.md diff --git a/patches/@greenwood+cli+0.30.0-alpha.6.patch b/patches/@greenwood+cli+0.30.0-alpha.6.patch index 85af4c95..6ac82fa8 100644 --- a/patches/@greenwood+cli+0.30.0-alpha.6.patch +++ b/patches/@greenwood+cli+0.30.0-alpha.6.patch @@ -787,7 +787,7 @@ index 286c2de..a336aed 100644 return new Response(body); } diff --git a/node_modules/@greenwood/cli/src/plugins/resource/plugin-standard-html.js b/node_modules/@greenwood/cli/src/plugins/resource/plugin-standard-html.js -index 06223cf..ab63448 100644 +index 06223cf..b372e38 100644 --- a/node_modules/@greenwood/cli/src/plugins/resource/plugin-standard-html.js +++ b/node_modules/@greenwood/cli/src/plugins/resource/plugin-standard-html.js @@ -5,7 +5,6 @@ @@ -884,7 +884,7 @@ index 06223cf..ab63448 100644 body = await getUserScripts(body, this.compilation); if (processedMarkdown) { -@@ -171,15 +150,32 @@ class StandardHtmlResource extends ResourceInterface { +@@ -171,15 +150,30 @@ class StandardHtmlResource extends ResourceInterface { body = body.replace(/\(.*)<\/content-outlet>/s, `${ssrBody.replace(/\$/g, '$$$')}`); } @@ -898,15 +898,13 @@ index 06223cf..ab63448 100644 + body = body.replace(new RegExp(interpolatedFrontmatter, 'g'), needle); + } + -+ // TODO -+ // const activeFrontmatterForwardKeys = ['route', 'label']; ++ const activeFrontmatterForwardKeys = ['route', 'label', 'title']; + -+ // for (const key of activeFrontmatterForwardKeys) { -+ // console.log({ key }) -+ // const interpolatedFrontmatter = '\\$\\{globalThis.page.' + key + '\\}'; ++ for (const key of activeFrontmatterForwardKeys) { ++ const interpolatedFrontmatter = '\\$\\{globalThis.page.' + key + '\\}'; + -+ // body = body.replace(new RegExp(interpolatedFrontmatter, 'g'), matchingRoute[key]); -+ // } ++ body = body.replace(new RegExp(interpolatedFrontmatter, 'g'), matchingRoute[key]); ++ } + + for (const collection in this.compilation.collections) { + const interpolatedFrontmatter = '\\$\\{globalThis.collection.' + collection + '\\}'; diff --git a/src/components/side-nav/side-nav.js b/src/components/side-nav/side-nav.js new file mode 100644 index 00000000..7048fecf --- /dev/null +++ b/src/components/side-nav/side-nav.js @@ -0,0 +1,65 @@ +import { getContentByRoute } from "@greenwood/cli/src/data/queries.js"; + +export default class SideNav extends HTMLElement { + async connectedCallback() { + const heading = this.getAttribute("heading") || ""; + const route = this.getAttribute("route"); + + // TODO on first render, even static attributes are undefined + // ??? { route: undefined, heading: '' } + // ??? { route: '/guides/', heading: 'Guides' } + // { route: '/guides/', heading: 'Guides' } + if (route && route !== "") { + const content = (await getContentByRoute(route)).filter((page) => page.route !== route); + const sections = []; + + content.forEach((item) => { + const segments = item.route + .replace(`${route}`, "") + .split("/") + .filter((segment) => segment !== ""); + const parent = content.find((page) => page.route === `${route}${segments[0]}/`); + + if (!sections[parent.data.order - 1]) { + sections[parent.data.order - 1] = { + link: parent.route, + heading: parent.label, // TODO title not populating + items: [], + }; + } + + if (parent.route !== item.route) { + sections[parent.data.order - 1].items[item.data.order - 1] = item; + } + }); + + this.innerHTML = ` +

${heading}

+ ${sections + .map((section) => { + const { heading, items, link } = section; + + return ` +

+ ${heading} +

+ + `; + }) + .join("")} + `; + } + } +} + +customElements.define("app-side-nav", SideNav); diff --git a/src/components/table-of-contents/table-of-contents.js b/src/components/table-of-contents/table-of-contents.js new file mode 100644 index 00000000..8774a516 --- /dev/null +++ b/src/components/table-of-contents/table-of-contents.js @@ -0,0 +1,32 @@ +import { getContent } from "@greenwood/cli/src/data/queries.js"; + +export default class TableOfContents extends HTMLElement { + async connectedCallback() { + const route = this.getAttribute("route") ?? ""; + const page = (await getContent()).find((page) => page.route === route); + // maybe call the frontmatter toc as well? + const { tableOfContents = [] } = page?.data ?? {}; + // console.log({ route, page, tableOfContents }); + + if (tableOfContents.length === 0) { + return; + } + + this.innerHTML = ` +

On This Page (${page.title})

+
    + ${tableOfContents + .map((item) => { + const { content, slug } = item; + + return ` +
  1. ${content}
  2. + `; + }) + .join("")} +
+ `; + } +} + +customElements.define("app-toc", TableOfContents); diff --git a/src/layouts/guides.html b/src/layouts/guides.html new file mode 100644 index 00000000..5d453c3b --- /dev/null +++ b/src/layouts/guides.html @@ -0,0 +1,52 @@ + + + Greenwood - ${globalThis.page.title} + + + + + + + +
+ +
+
+ +
+ + diff --git a/src/pages/guides/ecosystem/htmx.md b/src/pages/guides/ecosystem/htmx.md new file mode 100644 index 00000000..3619f4f6 --- /dev/null +++ b/src/pages/guides/ecosystem/htmx.md @@ -0,0 +1,7 @@ +--- +title: htmx +layout: guides +order: 2 +--- + +# htmx diff --git a/src/pages/guides/ecosystem/index.md b/src/pages/guides/ecosystem/index.md new file mode 100644 index 00000000..850e75b0 --- /dev/null +++ b/src/pages/guides/ecosystem/index.md @@ -0,0 +1,8 @@ +--- +order: 3 +layout: guides +--- + +# Ecosystem + +In general, for client side, just an `npm install` and import maps... diff --git a/src/pages/guides/ecosystem/lit.md b/src/pages/guides/ecosystem/lit.md new file mode 100644 index 00000000..4b3851b0 --- /dev/null +++ b/src/pages/guides/ecosystem/lit.md @@ -0,0 +1,7 @@ +--- +title: Lit +layout: guides +order: 1 +--- + +# Lit diff --git a/src/pages/guides/getting-started/index.md b/src/pages/guides/getting-started/index.md new file mode 100644 index 00000000..a5299767 --- /dev/null +++ b/src/pages/guides/getting-started/index.md @@ -0,0 +1,8 @@ +--- +order: 1 +layout: guides +--- + +# Getting Started + +Guides on getting started with Greenwood. diff --git a/src/pages/guides/getting-started/quick-start.md b/src/pages/guides/getting-started/quick-start.md new file mode 100644 index 00000000..6759f2b0 --- /dev/null +++ b/src/pages/guides/getting-started/quick-start.md @@ -0,0 +1,14 @@ +--- +order: 1 +title: Quick Start +layout: guides +tocHeading: 2 +--- + +# Quick Start + +## `init` + +## Stackblitz + +## Repo diff --git a/src/pages/guides/getting-started/scaffolding.md b/src/pages/guides/getting-started/scaffolding.md new file mode 100644 index 00000000..4f4b50bd --- /dev/null +++ b/src/pages/guides/getting-started/scaffolding.md @@ -0,0 +1,8 @@ +--- +order: 3 +layout: guides +--- + +## Scaffolding + +Let's talk about the `init` package and its options. diff --git a/src/pages/guides/getting-started/walkthrough.md b/src/pages/guides/getting-started/walkthrough.md new file mode 100644 index 00000000..3149778a --- /dev/null +++ b/src/pages/guides/getting-started/walkthrough.md @@ -0,0 +1,4 @@ +--- +order: 2 +layout: guides +--- diff --git a/src/pages/guides/hosting/aws.md b/src/pages/guides/hosting/aws.md new file mode 100644 index 00000000..d5e469fe --- /dev/null +++ b/src/pages/guides/hosting/aws.md @@ -0,0 +1,9 @@ +--- +title: AWS +layout: guides +order: 3 +--- + +# AWS + +Deploying to AWS. diff --git a/src/pages/guides/hosting/index.md b/src/pages/guides/hosting/index.md new file mode 100644 index 00000000..cc66ef50 --- /dev/null +++ b/src/pages/guides/hosting/index.md @@ -0,0 +1,8 @@ +--- +order: 2 +layout: guides +--- + +# Hosting + +How to build and deploy with Greenwood. diff --git a/src/pages/guides/hosting/netlify.md b/src/pages/guides/hosting/netlify.md new file mode 100644 index 00000000..b2f0bd25 --- /dev/null +++ b/src/pages/guides/hosting/netlify.md @@ -0,0 +1,7 @@ +--- +title: Netlify +layout: guides +order: 1 +--- + +# Netlify diff --git a/src/pages/guides/hosting/vercel.md b/src/pages/guides/hosting/vercel.md new file mode 100644 index 00000000..ed2648d6 --- /dev/null +++ b/src/pages/guides/hosting/vercel.md @@ -0,0 +1,7 @@ +--- +title: Vercel +layout: guides +order: 2 +--- + +# Vercel diff --git a/src/pages/guides/index.md b/src/pages/guides/index.md new file mode 100644 index 00000000..0b001f5d --- /dev/null +++ b/src/pages/guides/index.md @@ -0,0 +1,6 @@ +--- +title: Guides +layout: guides +--- + +# Welcome to our Guides diff --git a/src/pages/guides/tutorials/full-stack-web-components.md b/src/pages/guides/tutorials/full-stack-web-components.md new file mode 100644 index 00000000..1a93cf52 --- /dev/null +++ b/src/pages/guides/tutorials/full-stack-web-components.md @@ -0,0 +1,8 @@ +--- +layout: guides +order: 1 +--- + +## Full Stack Web Components + +Re-using a web component on the server and client! diff --git a/src/pages/guides/tutorials/index.md b/src/pages/guides/tutorials/index.md new file mode 100644 index 00000000..033255b0 --- /dev/null +++ b/src/pages/guides/tutorials/index.md @@ -0,0 +1,8 @@ +--- +order: 4 +layout: guides +--- + +# Tutorials + +Some fun things you can do with Greenwood diff --git a/src/pages/guides/tutorials/theme-packs.md b/src/pages/guides/tutorials/theme-packs.md new file mode 100644 index 00000000..b4ce70da --- /dev/null +++ b/src/pages/guides/tutorials/theme-packs.md @@ -0,0 +1,6 @@ +--- +layout: guides +order: 2 +--- + +## Theme Packs From bc5b70ebaa23ae0937ebb1dcb87f9845ea792d31 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Mon, 9 Sep 2024 21:02:26 -0400 Subject: [PATCH 02/50] initial side nav mobile styling --- src/components/side-nav/side-nav.js | 84 ++++++++++++++----- src/components/side-nav/side-nav.module.css | 64 ++++++++++++++ src/layouts/guides.html | 24 ++++-- src/pages/guides/ecosystem/htmx.md | 1 + .../guides/getting-started/walkthrough.md | 4 + src/pages/guides/hosting/aws.md | 1 + src/pages/guides/index.md | 2 + 7 files changed, 152 insertions(+), 28 deletions(-) create mode 100644 src/components/side-nav/side-nav.module.css diff --git a/src/components/side-nav/side-nav.js b/src/components/side-nav/side-nav.js index 7048fecf..d0a4aef2 100644 --- a/src/components/side-nav/side-nav.js +++ b/src/components/side-nav/side-nav.js @@ -1,9 +1,11 @@ import { getContentByRoute } from "@greenwood/cli/src/data/queries.js"; +import styles from './side-nav.module.css'; export default class SideNav extends HTMLElement { async connectedCallback() { const heading = this.getAttribute("heading") || ""; const route = this.getAttribute("route"); + const currentRoute = this.getAttribute("current-route") || ""; // TODO on first render, even static attributes are undefined // ??? { route: undefined, heading: '' } @@ -34,29 +36,69 @@ export default class SideNav extends HTMLElement { }); this.innerHTML = ` -

${heading}

- ${sections - .map((section) => { - const { heading, items, link } = section; +
+

${heading}

+ ${sections + .map((section) => { + const { heading, items, link } = section; - return ` -

- ${heading} -

-
    - ${items - .map((item) => { - const { label, route } = item; + return ` +

    + ${heading} +

    +
      + ${items + .map((item) => { + const { label, route } = item; - return ` -
    • ${label}
    • - `; - }) - .join("")} -
    - `; - }) - .join("")} + return ` +
  • ${label}
  • + `; + }) + .join("")} +
+ `; + }) + .join("") + } +
+
+ +
+ ${ + sections + .map((section) => { + const { heading, items, link } = section; + + return ` +

+ ${heading} +

+
    + ${items + .map((item) => { + const { label, route } = item; + const isActive = route === currentRoute + ? ' active' + : ""; + + return ` +
  • + ${label} +
  • + `; + }) + .join("") + } +
+ `; + }) + .join("") + } +
+
`; } } diff --git a/src/components/side-nav/side-nav.module.css b/src/components/side-nav/side-nav.module.css new file mode 100644 index 00000000..daec86a0 --- /dev/null +++ b/src/components/side-nav/side-nav.module.css @@ -0,0 +1,64 @@ +.fullMenu { + display: none; +} + +.compactMenu { + background-color: var(--color-gray-background); +} + +.compactMenu a { + color: var(--color-black); + text-decoration: none; +} + +.compactMenuPopoverTrigger { + background-color: var(--color-white); + border: none; + padding: var(--size-2); + border-radius: var(--radius-2); + box-shadow: var(--shadow-3); +} + +.compactMenuPopover { + top: 150px; + width: 100%; + padding: var(--size-4); + background-color: var(--color-gray-background); +} + +.compactMenuSectionHeading { + text-decoration: underline; + margin: var(--size-3) 0; +} + +.compactMenuSectionList { + list-style: none; +} + +.compactMenuSectionListItem { + margin: var(--size-2); + padding: 0 var(--size-2); + /* background-color: transparent; + border: 1px solid var(--color-black); + padding: var(--size-1); */ +} + +.compactMenuSectionList .active a { + border-left: var(--size-1) solid black; + border-radius: var(--radius-2); + background-color: white; + padding: 0 var(--size-2); + min-width: 200px; + display: inline-block; + box-shadow: var(--shadow-3); +} + +@media (min-width: 1024px) { + .fullMenu { + display: block; + } + + .compactMenu { + display: none; + } +} diff --git a/src/layouts/guides.html b/src/layouts/guides.html index 5d453c3b..5d8fc1eb 100644 --- a/src/layouts/guides.html +++ b/src/layouts/guides.html @@ -8,7 +8,17 @@ data-gwd-opt="static" > -
- ${ - sections - .map((section) => { - const { heading, items, link } = section; + ${sections + .map((section) => { + const { heading, items, link } = section; - return ` + return `

${heading}

@@ -80,9 +78,7 @@ export default class SideNav extends HTMLElement { ${items .map((item) => { const { label, route } = item; - const isActive = route === currentRoute - ? ' active' - : ""; + const isActive = route === currentRoute ? " active" : ""; return `
  • @@ -90,13 +86,11 @@ export default class SideNav extends HTMLElement {
  • `; }) - .join("") - } + .join("")} `; - }) - .join("") - } + }) + .join("")}
    `; diff --git a/src/components/side-nav/side-nav.module.css b/src/components/side-nav/side-nav.module.css index daec86a0..7de8d1c9 100644 --- a/src/components/side-nav/side-nav.module.css +++ b/src/components/side-nav/side-nav.module.css @@ -3,7 +3,8 @@ } .compactMenu { - background-color: var(--color-gray-background); + /* background-color: var(--color-gray-background); */ + /* position: fixed; */ } .compactMenu a { @@ -24,11 +25,15 @@ width: 100%; padding: var(--size-4); background-color: var(--color-gray-background); + position: fixed; } .compactMenuSectionHeading { - text-decoration: underline; margin: var(--size-3) 0; + + & a { + font-family: var(--font-primary-bold); + } } .compactMenuSectionList { @@ -37,10 +42,6 @@ .compactMenuSectionListItem { margin: var(--size-2); - padding: 0 var(--size-2); - /* background-color: transparent; - border: 1px solid var(--color-black); - padding: var(--size-1); */ } .compactMenuSectionList .active a { diff --git a/src/components/table-of-contents/table-of-contents.js b/src/components/table-of-contents/table-of-contents.js index 8774a516..bc657cfd 100644 --- a/src/components/table-of-contents/table-of-contents.js +++ b/src/components/table-of-contents/table-of-contents.js @@ -1,30 +1,36 @@ import { getContent } from "@greenwood/cli/src/data/queries.js"; +import styles from "./table-of-contents.module.css"; export default class TableOfContents extends HTMLElement { async connectedCallback() { const route = this.getAttribute("route") ?? ""; const page = (await getContent()).find((page) => page.route === route); - // maybe call the frontmatter toc as well? const { tableOfContents = [] } = page?.data ?? {}; - // console.log({ route, page, tableOfContents }); if (tableOfContents.length === 0) { return; } this.innerHTML = ` -

    On This Page (${page.title})

    -
      - ${tableOfContents - .map((item) => { - const { content, slug } = item; + +
      +

      Table of Contents

      +
        + ${tableOfContents + .map((item) => { + const { content, slug } = item; - return ` -
      1. ${content}
      2. - `; - }) - .join("")} -
      + return ` +
    1. + ${content} +
    2. + `; + }) + .join("")} +
    + `; } } diff --git a/src/components/table-of-contents/table-of-contents.module.css b/src/components/table-of-contents/table-of-contents.module.css new file mode 100644 index 00000000..d455043f --- /dev/null +++ b/src/components/table-of-contents/table-of-contents.module.css @@ -0,0 +1,27 @@ +.tocMenu { + top: 150px; + width: 100%; + padding: var(--size-4); + background-color: var(--color-gray-background); +} + +.tocMenuTrigger { + background-color: var(--color-white); + border: none; + padding: var(--size-2); + border-radius: var(--radius-2); + box-shadow: var(--shadow-3); +} + +.tocMenuItem { + margin: var(--size-2) var(--size-4); + padding: 0 var(--size-2); + + & a { + color: var(--color-black); + } +} + +.tocMenuHeading { + font-family: var(--font-primary-bold); +} diff --git a/src/layouts/guides.html b/src/layouts/guides.html index 5d8fc1eb..544207f1 100644 --- a/src/layouts/guides.html +++ b/src/layouts/guides.html @@ -14,49 +14,33 @@ margin: var(--size-4) 0 0; } - app-side-nav { + app-side-nav, + app-toc { display: inline-block; margin: 0 0 var(--size-4); } - /* .sidebar, - .content, - .toc { - display: inline-block; - padding-left: 2rem; - vertical-align: top; - } - - .sidebar { - width: 20%; - border-right: 1px solid black; - } - .content { - text-align: left; - vertical-align: top; - width: 50%; + h2, + p { + margin: var(--size-4) 0; } - .toc { - width: 20%; + hr { + margin: 0 0 var(--size-4); } - - app-toc { - float: right; - margin-right: 2rem; - } */ - + + + +
    + - diff --git a/src/pages/guides/getting-started/quick-start.md b/src/pages/guides/getting-started/quick-start.md index 6759f2b0..d53cbf78 100644 --- a/src/pages/guides/getting-started/quick-start.md +++ b/src/pages/guides/getting-started/quick-start.md @@ -9,6 +9,12 @@ tocHeading: 2 ## `init` +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. + ## Stackblitz +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. + ## Repo + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. diff --git a/src/pages/guides/getting-started/walkthrough.md b/src/pages/guides/getting-started/walkthrough.md index 67ba7c82..e0518e1c 100644 --- a/src/pages/guides/getting-started/walkthrough.md +++ b/src/pages/guides/getting-started/walkthrough.md @@ -5,4 +5,4 @@ layout: guides ## Walkthrough -Let's start a new project! \ No newline at end of file +Let's start a new project! diff --git a/src/pages/guides/index.md b/src/pages/guides/index.md index 1bc2957f..d3918a2b 100644 --- a/src/pages/guides/index.md +++ b/src/pages/guides/index.md @@ -5,4 +5,4 @@ layout: guides # Welcome to our Guides -"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?" \ No newline at end of file +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? From 823765bc6599d3848166b3fa31b9425e6c6fa846 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Thu, 12 Sep 2024 09:29:09 -0400 Subject: [PATCH 04/50] toc headings for guides content --- src/pages/guides/ecosystem/htmx.md | 1 + src/pages/guides/ecosystem/lit.md | 1 + src/pages/guides/getting-started/scaffolding.md | 3 ++- src/pages/guides/getting-started/walkthrough.md | 3 ++- src/pages/guides/hosting/aws.md | 1 + src/pages/guides/hosting/netlify.md | 1 + src/pages/guides/hosting/vercel.md | 1 + src/pages/guides/tutorials/full-stack-web-components.md | 3 ++- src/pages/guides/tutorials/theme-packs.md | 3 ++- 9 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/pages/guides/ecosystem/htmx.md b/src/pages/guides/ecosystem/htmx.md index 22b96a6e..153584ef 100644 --- a/src/pages/guides/ecosystem/htmx.md +++ b/src/pages/guides/ecosystem/htmx.md @@ -3,6 +3,7 @@ title: htmx label: HTMX layout: guides order: 2 +tocHeading: 2 --- # htmx diff --git a/src/pages/guides/ecosystem/lit.md b/src/pages/guides/ecosystem/lit.md index 4b3851b0..47556149 100644 --- a/src/pages/guides/ecosystem/lit.md +++ b/src/pages/guides/ecosystem/lit.md @@ -2,6 +2,7 @@ title: Lit layout: guides order: 1 +tocHeading: 2 --- # Lit diff --git a/src/pages/guides/getting-started/scaffolding.md b/src/pages/guides/getting-started/scaffolding.md index 4f4b50bd..488d9ba7 100644 --- a/src/pages/guides/getting-started/scaffolding.md +++ b/src/pages/guides/getting-started/scaffolding.md @@ -1,8 +1,9 @@ --- order: 3 layout: guides +tocHeading: 2 --- -## Scaffolding +# Scaffolding Let's talk about the `init` package and its options. diff --git a/src/pages/guides/getting-started/walkthrough.md b/src/pages/guides/getting-started/walkthrough.md index e0518e1c..e40450c4 100644 --- a/src/pages/guides/getting-started/walkthrough.md +++ b/src/pages/guides/getting-started/walkthrough.md @@ -1,8 +1,9 @@ --- order: 2 layout: guides +tocHeading: 2 --- -## Walkthrough +# Walkthrough Let's start a new project! diff --git a/src/pages/guides/hosting/aws.md b/src/pages/guides/hosting/aws.md index c370b019..b33749aa 100644 --- a/src/pages/guides/hosting/aws.md +++ b/src/pages/guides/hosting/aws.md @@ -3,6 +3,7 @@ title: AWS label: AWS layout: guides order: 3 +tocHeading: 2 --- # AWS diff --git a/src/pages/guides/hosting/netlify.md b/src/pages/guides/hosting/netlify.md index b2f0bd25..52a0b732 100644 --- a/src/pages/guides/hosting/netlify.md +++ b/src/pages/guides/hosting/netlify.md @@ -2,6 +2,7 @@ title: Netlify layout: guides order: 1 +tocHeading: 2 --- # Netlify diff --git a/src/pages/guides/hosting/vercel.md b/src/pages/guides/hosting/vercel.md index ed2648d6..671fa040 100644 --- a/src/pages/guides/hosting/vercel.md +++ b/src/pages/guides/hosting/vercel.md @@ -2,6 +2,7 @@ title: Vercel layout: guides order: 2 +tocHeading: 2 --- # Vercel diff --git a/src/pages/guides/tutorials/full-stack-web-components.md b/src/pages/guides/tutorials/full-stack-web-components.md index 1a93cf52..52d3f66d 100644 --- a/src/pages/guides/tutorials/full-stack-web-components.md +++ b/src/pages/guides/tutorials/full-stack-web-components.md @@ -1,8 +1,9 @@ --- layout: guides order: 1 +tocHeading: 2 --- -## Full Stack Web Components +# Full Stack Web Components Re-using a web component on the server and client! diff --git a/src/pages/guides/tutorials/theme-packs.md b/src/pages/guides/tutorials/theme-packs.md index b4ce70da..39de074d 100644 --- a/src/pages/guides/tutorials/theme-packs.md +++ b/src/pages/guides/tutorials/theme-packs.md @@ -1,6 +1,7 @@ --- layout: guides order: 2 +tocHeading: 2 --- -## Theme Packs +# Theme Packs From 0ff94cc127d0d4340f5d4c130c41973c1624e324 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Thu, 12 Sep 2024 09:30:01 -0400 Subject: [PATCH 05/50] additional and initial tablet and desktop mobile sizing --- src/components/side-nav/side-nav.js | 32 +++++----- src/components/side-nav/side-nav.module.css | 20 +++--- .../table-of-contents/table-of-contents.js | 32 +++++++--- .../table-of-contents.module.css | 39 ++++++++++-- src/layouts/guides.html | 62 ++++++++++++++++--- 5 files changed, 142 insertions(+), 43 deletions(-) diff --git a/src/components/side-nav/side-nav.js b/src/components/side-nav/side-nav.js index 30277009..e9f21365 100644 --- a/src/components/side-nav/side-nav.js +++ b/src/components/side-nav/side-nav.js @@ -37,27 +37,29 @@ export default class SideNav extends HTMLElement { this.innerHTML = `
    -

    ${heading}

    ${sections .map((section) => { const { heading, items, link } = section; return ` -

    - ${heading} -

    -
      - ${items - .map((item) => { - const { label, route } = item; +

      + ${heading} +

      +
        + ${items + .map((item) => { + const { label, route } = item; + const isActive = route === currentRoute ? " active" : ""; - return ` -
      • ${label}
      • - `; - }) - .join("")} -
      - `; + return ` +
    • + ${label} +
    • + `; + }) + .join("")} +
    + `; }) .join("")}
    diff --git a/src/components/side-nav/side-nav.module.css b/src/components/side-nav/side-nav.module.css index 7de8d1c9..f0de8017 100644 --- a/src/components/side-nav/side-nav.module.css +++ b/src/components/side-nav/side-nav.module.css @@ -2,14 +2,12 @@ display: none; } -.compactMenu { - /* background-color: var(--color-gray-background); */ - /* position: fixed; */ -} - -.compactMenu a { - color: var(--color-black); - text-decoration: none; +.compactMenu, +.fullMenu { + & a { + color: var(--color-black); + text-decoration: none; + } } .compactMenuPopoverTrigger { @@ -29,7 +27,7 @@ } .compactMenuSectionHeading { - margin: var(--size-3) 0; + margin: 0 0 var(--size-2) 0; & a { font-family: var(--font-primary-bold); @@ -38,10 +36,11 @@ .compactMenuSectionList { list-style: none; + margin: 0 0 var(--size-6) 0; } .compactMenuSectionListItem { - margin: var(--size-2); + margin: var(--size-1); } .compactMenuSectionList .active a { @@ -52,6 +51,7 @@ min-width: 200px; display: inline-block; box-shadow: var(--shadow-3); + margin-left: -16px; } @media (min-width: 1024px) { diff --git a/src/components/table-of-contents/table-of-contents.js b/src/components/table-of-contents/table-of-contents.js index bc657cfd..019e58ca 100644 --- a/src/components/table-of-contents/table-of-contents.js +++ b/src/components/table-of-contents/table-of-contents.js @@ -12,25 +12,43 @@ export default class TableOfContents extends HTMLElement { } this.innerHTML = ` - -
    -

    Table of Contents

    +
    +

    On this page

      ${tableOfContents .map((item) => { const { content, slug } = item; return ` -
    1. - ${content} +
    2. + ${content}
    3. `; }) .join("")}
    +
    + +
    +

    Table of Contents

    +
      + ${tableOfContents + .map((item) => { + const { content, slug } = item; + + return ` +
    1. + ${content} +
    2. + `; + }) + .join("")} +
    +
    +
    `; } } 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 d455043f..9c293411 100644 --- a/src/components/table-of-contents/table-of-contents.module.css +++ b/src/components/table-of-contents/table-of-contents.module.css @@ -1,11 +1,32 @@ -.tocMenu { +.fullMenu { + display: none; + + & h2 { + margin: 0 0 var(--size-3); + } +} + +.compactMenu, +.fullMenu { + & a { + color: var(--color-black); + text-decoration: none; + } + + & ol { + list-style-type: lower-roman; + margin: var(--size-2) 0; + } +} + +.compactMenuPopover { top: 150px; width: 100%; padding: var(--size-4); background-color: var(--color-gray-background); } -.tocMenuTrigger { +.compactMenuTrigger { background-color: var(--color-white); border: none; padding: var(--size-2); @@ -13,7 +34,7 @@ box-shadow: var(--shadow-3); } -.tocMenuItem { +.compactMenuItem { margin: var(--size-2) var(--size-4); padding: 0 var(--size-2); @@ -22,6 +43,16 @@ } } -.tocMenuHeading { +.compactMenuHeading { font-family: var(--font-primary-bold); } + +@media (min-width: 1024px) { + .fullMenu { + display: block; + } + + .compactMenu { + display: none; + } +} diff --git a/src/layouts/guides.html b/src/layouts/guides.html index 544207f1..82d39c25 100644 --- a/src/layouts/guides.html +++ b/src/layouts/guides.html @@ -10,8 +10,21 @@ @@ -41,6 +87,8 @@
    - +
    + +
    From c0c53be339cceafc42db3c6e4dcfb99a83f37c59 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sun, 15 Sep 2024 17:03:24 -0400 Subject: [PATCH 06/50] misc styling --- src/components/capabilities/capabilities.css | 8 --- .../get-started/get-started.module.css | 2 + .../hero-banner/hero-banner.module.css | 2 + .../table-of-contents/table-of-contents.js | 5 +- .../table-of-contents.module.css | 4 +- src/layouts/guides.html | 32 +++++++++--- src/styles/theme.css | 51 +++++++++++++++++++ 7 files changed, 85 insertions(+), 19 deletions(-) diff --git a/src/components/capabilities/capabilities.css b/src/components/capabilities/capabilities.css index f1dc4fc0..3b77344c 100644 --- a/src/components/capabilities/capabilities.css +++ b/src/components/capabilities/capabilities.css @@ -109,14 +109,6 @@ pre { margin: 0; } -@media (max-width: 760px) { - code[class*="language-"], - pre[class*="language-"], - .token { - font-size: 0.9rem; - } -} - @media (min-width: 768px) { .container { padding: var(--size-fluid-4); diff --git a/src/components/get-started/get-started.module.css b/src/components/get-started/get-started.module.css index 3468f075..20c3e431 100644 --- a/src/components/get-started/get-started.module.css +++ b/src/components/get-started/get-started.module.css @@ -67,6 +67,8 @@ .snippet pre { padding: var(--size-px-4) var(--size-px-1); + background-color: transparent; + color: var(--color-black); } .snippet app-ctc { diff --git a/src/components/hero-banner/hero-banner.module.css b/src/components/hero-banner/hero-banner.module.css index 2f5f7ebd..06fff364 100644 --- a/src/components/hero-banner/hero-banner.module.css +++ b/src/components/hero-banner/hero-banner.module.css @@ -87,6 +87,8 @@ align-content: center; display: inline; vertical-align: text-top; + background-color: transparent; + color: var(--color-black); } .snippet app-ctc { diff --git a/src/components/table-of-contents/table-of-contents.js b/src/components/table-of-contents/table-of-contents.js index 019e58ca..b35ce9e6 100644 --- a/src/components/table-of-contents/table-of-contents.js +++ b/src/components/table-of-contents/table-of-contents.js @@ -13,7 +13,7 @@ export default class TableOfContents extends HTMLElement { this.innerHTML = `
    -

    On this page

    +

    On This Page

      ${tableOfContents .map((item) => { @@ -30,10 +30,9 @@ export default class TableOfContents extends HTMLElement {
    -

    Table of Contents

      ${tableOfContents .map((item) => { 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 9c293411..13ead05f 100644 --- a/src/components/table-of-contents/table-of-contents.module.css +++ b/src/components/table-of-contents/table-of-contents.module.css @@ -2,7 +2,8 @@ display: none; & h2 { - margin: 0 0 var(--size-3); + margin: 0 0 var(--size-2); + text-decoration: underline; } } @@ -45,6 +46,7 @@ .compactMenuHeading { font-family: var(--font-primary-bold); + text-decoration: underline; } @media (min-width: 1024px) { diff --git a/src/layouts/guides.html b/src/layouts/guides.html index 82d39c25..3588acfb 100644 --- a/src/layouts/guides.html +++ b/src/layouts/guides.html @@ -9,21 +9,39 @@ > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/layouts/guides.html b/src/layouts/guides.html index 3588acfb..b808b3da 100644 --- a/src/layouts/guides.html +++ b/src/layouts/guides.html @@ -13,6 +13,7 @@ margin: 0 0 var(--size-4); } } + .page-content .content { max-width: 100ch; padding: 0 var(--size-4); @@ -22,10 +23,10 @@ } & h2 { - font-size: var(--font-size-3); + font-size: var(--font-size-4); font-family: var(--font-primary-bold); font-weight: 100; - margin: var(--size-6) 0 0 !important; + margin: var(--size-8) 0 0 !important; } & h2, @@ -38,6 +39,10 @@ padding-left: var(--size-4); } + & img { + width: 100%; + } + h2 > a > span.icon, h3 > a > span.icon, h4 > a > span.icon { diff --git a/src/pages/guides/hosting/aws.md b/src/pages/guides/hosting/aws.md index b33749aa..90866c8f 100644 --- a/src/pages/guides/hosting/aws.md +++ b/src/pages/guides/hosting/aws.md @@ -8,4 +8,92 @@ tocHeading: 2 # AWS -Deploying to AWS. +Greenwood can be automatically deployed to [**AWS**](https://aws.amazon.com/) for static hosting using [**S3**](https://aws.amazon.com/s3/) and [**Cloudfront**](https://aws.amazon.com/cloudfront/) with GitHub Actions. This requires having an **AWS** account. + +> There is no adapter plugin yet for serverless hosting, though it is on [our roadmap](https://github.com/ProjectEvergreen/greenwood/issues/1142). + +## Setup S3 + +1. Create a new bucket in S3 +1. Upload the contents of your 'public' directory (drag and drop all the files and folders, using the interface only grabs files). +1. Within your bucket click the "Properties" tab and select "Static website hosting" +1. Check "Use this bucket to host a website" and enter `index.html` to the "index document" input +1. Go to "Permissions" tab and edit "Block Public Access" to turn those off and save +1. Still in (or click on) the "Permissions" tab, click "Bucket Policy" and add the following snippet (putting in your buckets name) and save. + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "PublicReadGetObject", + "Effect": "Allow", + "Principal": "*", + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::your-bucket-name-here/*" + } + ] + } + ``` + +Your site will now be at the address visible in the "Static website hosting" card. + +## Setup CloudFront + +1. Navigate to CloudFront in your AWS account. +1. Click "get started" in the web section. +1. In the "Origin Domain Name" input, select the bucket you are setting up. +1. Further down that form find "Default Root Object" and enter `index.html` +1. Click "Create Distribution", then just wait for the Status to update to "deployed". + +Your site is now hosted on S3 with a CloudFront CDN! 🏆 + +## Enable GitHub Actions + +1. You'll want to add your AWS Secret Access Key (`AWS_SECRET_ACCESS_KEY`) and Access Key ID (`AWS_SECRET_ACCESS_KEY_ID`) to the repositories as [GitHub secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). +1. At the root of your repo add a GitHub Action called _.github/workflows/deploy.yml_ and adapt as needed for your own branch and package manager. (Using npm here) + ```yml + name: Upload Website to S3 + + on: + push: + branches: + # configure your branch accordingly + - main + + jobs: + build: + runs-on: ubuntu-20.04 + + # match to your version of NodeJS + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v3 + with: + node-version: 18.20.2 + + - name: Navigate to repo + run: | + cd $GITHUB_WORKSPACE + + # or replace with yarn, pnpm, etc + - name: Install Dependencies + run: | + npm ci + + # use your greenwood build script + - name: Run Build + run: | + npm run build + + - name: Deploy to S3 + uses: opspresso/action-s3-sync@master + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_SECRET_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: "us-east-2" + FROM_PATH: "./public" + # your target s3 bucket name goes here + DEST_PATH: "s3://your-s3-bucket-name" + OPTIONS: "--acl public-read" + ``` +1. Push your updates to your repo and the action will begin automatically. diff --git a/src/pages/guides/hosting/cloudflare.md b/src/pages/guides/hosting/cloudflare.md index 8c0febd6..13b03245 100644 --- a/src/pages/guides/hosting/cloudflare.md +++ b/src/pages/guides/hosting/cloudflare.md @@ -6,3 +6,98 @@ tocHeading: 2 --- # Cloudflare + +[Cloudflare Workers](https://workers.cloudflare.com/) is an excellent option as a CDN for deploying and hosting your static Greenwood site. This will require a paid account with Cloudflare with a linked domain for custom domain and subdomains. + +> There is no adapter plugin yet for serverless hosting, though it is on [our roadmap](https://github.com/ProjectEvergreen/greenwood/issues/1143). + + +## Setup + +1. You will need to globally install Cloudflare's CLI tool _Wrangler_ + ```shell + npm install -g global @cloudflare/wrangler + ``` +1. In the root of your project directory initialize _Wrangler_ + ```shell + wrangler init + ``` +1. Authenticate your cloudflare account with: + ```shell + wrangler config + ``` +1. A _wrangler.toml_ file will be generated at the root of your project directory. You will want to update accordingly: + ```toml + name = "demo" # workers.dev subdomain name automatically named for the directory + type = "webpack" + account_id = "abcd12345...." # your account id + + [env.production] + workers_dev = true + + [site] + bucket = "./public" # where greenwood generated the compiled code + entry-point = "workers-site" + ``` +1. Run a Greenwood build + ```shell + greenwood build + ``` +1. Then push your code to Cloudflare workers + ```shell + wrangler publish + ``` + +When completed a url for workers subdomain will be printed in your terminal. 🏆 + +## Enabling GitHub Actions + +To have automatic deployments whenever you push updates to your repo, you will need to configure GitHub Actions to accomplish this, instead of having to manually run the `build` and `publish` commands each time you wish to update your site. + +1. Add the email address (`CF_WORKERS_EMAIL`) and API key (`CF_WORKERS_KEY`) associated with your account to the repositories [GitHub secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). +1. At the root of your project add '.github/workflows/deploy.yml' + ```yml + name: Deploy Cloudflare Workers + + # configure your branch accordingly + on: + push: + branches: + # configure your branch accordingly + - main + + jobs: + build: + runs-on: ubuntu-20.04 + + # match to your version of NodeJS + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v3 + with: + node-version: 18.20.2 + + - name: Navigate to repo + run: | + cd $GITHUB_WORKSPACE + + # or replace with yarn, pnpm, etc + - name: Install Dependencies + run: | + npm ci + + # use your greenwood build script + - name: Run Build + run: | + npm run build + + - name: Publish to Cloudflare Workers + uses: cloudflare/wrangler-action@1.1.0 + with: + apiKey: ${{ secrets.CF_WORKERS_KEY }} + email: ${{ secrets.CF_WORKERS_EMAIL }} + environment: "production" + ``` +1. Push your updates to your repo and the action will begin automatically. + +This will create a new worker with the name from the _.toml_ file -production (i.e. &&demo-production&&). Make sure your custom url is attached to this worker. \ No newline at end of file diff --git a/src/pages/guides/hosting/github-pages.md b/src/pages/guides/hosting/github-pages.md index 8884a186..660c074e 100644 --- a/src/pages/guides/hosting/github-pages.md +++ b/src/pages/guides/hosting/github-pages.md @@ -6,3 +6,90 @@ tocHeading: 2 --- # GitHub Pages + +Greenwood can easily be configured to work with [GitHub Pages](https://pages.github.com/) and GitHub Actions. With this guide, anytime you push to the designated branch, GitHub will automatically build your project with Greenwood and publish it to GitHub Pages. + +> You can see an example project in our [demonstration repo](https://github.com/ProjectEvergreen/greenwood-demo-github-pages). + +## Prerequisites + +Following the steps [outlined here](https://pages.github.com/), first make sure you have already: + +1. Created a repo in the format `.github.io` or `.github.io/` +1. If using `.github.io/`, make sure to set Greenwood's [base path](/docs/configuration/#base-peth) configuration to match + ```js + export default { + basePath: '/repo-name' + } + ``` +1. Get your project all setup in your repository. +1. If you don't have a build script, let's add one to _package.json_ to use in our GitHub Action + ```json + { + "scripts": { + "build": "greenwood build" + } + } + ``` + +## Setup + +1. Create a file called _.github/workflows/gh-pages.yml_ in your repo +1. Now add this GitHub Action, _making sure to use the correct branch name for your project_; **_master_, _main_**, etc. (We're leveraging [this action](https://github.com/marketplace/actions/github-pages-action) at the end for the actual auto deploy) + ```yml + name: Deploy GitHub Pages + + on: + push: + branches: + # configure your branch accordingly + - main + + jobs: + build-and-deploy: + runs-on: ubuntu-20.04 + + # match to your version of NodeJS + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v3 + with: + node-version: 18.20.2 + + # or replace with yarn, pnpm, etc + - name: Install Dependencies + run: | + npm ci + + # use your greenwood build script + - name: Run Build + run: | + npm run build + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + # change the branch name to match your repo + if: ${{ github.ref == 'refs/heads/main' }} + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public + ``` +1. Now `git` commit that and push it to your repo! + +If all was successful, you should now see a [`gh-pages` branch](https://github.com/ProjectEvergreen/projectevergreen.github.io/tree/gh-pages) in your repo with the output of the _public/_ directory committed to it. + +![github pages branch](/assets/guides/gh-pages-branch.png) + +Now, configure your repository by going to your [repo's _Settings -> Pages_](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site) and make the following changes. + +1. Set the source branch to **gh-pages** +1. Set path to be **/root** + ![github pages branch](/assets/guides/repo-github-pages-config.png) + +## Next Steps + +Now, everything should be setup so that on future pushes to the branch specified in the GitHub Actions workflow, GitHub pages should automatically build from the `gh-pages` branch and publish that. 🏆 + +![github pages branch](/assets/guides/gh-pages-branch-commits.png) + +Congrats, enjoy working on your website!! 🥳 \ No newline at end of file diff --git a/src/pages/guides/hosting/index.md b/src/pages/guides/hosting/index.md index 68f17f0f..fd9a589b 100644 --- a/src/pages/guides/hosting/index.md +++ b/src/pages/guides/hosting/index.md @@ -7,8 +7,7 @@ tocHeading: 2

      Hosting

      -The guides within this section cover some of the hosting options you can use to build and deploy your Greenwood project. Below is a an overview of the deploy targets available for a Greenwood site, with additional target specific pages in the sidebar. - + The guides within this section cover some of the hosting options you can use to build and deploy your Greenwood project. Below is a an overview of the deploy targets available for a Greenwood site, with additional target specific pages in the sidebar.
      ## Building diff --git a/src/pages/guides/hosting/netlify.md b/src/pages/guides/hosting/netlify.md index 52a0b732..75919866 100644 --- a/src/pages/guides/hosting/netlify.md +++ b/src/pages/guides/hosting/netlify.md @@ -6,3 +6,38 @@ tocHeading: 2 --- # Netlify + +Greenwood can be deployed to [**Netlify**](https://www.netlify.com/) for either static hosting and / or serverless hosting. Just connect your [git repository](https://docs.netlify.com/git/overview/) and you're done! + +> You can see a complete hybrid project example in our [demonstration repo](https://github.com/ProjectEvergreen/greenwood-demo-adapter-netlify). + +## Static Hosting + +For static hosting, you can apply any build customization [through a _netlify.toml_](https://docs.netlify.com/configure-builds/file-based-configuration/) or the Netlify UI accordingly. + +```toml +[build] + publish = "public/" + command = "npm run build" + +[build.processing] + skip_processing = true + +[build.environment] + NODE_VERSION = "18.x" +} +``` + +## Serverless + +For serverless hosting of SSR pages and API routes you can read about and install [our adapter plugin](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-adapter-netlify) and then add it to your _greenwood.config.js_. + +```js +import { greenwoodPluginAdapterNetlify } from '@greenwood/plugin-adapter-netlify'; + +export default { + plugins: [ + greenwoodPluginAdapterNetlify() + ] +} +``` \ No newline at end of file diff --git a/src/pages/guides/hosting/vercel.md b/src/pages/guides/hosting/vercel.md index 8f919542..6e7e8493 100644 --- a/src/pages/guides/hosting/vercel.md +++ b/src/pages/guides/hosting/vercel.md @@ -7,68 +7,31 @@ tocHeading: 2 # Vercel -## Overview +Greenwood can be deployed to [**Vercel**](https://vercel.com/) for either static hosting and / or serverless hosting. Just connect your [git repository](https://vercel.com/docs/deployments/git) and you're done! -## Building - -Generally you will want to setup build (**npm**) scripts for running Greenwood and other related tasks revelant to your project. These can also be configured with the hosting provider of your choice. - -By default when running `greenwood build`, all your build assets will be output into a directory called _public/_ and will include all your static HTML and assets (JS, CSS, images) as well as any SSR pages and API routes. - -```shell -# common output -public/ - g -src/ -greenwood.config.js -``` - -> If you want to configure any of these options, you can either do so in the Vercel dashboard or create a custom _vercel.json_ +> You can see a complete hybrid project example in our [demonstration repo](https://github.com/ProjectEvergreen/greenwood-demo-adapter-vercel). ## Static Hosting -By default deploying a static site should not require anything other than making sure Vercel is pointed to a - -## Self Hosting +For static hosting, just connect your repository to Vercel (or upload files) you can apply any build customization [through _vercel.json_](https://vercel.com/docs/projects/project-configuration) or the Vercel UI accordingly. -## Serverless - -## Docker - -Greenwood works great with Vercel static and serverless function hosting. - -> Support for Edge functions is not supported yet. - -## Static Hosting +```json +{ + "outputDirectory": "./public", + "buildCommand": "npm run build" +} +``` ## Serverless -Requires an account with [Vercel](https://vercel.com/) linked to your GitHub account. If you need to sign up, the process is simple as is the deployment. - -After you have created your Greenwood project and pushed your code to a GitHub repo... - -1. Log in to your Vercel account and proceed to the dashboard. - -1. Click the **Import Project** button - -1. In the **From Git Repository** block, click **continue** - -1. The default tab is for GitHub (what we will be using), GitLab & Bitbucket are also supported but not tested for this guide. - -1. Click **Import Project from GitHub** - -1. Find an click **select** next to the relevant Repository, then click **Import** - -1. Next you can change the projects name (optional) then click **continue** - -1. the next screen asks for the path to the root directory, leave blank for the root and click **continue** - -1. next, the selection for framework should be automatically set to **other**, if not select it - -1. In **build command** enter `yarn build` - -1. In **output directory** enter `public` +For serverless hosting of SSR pages and API routes you can read about and install [our adapter plugin](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-adapter-vercel) and then add it to your _greenwood.config.js_. -1. Click **deploy** +```js +import { greenwoodPluginAdapterVercel } from '@greenwood/plugin-adapter-vercel'; -Done. It will start building your first deployment and will update each time you make changes to the master branch of your repo. +export default { + plugins: [ + greenwoodPluginAdapterVercel() + ] +} +``` \ No newline at end of file diff --git a/src/styles/blog.css b/src/styles/blog.css index 30c78bae..2ccab017 100644 --- a/src/styles/blog.css +++ b/src/styles/blog.css @@ -49,13 +49,6 @@ padding: var(--size-1); } -.page-content blockquote { - padding: var(--size-1); - background-color: var(--color-accent) !important; - border-left: var(--size-1) solid var(--color-secondary); - font-style: italic; -} - .publishDate { font-size: var(--font-size-1); display: block; diff --git a/src/styles/theme.css b/src/styles/theme.css index 9f763c3d..14df479e 100644 --- a/src/styles/theme.css +++ b/src/styles/theme.css @@ -149,3 +149,10 @@ li { text-decoration: underline; } } + +.page-content blockquote { + padding: var(--size-1); + background-color: var(--color-accent) !important; + border-left: var(--size-1) solid var(--color-secondary); + font-style: italic; +} From 072b4d1bb887b05b92101721152f4bd3ead68afe Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sun, 15 Sep 2024 19:05:58 -0400 Subject: [PATCH 09/50] formatting --- src/pages/guides/hosting/aws.md | 124 ++++++++------- src/pages/guides/hosting/cloudflare.md | 149 +++++++++--------- .../hosting/{github-pages.md => github.md} | 108 ++++++------- src/pages/guides/hosting/index.md | 3 +- src/pages/guides/hosting/netlify.md | 12 +- src/pages/guides/hosting/vercel.md | 12 +- 6 files changed, 206 insertions(+), 202 deletions(-) rename src/pages/guides/hosting/{github-pages.md => github.md} (65%) diff --git a/src/pages/guides/hosting/aws.md b/src/pages/guides/hosting/aws.md index 90866c8f..a7f5d5a2 100644 --- a/src/pages/guides/hosting/aws.md +++ b/src/pages/guides/hosting/aws.md @@ -8,7 +8,7 @@ tocHeading: 2 # AWS -Greenwood can be automatically deployed to [**AWS**](https://aws.amazon.com/) for static hosting using [**S3**](https://aws.amazon.com/s3/) and [**Cloudfront**](https://aws.amazon.com/cloudfront/) with GitHub Actions. This requires having an **AWS** account. +Greenwood can be automatically deployed to [**AWS**](https://aws.amazon.com/) for static hosting using [**S3**](https://aws.amazon.com/s3/) and [**Cloudfront**](https://aws.amazon.com/cloudfront/) with GitHub Actions. This requires having an **AWS** account. > There is no adapter plugin yet for serverless hosting, though it is on [our roadmap](https://github.com/ProjectEvergreen/greenwood/issues/1142). @@ -20,20 +20,20 @@ Greenwood can be automatically deployed to [**AWS**](https://aws.amazon.com/) fo 1. Check "Use this bucket to host a website" and enter `index.html` to the "index document" input 1. Go to "Permissions" tab and edit "Block Public Access" to turn those off and save 1. Still in (or click on) the "Permissions" tab, click "Bucket Policy" and add the following snippet (putting in your buckets name) and save. - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "PublicReadGetObject", - "Effect": "Allow", - "Principal": "*", - "Action": "s3:GetObject", - "Resource": "arn:aws:s3:::your-bucket-name-here/*" - } - ] - } - ``` + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "PublicReadGetObject", + "Effect": "Allow", + "Principal": "*", + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::your-bucket-name-here/*" + } + ] + } + ``` Your site will now be at the address visible in the "Static website hosting" card. @@ -50,50 +50,52 @@ Your site is now hosted on S3 with a CloudFront CDN! 🏆 ## Enable GitHub Actions 1. You'll want to add your AWS Secret Access Key (`AWS_SECRET_ACCESS_KEY`) and Access Key ID (`AWS_SECRET_ACCESS_KEY_ID`) to the repositories as [GitHub secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). -1. At the root of your repo add a GitHub Action called _.github/workflows/deploy.yml_ and adapt as needed for your own branch and package manager. (Using npm here) - ```yml - name: Upload Website to S3 - - on: - push: - branches: - # configure your branch accordingly - - main - - jobs: - build: - runs-on: ubuntu-20.04 - - # match to your version of NodeJS - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 - with: - node-version: 18.20.2 - - - name: Navigate to repo - run: | - cd $GITHUB_WORKSPACE - - # or replace with yarn, pnpm, etc - - name: Install Dependencies - run: | - npm ci - - # use your greenwood build script - - name: Run Build - run: | - npm run build - - - name: Deploy to S3 - uses: opspresso/action-s3-sync@master - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_SECRET_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: "us-east-2" - FROM_PATH: "./public" - # your target s3 bucket name goes here - DEST_PATH: "s3://your-s3-bucket-name" - OPTIONS: "--acl public-read" - ``` +1. At the root of your repo add a GitHub Action called _.github/workflows/deploy.yml_ and adapt as needed for your own branch and package manager. (Using npm here) + + ```yml + name: Upload Website to S3 + + on: + push: + branches: + # configure your branch accordingly + - main + + jobs: + build: + runs-on: ubuntu-20.04 + + # match to your version of NodeJS + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v3 + with: + node-version: 18.20.2 + + - name: Navigate to repo + run: | + cd $GITHUB_WORKSPACE + + # or replace with yarn, pnpm, etc + - name: Install Dependencies + run: | + npm ci + + # use your greenwood build script + - name: Run Build + run: | + npm run build + + - name: Deploy to S3 + uses: opspresso/action-s3-sync@master + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_SECRET_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: "us-east-2" + FROM_PATH: "./public" + # your target s3 bucket name goes here + DEST_PATH: "s3://your-s3-bucket-name" + OPTIONS: "--acl public-read" + ``` + 1. Push your updates to your repo and the action will begin automatically. diff --git a/src/pages/guides/hosting/cloudflare.md b/src/pages/guides/hosting/cloudflare.md index 13b03245..3829fe61 100644 --- a/src/pages/guides/hosting/cloudflare.md +++ b/src/pages/guides/hosting/cloudflare.md @@ -7,46 +7,47 @@ tocHeading: 2 # Cloudflare -[Cloudflare Workers](https://workers.cloudflare.com/) is an excellent option as a CDN for deploying and hosting your static Greenwood site. This will require a paid account with Cloudflare with a linked domain for custom domain and subdomains. +[Cloudflare Workers](https://workers.cloudflare.com/) is an excellent option as a CDN for deploying and hosting your static Greenwood site. This will require a paid account with Cloudflare with a linked domain for custom domain and subdomains. > There is no adapter plugin yet for serverless hosting, though it is on [our roadmap](https://github.com/ProjectEvergreen/greenwood/issues/1143). - ## Setup 1. You will need to globally install Cloudflare's CLI tool _Wrangler_ - ```shell - npm install -g global @cloudflare/wrangler - ``` + ```shell + npm install -g global @cloudflare/wrangler + ``` 1. In the root of your project directory initialize _Wrangler_ - ```shell - wrangler init - ``` + ```shell + wrangler init + ``` 1. Authenticate your cloudflare account with: - ```shell - wrangler config - ``` -1. A _wrangler.toml_ file will be generated at the root of your project directory. You will want to update accordingly: - ```toml - name = "demo" # workers.dev subdomain name automatically named for the directory - type = "webpack" - account_id = "abcd12345...." # your account id - - [env.production] - workers_dev = true - - [site] - bucket = "./public" # where greenwood generated the compiled code - entry-point = "workers-site" - ``` + ```shell + wrangler config + ``` +1. A _wrangler.toml_ file will be generated at the root of your project directory. You will want to update accordingly: + + ```toml + name = "demo" # workers.dev subdomain name automatically named for the directory + type = "webpack" + account_id = "abcd12345...." # your account id + + [env.production] + workers_dev = true + + [site] + bucket = "./public" # where greenwood generated the compiled code + entry-point = "workers-site" + ``` + 1. Run a Greenwood build - ```shell - greenwood build - ``` + ```shell + greenwood build + ``` 1. Then push your code to Cloudflare workers - ```shell - wrangler publish - ``` + ```shell + wrangler publish + ``` When completed a url for workers subdomain will be printed in your terminal. 🏆 @@ -56,48 +57,50 @@ To have automatic deployments whenever you push updates to your repo, you will n 1. Add the email address (`CF_WORKERS_EMAIL`) and API key (`CF_WORKERS_KEY`) associated with your account to the repositories [GitHub secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). 1. At the root of your project add '.github/workflows/deploy.yml' - ```yml - name: Deploy Cloudflare Workers - - # configure your branch accordingly - on: - push: - branches: - # configure your branch accordingly - - main - - jobs: - build: - runs-on: ubuntu-20.04 - - # match to your version of NodeJS - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 - with: - node-version: 18.20.2 - - - name: Navigate to repo - run: | - cd $GITHUB_WORKSPACE - - # or replace with yarn, pnpm, etc - - name: Install Dependencies - run: | - npm ci - - # use your greenwood build script - - name: Run Build - run: | - npm run build - - - name: Publish to Cloudflare Workers - uses: cloudflare/wrangler-action@1.1.0 - with: - apiKey: ${{ secrets.CF_WORKERS_KEY }} - email: ${{ secrets.CF_WORKERS_EMAIL }} - environment: "production" - ``` + + ```yml + name: Deploy Cloudflare Workers + + # configure your branch accordingly + on: + push: + branches: + # configure your branch accordingly + - main + + jobs: + build: + runs-on: ubuntu-20.04 + + # match to your version of NodeJS + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v3 + with: + node-version: 18.20.2 + + - name: Navigate to repo + run: | + cd $GITHUB_WORKSPACE + + # or replace with yarn, pnpm, etc + - name: Install Dependencies + run: | + npm ci + + # use your greenwood build script + - name: Run Build + run: | + npm run build + + - name: Publish to Cloudflare Workers + uses: cloudflare/wrangler-action@1.1.0 + with: + apiKey: ${{ secrets.CF_WORKERS_KEY }} + email: ${{ secrets.CF_WORKERS_EMAIL }} + environment: "production" + ``` + 1. Push your updates to your repo and the action will begin automatically. -This will create a new worker with the name from the _.toml_ file -production (i.e. &&demo-production&&). Make sure your custom url is attached to this worker. \ No newline at end of file +This will create a new worker with the name from the _.toml_ file -production (i.e. &&demo-production&&). Make sure your custom url is attached to this worker. diff --git a/src/pages/guides/hosting/github-pages.md b/src/pages/guides/hosting/github.md similarity index 65% rename from src/pages/guides/hosting/github-pages.md rename to src/pages/guides/hosting/github.md index 660c074e..89de5025 100644 --- a/src/pages/guides/hosting/github-pages.md +++ b/src/pages/guides/hosting/github.md @@ -1,11 +1,11 @@ --- -title: GitHub Pages +title: GitHub layout: guides order: 5 tocHeading: 2 --- -# GitHub Pages +# GitHub Greenwood can easily be configured to work with [GitHub Pages](https://pages.github.com/) and GitHub Actions. With this guide, anytime you push to the designated branch, GitHub will automatically build your project with Greenwood and publish it to GitHub Pages. @@ -17,63 +17,65 @@ Following the steps [outlined here](https://pages.github.com/), first make sure 1. Created a repo in the format `.github.io` or `.github.io/` 1. If using `.github.io/`, make sure to set Greenwood's [base path](/docs/configuration/#base-peth) configuration to match - ```js - export default { - basePath: '/repo-name' - } - ``` + ```js + export default { + basePath: "/repo-name", + }; + ``` 1. Get your project all setup in your repository. 1. If you don't have a build script, let's add one to _package.json_ to use in our GitHub Action - ```json - { - "scripts": { - "build": "greenwood build" - } - } - ``` + ```json + { + "scripts": { + "build": "greenwood build" + } + } + ``` ## Setup 1. Create a file called _.github/workflows/gh-pages.yml_ in your repo 1. Now add this GitHub Action, _making sure to use the correct branch name for your project_; **_master_, _main_**, etc. (We're leveraging [this action](https://github.com/marketplace/actions/github-pages-action) at the end for the actual auto deploy) - ```yml - name: Deploy GitHub Pages - - on: - push: - branches: - # configure your branch accordingly - - main - - jobs: - build-and-deploy: - runs-on: ubuntu-20.04 - - # match to your version of NodeJS - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 - with: - node-version: 18.20.2 - - # or replace with yarn, pnpm, etc - - name: Install Dependencies - run: | - npm ci - - # use your greenwood build script - - name: Run Build - run: | - npm run build - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - # change the branch name to match your repo - if: ${{ github.ref == 'refs/heads/main' }} - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./public - ``` + + ```yml + name: Deploy GitHub Pages + + on: + push: + branches: + # configure your branch accordingly + - main + + jobs: + build-and-deploy: + runs-on: ubuntu-20.04 + + # match to your version of NodeJS + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v3 + with: + node-version: 18.20.2 + + # or replace with yarn, pnpm, etc + - name: Install Dependencies + run: | + npm ci + + # use your greenwood build script + - name: Run Build + run: | + npm run build + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + # change the branch name to match your repo + if: ${{ github.ref == 'refs/heads/main' }} + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public + ``` + 1. Now `git` commit that and push it to your repo! If all was successful, you should now see a [`gh-pages` branch](https://github.com/ProjectEvergreen/projectevergreen.github.io/tree/gh-pages) in your repo with the output of the _public/_ directory committed to it. @@ -92,4 +94,4 @@ Now, everything should be setup so that on future pushes to the branch specified ![github pages branch](/assets/guides/gh-pages-branch-commits.png) -Congrats, enjoy working on your website!! 🥳 \ No newline at end of file +Congrats, enjoy working on your website!! 🥳 diff --git a/src/pages/guides/hosting/index.md b/src/pages/guides/hosting/index.md index fd9a589b..68f17f0f 100644 --- a/src/pages/guides/hosting/index.md +++ b/src/pages/guides/hosting/index.md @@ -7,7 +7,8 @@ tocHeading: 2

      Hosting

      - The guides within this section cover some of the hosting options you can use to build and deploy your Greenwood project. Below is a an overview of the deploy targets available for a Greenwood site, with additional target specific pages in the sidebar. +The guides within this section cover some of the hosting options you can use to build and deploy your Greenwood project. Below is a an overview of the deploy targets available for a Greenwood site, with additional target specific pages in the sidebar. +
      ## Building diff --git a/src/pages/guides/hosting/netlify.md b/src/pages/guides/hosting/netlify.md index 75919866..6f5e151c 100644 --- a/src/pages/guides/hosting/netlify.md +++ b/src/pages/guides/hosting/netlify.md @@ -7,7 +7,7 @@ tocHeading: 2 # Netlify -Greenwood can be deployed to [**Netlify**](https://www.netlify.com/) for either static hosting and / or serverless hosting. Just connect your [git repository](https://docs.netlify.com/git/overview/) and you're done! +Greenwood can be deployed to [**Netlify**](https://www.netlify.com/) for either static hosting and / or serverless hosting. Just connect your [git repository](https://docs.netlify.com/git/overview/) and you're done! > You can see a complete hybrid project example in our [demonstration repo](https://github.com/ProjectEvergreen/greenwood-demo-adapter-netlify). @@ -33,11 +33,9 @@ For static hosting, you can apply any build customization [through a _netlify.to For serverless hosting of SSR pages and API routes you can read about and install [our adapter plugin](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-adapter-netlify) and then add it to your _greenwood.config.js_. ```js -import { greenwoodPluginAdapterNetlify } from '@greenwood/plugin-adapter-netlify'; +import { greenwoodPluginAdapterNetlify } from "@greenwood/plugin-adapter-netlify"; export default { - plugins: [ - greenwoodPluginAdapterNetlify() - ] -} -``` \ No newline at end of file + plugins: [greenwoodPluginAdapterNetlify()], +}; +``` diff --git a/src/pages/guides/hosting/vercel.md b/src/pages/guides/hosting/vercel.md index 6e7e8493..8e135279 100644 --- a/src/pages/guides/hosting/vercel.md +++ b/src/pages/guides/hosting/vercel.md @@ -7,7 +7,7 @@ tocHeading: 2 # Vercel -Greenwood can be deployed to [**Vercel**](https://vercel.com/) for either static hosting and / or serverless hosting. Just connect your [git repository](https://vercel.com/docs/deployments/git) and you're done! +Greenwood can be deployed to [**Vercel**](https://vercel.com/) for either static hosting and / or serverless hosting. Just connect your [git repository](https://vercel.com/docs/deployments/git) and you're done! > You can see a complete hybrid project example in our [demonstration repo](https://github.com/ProjectEvergreen/greenwood-demo-adapter-vercel). @@ -27,11 +27,9 @@ For static hosting, just connect your repository to Vercel (or upload files) you For serverless hosting of SSR pages and API routes you can read about and install [our adapter plugin](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-adapter-vercel) and then add it to your _greenwood.config.js_. ```js -import { greenwoodPluginAdapterVercel } from '@greenwood/plugin-adapter-vercel'; +import { greenwoodPluginAdapterVercel } from "@greenwood/plugin-adapter-vercel"; export default { - plugins: [ - greenwoodPluginAdapterVercel() - ] -} -``` \ No newline at end of file + plugins: [greenwoodPluginAdapterVercel()], +}; +``` From 332bd49db691fbc65e56bb2dfc82689db44f23e0 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sun, 15 Sep 2024 21:57:54 -0400 Subject: [PATCH 10/50] remaining ecosystem docs --- src/pages/guides/ecosystem/htmx.md | 66 +++++- src/pages/guides/ecosystem/index.md | 54 ++++- src/pages/guides/ecosystem/lit.md | 66 ++++++ src/pages/guides/ecosystem/storybook.md | 204 ++++++++++++++++++ src/pages/guides/ecosystem/tailwind.md | 106 +++++++++ src/pages/guides/ecosystem/web-test-runner.md | 168 +++++++++++++++ src/pages/guides/hosting/index.md | 5 +- vite.config.js | 6 +- web-test-runner.config.js | 4 +- 9 files changed, 669 insertions(+), 10 deletions(-) create mode 100644 src/pages/guides/ecosystem/storybook.md create mode 100644 src/pages/guides/ecosystem/tailwind.md create mode 100644 src/pages/guides/ecosystem/web-test-runner.md diff --git a/src/pages/guides/ecosystem/htmx.md b/src/pages/guides/ecosystem/htmx.md index 153584ef..a599fa13 100644 --- a/src/pages/guides/ecosystem/htmx.md +++ b/src/pages/guides/ecosystem/htmx.md @@ -1,9 +1,73 @@ --- title: htmx -label: HTMX +label: htmx layout: guides order: 2 tocHeading: 2 --- # htmx + +[**htmx**](https://htmx.org/) is a JavaScript library for enhancing existing HTML elements with ["hypermedia controls"](https://htmx.org/essays/hypermedia-clients/), which effectively allows any element to make requests (like a `
      ` or ``) and update the page with HTML as needed. + +> You can see a complete hybrid project example in this [demonstration repo](https://github.com/thescientist13/greenwood-htmx/). + +## Installation + +As with most libraries, just install **htmx.org** with your favorite package manager as a dependency. + +```shell +npm i htmx.org +``` + +## Example + +As a basic example, let's create a `` in the client side that can send a request to an API route as `FormData` and send a custom HTML response back. + +### Frontend + +First we'll create our frontend including htmx in a ` + + + + + + + + +

      + + + +``` + +### Backend + +Now let's add our API endpoint. +```js +// src/pages/api/greeting.js +export async function handler(request) { + const formData = await request.formData(); + const name = formData.has('name') ? formData.get('name') : 'Greenwood'; + const body = `Hello ${name}! 👋`; + + return new Response(body, { + headers: new Headers({ + 'Content-Type': 'text/html' + }) + }); +} +``` + +Now when the form is submitted, htmx will make a request to our backend API and output the returned HTML to the page. 🎯 \ No newline at end of file diff --git a/src/pages/guides/ecosystem/index.md b/src/pages/guides/ecosystem/index.md index 850e75b0..2e95f337 100644 --- a/src/pages/guides/ecosystem/index.md +++ b/src/pages/guides/ecosystem/index.md @@ -3,6 +3,56 @@ order: 3 layout: guides --- -# Ecosystem + +
      +

      Ecosystem

      -In general, for client side, just an `npm install` and import maps... + The guides within this section cover examples of using popular libraries and tools that are based on or work well with developing for web standards, and in particular Web Components, when using Greenwood. As Greenwood's philosophy it to stay out of your way, as long as the tool embraces web standards, it should just work out of the box. +
      + + +In most cases an `npm install` should be all you need to use any third party library and including it in a ` + + + +``` + +Or to use something CSS based like [**Open Props**](https://open-props.style), simply install it from **npm** and reference the CSS file through a `` tag. Easy! + +```shell +npm i open-props +``` + +```html + + + + + + + + + +

      Welcome to my website!

      + + +``` \ No newline at end of file diff --git a/src/pages/guides/ecosystem/lit.md b/src/pages/guides/ecosystem/lit.md index 47556149..160ffd81 100644 --- a/src/pages/guides/ecosystem/lit.md +++ b/src/pages/guides/ecosystem/lit.md @@ -6,3 +6,69 @@ tocHeading: 2 --- # Lit + +[**Lit**](https://lit.dev/) is builds on top Web Components standards adding additional developer experience ergonomics to authoring Web Components like reactivity, declarative templates and overall helping to reduce boilerplate. Lit also has support for SSR (server-side rendering), which Greenwood supports through a plugin. + +> You can see a complete hybrid project example in this [demonstration repo](https://github.com/thescientist13/greenwood-lit-ssr). + +## Installation + +As with most libraries, just install **lit** with your favorite package manager as a dependency. + +```shell +npm i lit +``` + +Now you can start writing Lit based Web Components! + +```html + + + + + + + + + + + +``` + +That's it! + +## SSR + +For [Lit and SSR](https://lit.dev/docs/ssr/overview/), you can enable this capability by installing a Greenwood [plugin](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-renderer-lit) and adding it to your _greenwood.config.js_. + +```js +import { greenwoodPluginRendererLit } from '@greenwood/plugin-renderer-lit'; + +export default { + plugins: [ + greenwoodPluginRendererLit() + ] +} +``` + +> Please see [the README](https://github.com/ProjectEvergreen/greenwood/blob/master/packages/plugin-renderer-lit/README.md) to learn more about full usage details and caveats. diff --git a/src/pages/guides/ecosystem/storybook.md b/src/pages/guides/ecosystem/storybook.md new file mode 100644 index 00000000..48433412 --- /dev/null +++ b/src/pages/guides/ecosystem/storybook.md @@ -0,0 +1,204 @@ +--- +layout: guides +order: 4 +tocHeading: 2 +--- + +# Storybook + +[**Storybook**](https://storybook.js.org/) is a developer tool created for authoring components in isolation. This guide will also cover how to customize the runner when using custom Greenwood plugins. + +> You can see an example (this website's own repo!) [here](https://github.com/ProjectEvergreen/www.greenwoodjs.dev). + +## Installation + +We recommend using the [Storybook CLI](https://storybook.js.org/docs/get-started/instal) to setup a project from scratch. + +```shell +npx storybook@latest init +``` + +As part of the prompts, we suggest the following answers to project type (**web_components**) and builder (**Vite**). + +```shell +✔ Do you want to manually choose a Storybook project type to install? … yes +? Please choose a project type from the following list: › - Use arrow-keys. Return to submit. + ↑ webpack_react + nextjs + vue3 + angular + ember +❯ web_components + html + qwik + preact + ↓ svelte + +We were not able to detect the right builder for your project. Please select one: › - Use arrow-keys. Return to submit. +❯ Vite + Webpack 5 +``` + +To help with resolving static assets, you'll want to configure [`staticDirs`](https://storybook.js.org/docs/api/main-config/main-config-static-dirs) in your _.storybook/main.js_ to point to your Greenwood workspace. + +```js +/** @type { import('@storybook/web-components-vite').StorybookConfig } */ +const config = { + //... + + staticDirs: ["../src"], +}; + +export default config; +``` + +## Vite Config + +Additionally, we'll need to create a _vite.config.js_ config file and provide a [custom plugin](https://vitejs.dev/guide/api-plugin) to handle Import Attributes as [Vite does not support them](https://github.com/vitejs/vite/issues/14674). + +```js +import { defineConfig } from "vite"; +import fs from "fs/promises"; +import path from "path"; +import { greenwoodPluginStandardCss } from "@greenwood/cli/src/plugins/resource/plugin-standard-css.js"; + +// dependency inject Greenwood's internal context +const compilation = { + context: { + projectDirectory: import.meta.url, + }, +}; +const standardCssResource = greenwoodPluginStandardCss.provider(compilation); + +function transformConstructableStylesheetsPlugin() { + return { + name: "transform-constructable-stylesheets", + enforce: "pre", + resolveId: (id, importer) => { + if ( + // you'll need to configure this `importer` line to the location of your own components + importer?.indexOf("/src/components/") >= 0 && + id.endsWith(".css") && + !id.endsWith(".module.css") + ) { + // add .type so Constructable Stylesheets are not precessed by Vite's default pipeline + return path.join(path.dirname(importer), `${id}.type`); + } + }, + load: async (id) => { + if (id.endsWith(".css.type")) { + const filename = id.slice(0, -5); + const contents = await fs.readFile(filename, "utf-8"); + const url = new URL(`file://${id.replace(".type", "")}`); + // "coerce" native constructable stylesheets into inline JS so Vite / Rollup do not complain + const request = new Request(url, { + headers: { + Accept: "text/javascript", + }, + }); + const response = await standardCssResource.intercept(url, request, new Response(contents)); + const body = await response.text(); + + return body; + } + }, + }; +} + +export default defineConfig({ + plugins: [ + transformConstructableStylesheetsPlugin() + ] +}); +``` + +Phew, should be all set now. 😅 + +## Custom Resources + +If you're using one of Greenwood's [resource plugins](/docs/plugins/), you'll need to augment the custom _vite.config.js_ so it can leverage the Greenwood plugins your using to automatically to handle these custom transformations. + +For example, if you're using Greenwood's [Raw Plugin](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-raw), you'll need to add a plugin transformation and stub out the signature. + +```js +import { defineConfig } from "vite"; +import fs from "fs/promises"; +import path from "path"; +import { greenwoodPluginStandardCss } from "@greenwood/cli/src/plugins/resource/plugin-standard-css.js"; +// 1) import the greenwood plugin +import { greenwoodPluginImportRaw } from "@greenwood/plugin-import-raw"; + +const compilation = { + context: { + projectDirectory: import.meta.url, + }, +}; +const standardCssResource = greenwoodPluginStandardCss.provider(compilation); +// 2) initialize it +const rawResource = greenwoodPluginImportRaw()[0].provider(compilation); + +function transformConstructableStylesheetsPlugin() { + // ... +} + +// 3) customize Vite +function transformRawImports() { + return { + name: "transform-raw-imports", + enforce: "pre", + load: async (id) => { + if (id.endsWith("?type=raw")) { + const filename = id.slice(0, -9); + const contents = await fs.readFile(filename, "utf-8"); + const response = await rawResource.intercept(null, null, new Response(contents)); + const body = await response.text(); + + return body; + } + }, + }; +} + +export default defineConfig({ + plugins: [ + transformConstructableStylesheetsPlugin(), + // 4) add it the plugins option + transformRawImports() + ], +}); +``` + +## Usage + +With everything install and configured, you should now be good to start writing your first story ! 🏆 + +```js +// src/components/footer/footer.js +import greenwoodLogo from "./assets/greenwood-logo-full.svg?type=raw"; + +export default class Footer extends HTMLElement { + connectedCallback() { + this.innerHTML = ` +
      +

      Greenwood

      + ${greenwoodLogo} +
      + `; + } +} + +customElements.define("app-footer", Footer); +``` + +```js +// src/components/footer/footer.stories.js +import "./footer.js"; + +export default { + title: "Components/Footer", +}; + +const Template = () => ""; + +export const Primary = Template.bind({}); +``` \ No newline at end of file diff --git a/src/pages/guides/ecosystem/tailwind.md b/src/pages/guides/ecosystem/tailwind.md new file mode 100644 index 00000000..5c80c939 --- /dev/null +++ b/src/pages/guides/ecosystem/tailwind.md @@ -0,0 +1,106 @@ +--- +layout: guides +order: 3 +tocHeading: 2 +--- + +# Tailwind + +[**Tailwind**](https://tailwindcss.com/) is a CSS utility library providing all the modern features and capabilities of CSS in a compact, composable, and efficient way. + +> You can see an example in this [demonstration repo](https://github.com/AnalogStudiosRI/www.tuesdaystunes.tv). + +## Installation + +As Tailwind is a PostCSS plugin, you'll need to a couple extra steps to get things setup, but for the most part you can follow the steps listed on the [Tailwind website](https://tailwindcss.com/docs/installation/using-postcss). + +1. Let's install Tailwind and needed dependencies into our project, including our [PostCSS plugin](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-postcss) + + ```shell + npm install -D @greenwood/plugin-postcss tailwindcss autoprefixer + ``` +1. Now run the Tailwind CLI + + ```shell + npx tailwindcss init + ``` +1. Create two PostCSS configuration files (needed in Greenwood for ESM / CJS interop) + + ```js + // postcss.config.cjs (CJS) + module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + } + } + + // postcss.config.mjs (ESM) + export default { + plugins: [ + (await import('tailwindcss')).default, + (await import('autoprefixer')).default + ] + } + ``` +1. Create a _tailwind.config.js_ file and configure accordingly for your project + + ```js + /** @type {import('tailwindcss').Config} */ + export default { + content: ['./src/**/*.{html,js}'], + theme: {}, + plugins: [] + }; + ``` +1. Add the PostCSS plugin to your _greenwood.config.js_ + + ```js + import { greenwoodPluginPostCss } from '@greenwood/plugin-postcss'; + + export default { + plugins: [ + greenwoodPluginPostCss() + ] + }; + ``` + +## Usage + +1. Now you'll want to create an "entry" point CSS file to include the initial Tailwind `@import`s. + + ```css + /* src/styles/main.css */ + @tailwind base; + @tailwind components; + @tailwind utilities; + ``` +1. And include that in your layouts or pages + + ```html + + + + + + + + + + ``` + +Now you're ready to start using Tailwind! 🎯 + +```html + + + + + + + +

      Welcome to my website!

      + + + +``` diff --git a/src/pages/guides/ecosystem/web-test-runner.md b/src/pages/guides/ecosystem/web-test-runner.md new file mode 100644 index 00000000..745429d9 --- /dev/null +++ b/src/pages/guides/ecosystem/web-test-runner.md @@ -0,0 +1,168 @@ +--- +layout: guides +order: 5 +tocHeading: 2 +--- + +# Web Test Runner + +[**Web Test Runner**](https://modern-web.dev/docs/test-runner/overview/) is a developer tool created by the [Modern Web](https://modern-web.dev/) team that helps with the facilitating of testing Web Components, especially being able to test them in a browser. This guide will also cover how to customize the runner when using custom Greenwood plugins. + +> You can see an example project (this website's own repo!) [here](https://github.com/ProjectEvergreen/www.greenwoodjs.dev). + +## Installation + +For the sake of this guide, we will be covering a minimal setup but you are free to extends things as much as you need. + +1. First, let's install WTR and the junit reporter. You can use your favorite package manager + + ```shell + npm i -D @web/test-runner @web/test-runner-junit-reporter + ``` +1. You'll also want something like [**chai**](https://www.chaijs.com/) to write your assertions with + + ```shell + npm i -D @esm-bundle/chai + ``` +1. Next, create a basic _web-test-runner.config.js_ configuration file + + ```js + import path from 'path'; + import { defaultReporter } from "@web/test-runner"; + import { junitReporter } from "@web/test-runner-junit-reporter"; + + export default { + // customize your spec pattern here + files: "./src/**/*.spec.js", + // enable this if you're using npm / node_modules + nodeResolve: true, + // optionally configure reporters and coverage + reporters: [ + defaultReporter({ reportTestResults: true, reportTestProgress: true }), + junitReporter({ + outputPath: "./reports/test-results.xml", + }), + ], + coverage: true, + coverageConfig: { + reportDir: "./reports", + }, + // we can use middleware here to resolve assets like images + // to your Greenwood workspace to fix any 404s + middleware: [ + function rewriteIndex(context, next) { + const { url } = context.request; + + if (url.indexOf("/assets") === 0) { + context.request.url = path.join(process.cwd(), "src", url); + } + + return next(); + }, + ], + }; + ``` + +## Custom Resources + +If you're using one of Greenwood's [resource plugins](/docs/plugins/), you'll need to customize WTR manually through [its plugins option](https://modern-web.dev/docs/test-runner/plugins/) so it can leverage the Greenwood plugins your using to automatically to handle these custom transformations. + + +For example, if you're using Greenwood's [Raw Plugin](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-raw), you'll need to add a plugin transformation and stub out the signature. + +```js +import path from 'path'; +import fs from 'fs/promises'; +import { defaultReporter } from "@web/test-runner"; +import { junitReporter } from "@web/test-runner-junit-reporter"; +// 1) import the greenwood plugin +import { greenwoodPluginImportRaw } from "@greenwood/plugin-import-raw"; + +// 2) initialize it +const rawResourcePlugin = greenwoodPluginImportRaw()[0].provider({}); + +export default { + // ... + + // 3) customize WTR + plugins: [{ + name: "import-raw", + async transform(context) { + const { url } = context.request; + + if (url.endsWith("?type=raw")) { + const contents = await fs.readFile(new URL(`.${url}`, import.meta.url), "utf-8"); + const response = await rawResourcePlugin.intercept(null, null, new Response(contents)); + const body = await response.text(); + + return { + body, + headers: { "Content-Type": "application/javascript" }, + }; + } + }, + }], +}; +``` + +## Usage + +With everything install and configured, you should now be good to start writing your tests! 🏆 + +```js +// src/components/footer/footer.js +import greenwoodLogo from "./assets/greenwood-logo-full.svg?type=raw"; + +export default class Footer extends HTMLElement { + connectedCallback() { + this.innerHTML = ` +
      +

      Greenwood

      + ${greenwoodLogo} +
      + `; + } +} + +customElements.define("app-footer", Footer); +``` + +```js +// src/components/footer/footer.spec.js +describe("Components/Footer", () => { + let footer; + + before(async () => { + footer = document.createElement("app-footer"); + document.body.appendChild(footer); + + await footer.updateComplete; + }); + + describe("Default Behavior", () => { + it("should not be null", () => { + expect(footer).not.equal(undefined); + expect(footer.querySelectorAll("footer").length).equal(1); + }); + + it("should have the expected footer heading text", () => { + const header = footer.querySelectorAll("footer h4"); + + expect(header.length).equal(1); + expect(header[0].textContent).to.equal("Greenwood"); + }); + + it("should have the expected logo SVG", () => { + const logo = footer.querySelectorAll("footer svg"); + + expect(logo.length).equal(1); + expect(logo[0]).not.equal(undefined); + }); + }); + + after(() => { + footer.remove(); + footer = null; + }); +}); +``` diff --git a/src/pages/guides/hosting/index.md b/src/pages/guides/hosting/index.md index 68f17f0f..bdcfa4f1 100644 --- a/src/pages/guides/hosting/index.md +++ b/src/pages/guides/hosting/index.md @@ -4,12 +4,13 @@ order: 2 tocHeading: 2 --- +

      Hosting

      -The guides within this section cover some of the hosting options you can use to build and deploy your Greenwood project. Below is a an overview of the deploy targets available for a Greenwood site, with additional target specific pages in the sidebar. - + The guides within this section cover some of the hosting options you can use to build and deploy your Greenwood project. Below is a an overview of the deploy targets available for a Greenwood site, with additional target specific pages in the sidebar.
      + ## Building diff --git a/vite.config.js b/vite.config.js index 5fc733f0..98b7badd 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,5 +1,5 @@ import { defineConfig } from "vite"; -import fs from "fs"; +import fs from "fs/promises"; import path from "path"; import { greenwoodPluginStandardCss } from "@greenwood/cli/src/plugins/resource/plugin-standard-css.js"; import { greenwoodPluginImportRaw } from "@greenwood/plugin-import-raw"; @@ -36,7 +36,7 @@ function transformConstructableStylesheetsPlugin() { load: async (id) => { if (id.endsWith(".css.type")) { const filename = id.slice(0, -5); - const contents = fs.readFileSync(filename, "utf-8"); + const contents = await fs.readFile(filename, "utf-8"); const url = new URL(`file://${id.replace(".type", "")}`); // "coerce" native conststructable stylesheets into inline JS so Vite / Rollup do not complain const request = new Request(url, { @@ -60,7 +60,7 @@ function transformRawImports() { load: async (id) => { if (id.endsWith("?type=raw")) { const filename = id.slice(0, -9); - const contents = fs.readFileSync(filename, "utf-8"); + const contents = await fs.readFile(filename, "utf-8"); const response = await rawResource.intercept(null, null, new Response(contents)); const body = await response.text(); diff --git a/web-test-runner.config.js b/web-test-runner.config.js index 9a375a7b..964e381f 100644 --- a/web-test-runner.config.js +++ b/web-test-runner.config.js @@ -1,5 +1,5 @@ import { defaultReporter } from "@web/test-runner"; -import fs from "fs"; +import fs from "fs/promises"; import { greenwoodPluginImportRaw } from "@greenwood/plugin-import-raw"; import { junitReporter } from "@web/test-runner-junit-reporter"; import path from "path"; @@ -26,7 +26,7 @@ export default { const { url } = context.request; if (url.endsWith("?type=raw")) { - const contents = fs.readFileSync(new URL(`.${url}`, import.meta.url), "utf-8"); + const contents = await fs.readFile(new URL(`.${url}`, import.meta.url), "utf-8"); const response = await rawResource.intercept(null, null, new Response(contents)); const body = await response.text(); From 439131dac01261bd0ad0418e9c2bfb51df17d77e Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sun, 15 Sep 2024 21:59:02 -0400 Subject: [PATCH 11/50] formatting --- src/pages/guides/ecosystem/htmx.md | 19 ++- src/pages/guides/ecosystem/index.md | 21 +-- src/pages/guides/ecosystem/lit.md | 25 ++-- src/pages/guides/ecosystem/storybook.md | 12 +- src/pages/guides/ecosystem/tailwind.md | 123 +++++++++-------- src/pages/guides/ecosystem/web-test-runner.md | 129 +++++++++--------- src/pages/guides/hosting/index.md | 3 +- 7 files changed, 166 insertions(+), 166 deletions(-) diff --git a/src/pages/guides/ecosystem/htmx.md b/src/pages/guides/ecosystem/htmx.md index a599fa13..520bb64c 100644 --- a/src/pages/guides/ecosystem/htmx.md +++ b/src/pages/guides/ecosystem/htmx.md @@ -36,38 +36,35 @@ First we'll create our frontend including htmx in a ` ``` -Or to use something CSS based like [**Open Props**](https://open-props.style), simply install it from **npm** and reference the CSS file through a `` tag. Easy! +Or to use something CSS based like [**Open Props**](https://open-props.style), simply install it from **npm** and reference the CSS file through a `` tag. Easy! ```shell npm i open-props @@ -39,15 +40,15 @@ npm i open-props ```html - - - + + + @@ -55,4 +56,4 @@ npm i open-props

      Welcome to my website!

      -``` \ No newline at end of file +``` diff --git a/src/pages/guides/ecosystem/lit.md b/src/pages/guides/ecosystem/lit.md index 160ffd81..248060c5 100644 --- a/src/pages/guides/ecosystem/lit.md +++ b/src/pages/guides/ecosystem/lit.md @@ -7,7 +7,7 @@ tocHeading: 2 # Lit -[**Lit**](https://lit.dev/) is builds on top Web Components standards adding additional developer experience ergonomics to authoring Web Components like reactivity, declarative templates and overall helping to reduce boilerplate. Lit also has support for SSR (server-side rendering), which Greenwood supports through a plugin. +[**Lit**](https://lit.dev/) is builds on top Web Components standards adding additional developer experience ergonomics to authoring Web Components like reactivity, declarative templates and overall helping to reduce boilerplate. Lit also has support for SSR (server-side rendering), which Greenwood supports through a plugin. > You can see a complete hybrid project example in this [demonstration repo](https://github.com/thescientist13/greenwood-lit-ssr). @@ -25,25 +25,29 @@ Now you can start writing Lit based Web Components! @@ -51,7 +55,6 @@ Now you can start writing Lit based Web Components! - ``` @@ -62,13 +65,11 @@ That's it! For [Lit and SSR](https://lit.dev/docs/ssr/overview/), you can enable this capability by installing a Greenwood [plugin](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-renderer-lit) and adding it to your _greenwood.config.js_. ```js -import { greenwoodPluginRendererLit } from '@greenwood/plugin-renderer-lit'; +import { greenwoodPluginRendererLit } from "@greenwood/plugin-renderer-lit"; export default { - plugins: [ - greenwoodPluginRendererLit() - ] -} + plugins: [greenwoodPluginRendererLit()], +}; ``` > Please see [the README](https://github.com/ProjectEvergreen/greenwood/blob/master/packages/plugin-renderer-lit/README.md) to learn more about full usage details and caveats. diff --git a/src/pages/guides/ecosystem/storybook.md b/src/pages/guides/ecosystem/storybook.md index 48433412..e4e90e96 100644 --- a/src/pages/guides/ecosystem/storybook.md +++ b/src/pages/guides/ecosystem/storybook.md @@ -6,7 +6,7 @@ tocHeading: 2 # Storybook -[**Storybook**](https://storybook.js.org/) is a developer tool created for authoring components in isolation. This guide will also cover how to customize the runner when using custom Greenwood plugins. +[**Storybook**](https://storybook.js.org/) is a developer tool created for authoring components in isolation. This guide will also cover how to customize the runner when using custom Greenwood plugins. > You can see an example (this website's own repo!) [here](https://github.com/ProjectEvergreen/www.greenwoodjs.dev). @@ -106,13 +106,11 @@ function transformConstructableStylesheetsPlugin() { } export default defineConfig({ - plugins: [ - transformConstructableStylesheetsPlugin() - ] + plugins: [transformConstructableStylesheetsPlugin()], }); ``` -Phew, should be all set now. 😅 +Phew, should be all set now. 😅 ## Custom Resources @@ -163,7 +161,7 @@ export default defineConfig({ plugins: [ transformConstructableStylesheetsPlugin(), // 4) add it the plugins option - transformRawImports() + transformRawImports(), ], }); ``` @@ -201,4 +199,4 @@ export default { const Template = () => ""; export const Primary = Template.bind({}); -``` \ No newline at end of file +``` diff --git a/src/pages/guides/ecosystem/tailwind.md b/src/pages/guides/ecosystem/tailwind.md index 5c80c939..e7dfadaa 100644 --- a/src/pages/guides/ecosystem/tailwind.md +++ b/src/pages/guides/ecosystem/tailwind.md @@ -16,91 +16,90 @@ As Tailwind is a PostCSS plugin, you'll need to a couple extra steps to get thin 1. Let's install Tailwind and needed dependencies into our project, including our [PostCSS plugin](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-postcss) - ```shell - npm install -D @greenwood/plugin-postcss tailwindcss autoprefixer - ``` + ```shell + npm install -D @greenwood/plugin-postcss tailwindcss autoprefixer + ``` + 1. Now run the Tailwind CLI - ```shell - npx tailwindcss init - ``` + ```shell + npx tailwindcss init + ``` + 1. Create two PostCSS configuration files (needed in Greenwood for ESM / CJS interop) - ```js - // postcss.config.cjs (CJS) - module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - } - } - - // postcss.config.mjs (ESM) - export default { - plugins: [ - (await import('tailwindcss')).default, - (await import('autoprefixer')).default - ] - } - ``` + ```js + // postcss.config.cjs (CJS) + module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, + }; + + // postcss.config.mjs (ESM) + export default { + plugins: [(await import("tailwindcss")).default, (await import("autoprefixer")).default], + }; + ``` + 1. Create a _tailwind.config.js_ file and configure accordingly for your project - ```js - /** @type {import('tailwindcss').Config} */ - export default { - content: ['./src/**/*.{html,js}'], - theme: {}, - plugins: [] - }; - ``` + ```js + /** @type {import('tailwindcss').Config} */ + export default { + content: ["./src/**/*.{html,js}"], + theme: {}, + plugins: [], + }; + ``` + 1. Add the PostCSS plugin to your _greenwood.config.js_ - ```js - import { greenwoodPluginPostCss } from '@greenwood/plugin-postcss'; + ```js + import { greenwoodPluginPostCss } from "@greenwood/plugin-postcss"; - export default { - plugins: [ - greenwoodPluginPostCss() - ] - }; - ``` + export default { + plugins: [greenwoodPluginPostCss()], + }; + ``` ## Usage 1. Now you'll want to create an "entry" point CSS file to include the initial Tailwind `@import`s. - - ```css - /* src/styles/main.css */ - @tailwind base; - @tailwind components; - @tailwind utilities; - ``` + + ```css + /* src/styles/main.css */ + @tailwind base; + @tailwind components; + @tailwind utilities; + ``` + 1. And include that in your layouts or pages - - ```html - - - - - - - - - - ``` - -Now you're ready to start using Tailwind! 🎯 + + ```html + + + + + + + + + + ``` + +Now you're ready to start using Tailwind! 🎯 ```html - +

      Welcome to my website!

      - ``` diff --git a/src/pages/guides/ecosystem/web-test-runner.md b/src/pages/guides/ecosystem/web-test-runner.md index 745429d9..63bcb100 100644 --- a/src/pages/guides/ecosystem/web-test-runner.md +++ b/src/pages/guides/ecosystem/web-test-runner.md @@ -6,7 +6,7 @@ tocHeading: 2 # Web Test Runner -[**Web Test Runner**](https://modern-web.dev/docs/test-runner/overview/) is a developer tool created by the [Modern Web](https://modern-web.dev/) team that helps with the facilitating of testing Web Components, especially being able to test them in a browser. This guide will also cover how to customize the runner when using custom Greenwood plugins. +[**Web Test Runner**](https://modern-web.dev/docs/test-runner/overview/) is a developer tool created by the [Modern Web](https://modern-web.dev/) team that helps with the facilitating of testing Web Components, especially being able to test them in a browser. This guide will also cover how to customize the runner when using custom Greenwood plugins. > You can see an example project (this website's own repo!) [here](https://github.com/ProjectEvergreen/www.greenwoodjs.dev). @@ -14,65 +14,66 @@ tocHeading: 2 For the sake of this guide, we will be covering a minimal setup but you are free to extends things as much as you need. -1. First, let's install WTR and the junit reporter. You can use your favorite package manager +1. First, let's install WTR and the junit reporter. You can use your favorite package manager + + ```shell + npm i -D @web/test-runner @web/test-runner-junit-reporter + ``` - ```shell - npm i -D @web/test-runner @web/test-runner-junit-reporter - ``` 1. You'll also want something like [**chai**](https://www.chaijs.com/) to write your assertions with - ```shell - npm i -D @esm-bundle/chai - ``` + ```shell + npm i -D @esm-bundle/chai + ``` + 1. Next, create a basic _web-test-runner.config.js_ configuration file - ```js - import path from 'path'; - import { defaultReporter } from "@web/test-runner"; - import { junitReporter } from "@web/test-runner-junit-reporter"; - - export default { - // customize your spec pattern here - files: "./src/**/*.spec.js", - // enable this if you're using npm / node_modules - nodeResolve: true, - // optionally configure reporters and coverage - reporters: [ - defaultReporter({ reportTestResults: true, reportTestProgress: true }), - junitReporter({ - outputPath: "./reports/test-results.xml", - }), - ], - coverage: true, - coverageConfig: { - reportDir: "./reports", - }, - // we can use middleware here to resolve assets like images - // to your Greenwood workspace to fix any 404s - middleware: [ - function rewriteIndex(context, next) { - const { url } = context.request; - - if (url.indexOf("/assets") === 0) { - context.request.url = path.join(process.cwd(), "src", url); - } - - return next(); - }, - ], - }; - ``` + ```js + import path from "path"; + import { defaultReporter } from "@web/test-runner"; + import { junitReporter } from "@web/test-runner-junit-reporter"; + + export default { + // customize your spec pattern here + files: "./src/**/*.spec.js", + // enable this if you're using npm / node_modules + nodeResolve: true, + // optionally configure reporters and coverage + reporters: [ + defaultReporter({ reportTestResults: true, reportTestProgress: true }), + junitReporter({ + outputPath: "./reports/test-results.xml", + }), + ], + coverage: true, + coverageConfig: { + reportDir: "./reports", + }, + // we can use middleware here to resolve assets like images + // to your Greenwood workspace to fix any 404s + middleware: [ + function rewriteIndex(context, next) { + const { url } = context.request; + + if (url.indexOf("/assets") === 0) { + context.request.url = path.join(process.cwd(), "src", url); + } + + return next(); + }, + ], + }; + ``` ## Custom Resources If you're using one of Greenwood's [resource plugins](/docs/plugins/), you'll need to customize WTR manually through [its plugins option](https://modern-web.dev/docs/test-runner/plugins/) so it can leverage the Greenwood plugins your using to automatically to handle these custom transformations. - For example, if you're using Greenwood's [Raw Plugin](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-raw), you'll need to add a plugin transformation and stub out the signature. ```js -import path from 'path'; -import fs from 'fs/promises'; +import path from "path"; +import fs from "fs/promises"; import { defaultReporter } from "@web/test-runner"; import { junitReporter } from "@web/test-runner-junit-reporter"; // 1) import the greenwood plugin @@ -85,23 +86,25 @@ export default { // ... // 3) customize WTR - plugins: [{ - name: "import-raw", - async transform(context) { - const { url } = context.request; - - if (url.endsWith("?type=raw")) { - const contents = await fs.readFile(new URL(`.${url}`, import.meta.url), "utf-8"); - const response = await rawResourcePlugin.intercept(null, null, new Response(contents)); - const body = await response.text(); - - return { - body, - headers: { "Content-Type": "application/javascript" }, - }; - } + plugins: [ + { + name: "import-raw", + async transform(context) { + const { url } = context.request; + + if (url.endsWith("?type=raw")) { + const contents = await fs.readFile(new URL(`.${url}`, import.meta.url), "utf-8"); + const response = await rawResourcePlugin.intercept(null, null, new Response(contents)); + const body = await response.text(); + + return { + body, + headers: { "Content-Type": "application/javascript" }, + }; + } + }, }, - }], + ], }; ``` diff --git a/src/pages/guides/hosting/index.md b/src/pages/guides/hosting/index.md index bdcfa4f1..9797b7b0 100644 --- a/src/pages/guides/hosting/index.md +++ b/src/pages/guides/hosting/index.md @@ -8,7 +8,8 @@ tocHeading: 2

      Hosting

      - The guides within this section cover some of the hosting options you can use to build and deploy your Greenwood project. Below is a an overview of the deploy targets available for a Greenwood site, with additional target specific pages in the sidebar. +The guides within this section cover some of the hosting options you can use to build and deploy your Greenwood project. Below is a an overview of the deploy targets available for a Greenwood site, with additional target specific pages in the sidebar. +
      From dee2d32d57e83fec8893ca4520049490b1babb32 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Mon, 16 Sep 2024 15:06:53 -0400 Subject: [PATCH 12/50] content placeholders and enhancements --- src/pages/guides/ecosystem/index.md | 3 ++- src/pages/guides/getting-started/index.md | 12 ++++++++++-- src/pages/guides/hosting/github.md | 1 + src/pages/guides/hosting/index.md | 17 +++++++++-------- src/pages/guides/tutorials/index.md | 12 ++++++++++-- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/pages/guides/ecosystem/index.md b/src/pages/guides/ecosystem/index.md index a32675dd..b991c92b 100644 --- a/src/pages/guides/ecosystem/index.md +++ b/src/pages/guides/ecosystem/index.md @@ -7,9 +7,10 @@ layout: guides

      Ecosystem

      -The guides within this section cover examples of using popular libraries and tools that are based on or work well with developing for web standards, and in particular Web Components, when using Greenwood. As Greenwood's philosophy it to stay out of your way, as long as the tool embraces web standards, it should just work out of the box. + The guides within this section cover examples of using popular libraries and tools that are based on or work well with developing for web standards, and in particular Web Components, when using Greenwood. As Greenwood's philosophy it to stay out of your way, as long as the tool embraces web standards, it should just work out of the box.
      + In most cases an `npm install` should be all you need to use any third party library and including it in a ` + + + +

      Search Page

      + +
      + + +
      + +
      + + +``` + +## Products Page + +For the Products page, we just need to get our products and render them out into card components. + +```js +// src/pages/products.js +import '../components/card/card.js'; +import { getProducts } from '../services/products.js'; + +export default class ProductsPage extends HTMLElement { + async connectedCallback() { + const products = await getProducts(); + const html = products.map((product) => { + const { title, thumbnail } = product; + + return ` + + + `; + }).join(''); + + this.innerHTML = ` +

      Products Page

      + +
      + ${html} +
      + `; + } +} +``` + +## App Layout + +Since we will want to put our card component on all pages, its easiest to create an app layout and include the card component in a ` + + + + + + + +``` + +So with everything put together, this is what the final project structure would look like. +```shell +src/ + components/ + card/ + card.js + card.css + layouts/ + app.html + pages/ + products.js + search.html +``` + +## Conclusion + +In this tutorial we demonstrated a hybrid rendered application in Greenwood, leveraging a custom element definition usable on both the client and the server all using normal web standards. + +![full-stack-web-components](/assets/guides/full-stack-web-components.webp) + +If you like this approach, make sure to check out our [full demo repo](https://github.com/ProjectEvergreen/greenwood-demo-adapter-vercel) and [our guide on using something like **htmx**](/guides/ecosystem/htmx/) to further embrace this more hypermedia focused architecture. + +Whatever you decide, make sure to share with us whatever you end up building with Greenwood! 💚 \ No newline at end of file diff --git a/src/pages/guides/tutorials/index.md b/src/pages/guides/tutorials/index.md index 572629a7..77ff1192 100644 --- a/src/pages/guides/tutorials/index.md +++ b/src/pages/guides/tutorials/index.md @@ -7,10 +7,10 @@ layout: guides

      Tutorials

      - Some fun things you can do with Greenwood... + Browse some of the patterns and architectures that can be accomplished with Greenwood.
      -TODO \ No newline at end of file +Topics include creating theme packs, isomorphic rendering of web components, and more (to come)! \ No newline at end of file diff --git a/src/pages/guides/tutorials/theme-packs.md b/src/pages/guides/tutorials/theme-packs.md index 39de074d..a9eb5e83 100644 --- a/src/pages/guides/tutorials/theme-packs.md +++ b/src/pages/guides/tutorials/theme-packs.md @@ -1,7 +1,312 @@ --- +title: Creating a Theme Pack layout: guides order: 2 tocHeading: 2 --- -# Theme Packs +# Creating a Theme Pack + +Introduced as a concept in the [Context Plugin docs](/docs/plugins/api/#context), a theme pack is what Greenwood uses to refer to a plugin that aims to provide a set of reusable layouts, pages and more to a user (think of [**CSS Zen Garden**](http://www.csszengarden.com/)). A good example (and the one this guide is based on) is [**greenwood-starter-presentation**](https://github.com/thescientist13/greenwood-starter-presentation), which provides the starting point for creating a [slide deck entirely from markdown](https://github.com/thescientist13/knowing-your-tco), using Greenwood! + +![greenwood-starter-presentation](/assets/guides/greenwood-starter-presentation.png) + +## Prerequisites + +This guide will walk through the process of setting up Greenwood to support the developing and publishing of your package (theme pack) to **npm**. + +To try and focus on just the theme pack aspects, this guide assumes a couple things: + +1. You are already familiar with [setting up](/guides/getting-started/) a Greenwood project. +1. You are familiar with [publishing packages to npm](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages). +1. Assumes a Unix "like" environment (in regards to commands and file path examples used), though the same can definitely be done on Windows. + +We encourage using Greenwood to develop your theme pack mainly so that you can ensure a seamless experience when publishing to npm knowing that things should just work. ™️ + +## Project Setup + +For the sake of development, you can create as much as you need to recreate a user workspace and to simulate what your theme pack would look like. Think of it like creating a [Storybook](https://storybook.js.org/) for your theme pack. + +For this guide, we will be publishing _layouts/_ (layouts) and _styles/_ to **npm**. The _pages/_ directory is just being used to pull in the layout for local development and testing purposes for you as the plugin author. + +```shell +src/ + pages/ + index.md + styles/ + theme.css + layouts/ + blog-post.html +package.json +my-theme-pack.js +greenwood.config.js +``` + +_package.json_ + +```json +{ + "name": "my-theme-pack", + "version": "0.1.0", + "description": "My Custom Greenwood Theme Pack", + "main": "my-theme-pack.js", + "type": "module", + "files": ["dist/"] +} +``` + +_my-theme-pack.js_ + +```js +const myThemePack = () => [ + { + type: "context", + name: "my-theme-pack:context", + provider: () => { + return { + layouts: [ + // import.meta.url will be located at _node_modules/your-package/_ + // when your plugin is run in a user's project + new URL("./dist/my-layouts/", import.meta.url), + ], + }; + }, + }, +]; + +export { myThemePack }; +``` + +_src/layouts/blog-post.html_ + +```html + + + + + + + + + + + + + +``` + +_src/styles/theme.css_ + +```css +* { + color: red; +} +``` + +_src/pages/index.md_ + +```md +--- +layout: "blog-post" +--- + +# Title of blog post + +Lorum Ipsum, this is a test. +``` + +## Development + +The main consideration needed for development is that your files won't be in _node_modules_, which is what the case would be for users when you publish. So for that reason, we need to add a little boilerplate to _my-theme-pack.js_. There might be others way to solve it, but for right now, accepting a "developer only" flag can easily make the plugin pivot into local or "published" modes. + +1. If the flag _is_ passed, then use `new URL('.', import.meta.url)` (which would resolve to the package's location inside _node_modules_) as the base path +1. If the flag _is not_ installed (like we want for local development) then you can use use whatever location you have defined in your repository. Most common would just be to use `process.cwd` + +So using our current example, our final _my-theme-pack.js_ would look like this: + + + +```js +const myThemePackPlugin = (options = {}) => [ + { + type: "context", + name: "my-theme-pack:context", + provider: (compilation) => { + // you can use other directory names besides layouts/ this way! + const layoutLocation = options.__isDevelopment + ? new URL("./layouts/", compilation.context.userWorkspace) + : new URL("dist/layouts/", import.meta.url); + + return { + layouts: [layoutLocation], + }; + }, + }, +]; + +export { myThemePackPlugin }; +``` + +And our final _greenwood.config.js_ would look like this, which adds a "one-off" [resource plugin](/plugins/resource/) to tell Greenwood to route requests to your theme pack files away from \_node_modules+ and to the location of your projects files for development. + +Additionally, we make sure to pass the flag from above for `__isDevelopment` to our plugin. + +```js +// shared from another test +import fs from "fs"; +import { myThemePackPlugin } from "./my-theme-pack.js"; +import { ResourceInterface } from "@greenwood/cli/src/lib/resource-interface.js"; + +const packageName = JSON.parse(fs.readFileSync("./package.json", "utf-8")).name; + +class MyThemePackDevelopmentResource extends ResourceInterface { + constructor(compilation, options) { + super(compilation, options); + this.extensions = ["*"]; + } + + async shouldResolve(url) { + const { pathname } = url; + + return ( + process.env.__GWD_COMMAND__ === "develop" && + pathname.indexOf(`/node_modules/${packageName}/`) >= 0 + ); + } + + async resolve(url) { + const { userWorkspace } = this.compilation.context; + const filePath = this.getBareUrlPath(url).split(`/node_modules/${packageName}/dist/`)[1]; + const params = searchParams.size > 0 ? `?${searchParams.toString()}` : ""; + + return new URL(`./${filePath}${params}`, userWorkspace, filePath); + } +} + +export default { + plugins: [ + ...myThemePackPlugin({ + __isDevelopment: true, + }), + { + type: "resource", + name: "my-theme-pack:resource", + provider: (compilation, options) => new MyThemePackDevelopmentResource(compilation, options), + }, + ], +}; +``` + +You should then be able to run `yarn develop` and load `/` in your browser and the color of the text should be red. + +You're all ready for development now! 🙌 + +## Production Testing + +You can also use Greenwood to test your theme pack using a production build so that you can run `greenwood build` or `greenwood serve` to validate your work. To do so requires just one additional script to your _package.json_ to put your theme pack files in the _node_modules_ where Greenwood would assume them to be. Just call this before `build` or `serve`. + +```json +{ + "scripts": { + "build:pre": "mkdir -pv ./node_modules/greenwood-starter-presentation/dist && rsync -rv --exclude 'pages/' ./src/ ./node_modules/greenwood-starter-presentation/dist", + + "build": "npm run build:pre && greenwood build", + "serve": "npm run build:pre && greenwood serve" + } +} +``` + +## Publishing + +When it comes to publishing, it should be fairly straightforward, and you'll just want to do the following: + +1. Add _dist/_ to _.gitignore_ (or whatever `files` location you want to use for publishing) +1. Add a `prepublish` script to your _package.json_ to create the _dist/_ directory with all the needed _layouts_ (layouts) /_ and \_styles/_ + ```json + { + "name": "my-theme-pack", + "version": "0.1.0", + "description": "My Custom Greenwood Theme Pack", + "main": "my-theme-pack.js", + "type": "module", + "files": ["dist/"], + "scripts": { + "prepublish": "rm -rf dist/ && mkdir dist/ && rsync -rv --exclude 'pages/' src/ dist" + } + } + ``` +1. Now, when you run `npm publish` a fresh _dist/_ folder will be made and [included in your package](https://unpkg.com/browse/greenwood-starter-presentation/) + +## Installation and Usage for Users + +With the above in place and the package published, you're now ready to share your theme pack with other Greenwood users! + +For users, they would just need to do the following: + +1. Install the plugin from npm + + ```shell + $ npm install my-theme-pack --save-dev + ``` +1. Add the plugin to their _greenwood.config.js_ + + ```js + const myThemePackPlugin = require("my-theme-pack"); + + module.exports = { + plugins: [...myThemePackPlugin()], + }; + ``` + +1. Then in any of their markdown files, users would just need to reference the published layout's filename + + ```md + --- + layout: "blog-post" + --- + + My Blog Post using Theme Packs! 💯 + ``` + +Success! 🥳 + +> _Don't forget, user's can also [include additional CSS / JS files in their frontmatter](/docs/front-matter/#imports), to further extend, customize, and override your layouts!_ + +## FAQ + +### _Can I include pages as part of a theme pack?_ + +Support for [including pages as part of a theme pack](https://github.com/ProjectEvergreen/greenwood/issues/681) is planned and coming soon, pretty much as soon as we can support [external data sources](https://github.com/ProjectEvergreen/greenwood/issues/21) in the CLI. + +### _Will there be less development boilerplate in the future for plugin authors?_ + +Yes, we do realize this current workflow is a bit clunky at the moment, so please follow [this discussion](https://github.com/ProjectEvergreen/greenwood/discussions/682) for ways we can try and make this more elegant! 🙏🏻 + +### Why can't I just use relative paths in my layouts to help avoid the boilerplate? + +ex. + +```html + + + + + + + + + + ... + + +``` + +Good question! We tried that approach initially as it would help alleviate the open issues and needing to work around local development vs published development identified above, but the issue that was faced was that relative paths like the above [don't preserve their location / context on disk](https://github.com/ProjectEvergreen/greenwood/issues/689#issuecomment-895519561) when coming through the development server + +```html +# with explicit path that includes node_modules (is exactly the same) url -> +/node_modules/my-theme-pack/dist/styles/theme.css # with relative paths url -> < pagesDir +>/styles/theme.css +``` + +And so at this time Greenwood only looks in the user's workspace, not in _node_modules_, and so it will `404`. From c34a19939e38136ddaea4b0e8a3fdb64a3e72407 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Mon, 16 Sep 2024 21:33:35 -0400 Subject: [PATCH 19/50] formatting --- src/components/side-nav/side-nav.js | 7 +- src/pages/guides/getting-started/index.md | 2 +- src/pages/guides/hosting/netlify.md | 2 +- src/pages/guides/hosting/vercel.md | 2 +- .../tutorials/full-stack-web-components.md | 95 ++++++++++--------- src/pages/guides/tutorials/index.md | 2 +- src/pages/guides/tutorials/theme-packs.md | 1 + 7 files changed, 57 insertions(+), 54 deletions(-) diff --git a/src/components/side-nav/side-nav.js b/src/components/side-nav/side-nav.js index 6d1eafda..59ba428a 100644 --- a/src/components/side-nav/side-nav.js +++ b/src/components/side-nav/side-nav.js @@ -90,12 +90,11 @@ export default class SideNav extends HTMLElement { `; }) - .join("") - } + .join("")} `; - }).join("") - } + }) + .join("")}
    `; diff --git a/src/pages/guides/getting-started/index.md b/src/pages/guides/getting-started/index.md index baeeb1b0..d0bc1cd6 100644 --- a/src/pages/guides/getting-started/index.md +++ b/src/pages/guides/getting-started/index.md @@ -13,4 +13,4 @@ layout: guides -TODO \ No newline at end of file +TODO diff --git a/src/pages/guides/hosting/netlify.md b/src/pages/guides/hosting/netlify.md index bf0c71ab..ad41b414 100644 --- a/src/pages/guides/hosting/netlify.md +++ b/src/pages/guides/hosting/netlify.md @@ -13,7 +13,7 @@ Greenwood can be deployed to [**Netlify**](https://www.netlify.com/) for either ## Static Hosting -For static hosting, nothing is needed other than connecting your repository and building your Greenwood project. You can apply any of your own build customizations [through a _netlify.toml_](https://docs.netlify.com/configure-builds/file-based-configuration/) or the Netlify UI. +For static hosting, nothing is needed other than connecting your repository and building your Greenwood project. You can apply any of your own build customizations [through a _netlify.toml_](https://docs.netlify.com/configure-builds/file-based-configuration/) or the Netlify UI. ```toml [build] diff --git a/src/pages/guides/hosting/vercel.md b/src/pages/guides/hosting/vercel.md index 6e34b8ab..9798de15 100644 --- a/src/pages/guides/hosting/vercel.md +++ b/src/pages/guides/hosting/vercel.md @@ -13,7 +13,7 @@ Greenwood can be deployed to [**Vercel**](https://vercel.com/) for either static ## Static Hosting -For static hosting, nothing is needed other than just connecting your repository and building your Greenwood project. You can apply any of your own build customizations [through a _vercel.json_](https://vercel.com/docs/projects/project-configuration) or the Vercel UI. +For static hosting, nothing is needed other than just connecting your repository and building your Greenwood project. You can apply any of your own build customizations [through a _vercel.json_](https://vercel.com/docs/projects/project-configuration) or the Vercel UI. ```json { diff --git a/src/pages/guides/tutorials/full-stack-web-components.md b/src/pages/guides/tutorials/full-stack-web-components.md index 0b0d4f8f..30dc5cd6 100644 --- a/src/pages/guides/tutorials/full-stack-web-components.md +++ b/src/pages/guides/tutorials/full-stack-web-components.md @@ -6,7 +6,7 @@ tocHeading: 2 # Full Stack Web Components -This guide will walk through some of the patterns demonstrated in our Vercel adapter repo, in which we server render a web component on the server through a "fragments" API with WCC generating HTML, while also re-using that same custom elements definition on the client so we can use it for interactivity too. All through the power of web standards. 💯 +This guide will walk through some of the patterns demonstrated in our Vercel adapter repo, in which we server render a web component on the server through a "fragments" API with WCC generating HTML, while also re-using that same custom elements definition on the client so we can use it for interactivity too. All through the power of web standards. 💯 ![full-stack-web-components](/assets/guides/full-stack-web-components.webp) @@ -17,12 +17,13 @@ This guide will walk through some of the patterns demonstrated in our Vercel ada As alluded to in the intro, our goal here is to leverage a custom element that we can use on the client (for interactivity) and the server (for templating). For this example, we will be creating a card component that renders content based in attributes set on it, as well as a `
    `; - this.attachShadow({ mode: 'open' }); + this.attachShadow({ mode: "open" }); this.shadowRoot.appendChild(template.content.cloneNode(true)); } @@ -78,32 +79,33 @@ export default class Card extends HTMLElement { } } -customElements.define('app-card', Card); +customElements.define("app-card", Card); ``` ## Search API -Next we'll make a "fragments" based API route that we'll use on the Search page. When the user submits the `` on the Search page for a product, a request from the client will be made to this endpoint to filter through the products and if there are any matches, will return the response as an HTML payload from server rendering all the products out to card components. +Next we'll make a "fragments" based API route that we'll use on the Search page. When the user submits the `` on the Search page for a product, a request from the client will be made to this endpoint to filter through the products and if there are any matches, will return the response as an HTML payload from server rendering all the products out to card components. ```js // src/pages/api/search.js // we pull in WCC here to generate HTML fragments for us -import { renderFromHTML } from 'wc-compiler'; -import { getProductsBySearchTerm } from '../../services/products.js'; +import { renderFromHTML } from "wc-compiler"; +import { getProductsBySearchTerm } from "../../services/products.js"; export async function handler(request) { // use the web standard FormData to get the incoming form submission const formData = await request.formData(); - const term = formData.has('term') ? formData.get('term') : ''; + const term = formData.has("term") ? formData.get("term") : ""; const products = await getProductsBySearchTerm(term); - let body = ''; + let body = ""; if (products.length === 0) { - body = 'No results found.'; + body = "No results found."; } else { - const { html } = await renderFromHTML(` - ${ - products.map((item, idx) => { + const { html } = await renderFromHTML( + ` + ${products + .map((item, idx) => { const { title, thumbnail } = item; return ` @@ -112,19 +114,19 @@ export async function handler(request) { thumbnail="${thumbnail}" > `; - }).join('') - } - `, [ - new URL('../components/card/card.js', import.meta.url) - ]); + }) + .join("")} + `, + [new URL("../components/card/card.js", import.meta.url)], + ); body = html; } return new Response(body, { headers: new Headers({ - 'Content-Type': 'text/html' - }) + "Content-Type": "text/html", + }), }); } ``` @@ -138,29 +140,29 @@ For the Search page, we'll just need to set the page up with a `` for the @@ -171,7 +173,7 @@ For the Search page, we'll just need to set the page up with a `` for the @@ -187,23 +189,25 @@ For the Products page, we just need to get our products and render them out into ```js // src/pages/products.js -import '../components/card/card.js'; -import { getProducts } from '../services/products.js'; +import "../components/card/card.js"; +import { getProducts } from "../services/products.js"; export default class ProductsPage extends HTMLElement { async connectedCallback() { const products = await getProducts(); - const html = products.map((product) => { - const { title, thumbnail } = product; + const html = products + .map((product) => { + const { title, thumbnail } = product; - return ` + return ` `; - }).join(''); + }) + .join(""); this.innerHTML = `

    Products Page

    @@ -221,9 +225,8 @@ export default class ProductsPage extends HTMLElement { Since we will want to put our card component on all pages, its easiest to create an app layout and include the card component in a ` @@ -232,11 +235,11 @@ Since we will want to put our card component on all pages, its easiest to create - ``` So with everything put together, this is what the final project structure would look like. + ```shell src/ components/ @@ -258,4 +261,4 @@ In this tutorial we demonstrated a hybrid rendered application in Greenwood, lev If you like this approach, make sure to check out our [full demo repo](https://github.com/ProjectEvergreen/greenwood-demo-adapter-vercel) and [our guide on using something like **htmx**](/guides/ecosystem/htmx/) to further embrace this more hypermedia focused architecture. -Whatever you decide, make sure to share with us whatever you end up building with Greenwood! 💚 \ No newline at end of file +Whatever you decide, make sure to share with us whatever you end up building with Greenwood! 💚 diff --git a/src/pages/guides/tutorials/index.md b/src/pages/guides/tutorials/index.md index 77ff1192..70e95abf 100644 --- a/src/pages/guides/tutorials/index.md +++ b/src/pages/guides/tutorials/index.md @@ -13,4 +13,4 @@ layout: guides -Topics include creating theme packs, isomorphic rendering of web components, and more (to come)! \ No newline at end of file +Topics include creating theme packs, isomorphic rendering of web components, and more (to come)! diff --git a/src/pages/guides/tutorials/theme-packs.md b/src/pages/guides/tutorials/theme-packs.md index a9eb5e83..416e4e58 100644 --- a/src/pages/guides/tutorials/theme-packs.md +++ b/src/pages/guides/tutorials/theme-packs.md @@ -248,6 +248,7 @@ For users, they would just need to do the following: ```shell $ npm install my-theme-pack --save-dev ``` + 1. Add the plugin to their _greenwood.config.js_ ```js From cf74f7ae2e915bf9e01a5815ab3c46f2a9f97307 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Mon, 16 Sep 2024 21:34:19 -0400 Subject: [PATCH 20/50] trim social tray padding --- src/components/header/header.module.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/header/header.module.css b/src/components/header/header.module.css index e28f3c18..11d834c2 100644 --- a/src/components/header/header.module.css +++ b/src/components/header/header.module.css @@ -56,7 +56,7 @@ width: fit-content; border: var(--border-size-1) solid #4d4d4d45; border-radius: var(--radius-6); - padding: var(--size-2) var(--size-3); + padding: var(--size-1); align-items: center; justify-content: center; cursor: pointer; @@ -138,6 +138,10 @@ .logoLink svg.greenwood-logo-full { width: 60%; } + + .socialTray { + padding: var(--size-1) var(--size-2); + } } @media screen and (min-width: 1024px) { From 7c55aa70e925133d0f95e36b502ef13266346d9b Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Tue, 17 Sep 2024 21:36:53 -0400 Subject: [PATCH 21/50] patch duplicate import maps bug --- patches/@greenwood+cli+0.30.0-alpha.6.patch | 75 +++++++++++---------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/patches/@greenwood+cli+0.30.0-alpha.6.patch b/patches/@greenwood+cli+0.30.0-alpha.6.patch index 9ea082dc..47d7c9f1 100644 --- a/patches/@greenwood+cli+0.30.0-alpha.6.patch +++ b/patches/@greenwood+cli+0.30.0-alpha.6.patch @@ -133,43 +133,49 @@ index 8dbf281..a6f252e 100644 const mergedHtml = pageRoot && pageRoot.querySelector('html').rawAttrs !== '' ? `` diff --git a/node_modules/@greenwood/cli/src/lib/walker-package-ranger.js b/node_modules/@greenwood/cli/src/lib/walker-package-ranger.js -index 5ce30c1..11195a7 100644 +index 5ce30c1..a92bb3d 100644 --- a/node_modules/@greenwood/cli/src/lib/walker-package-ranger.js +++ b/node_modules/@greenwood/cli/src/lib/walker-package-ranger.js -@@ -217,15 +217,30 @@ async function walkPackageJson(packageJson = {}) { +@@ -215,17 +215,33 @@ async function walkPackageJson(packageJson = {}) { + return importMap; + } - function mergeImportMap(html = '', map = {}) { - // es-modules-shims breaks on dangling commas in an importMap :/ +-function mergeImportMap(html = '', map = {}) { +- // es-modules-shims breaks on dangling commas in an importMap :/ - const danglingComma = html.indexOf('"imports": {}') > 0 ? '' : ','; -+ const hasImportMap = html.indexOf('script type="importmap"') > 0; -+ const danglingComma = hasImportMap && html.indexOf('"imports": {}') > 0 ? '' : ','; - const importMap = JSON.stringify(map).replace('}', '').replace('{', ''); - +- const importMap = JSON.stringify(map).replace('}', '').replace('{', ''); +- - const merged = html.replace('"imports": {', ` - "imports": { - ${importMap}${danglingComma} - `); -- ++function mergeImportMap(html = '', map = {}, shouldShim = false) { ++ const importMapType = shouldShim ? 'importmap-shim' : 'importmap'; ++ const hasImportMap = html.indexOf(`script type="${importMapType}"`) > 0; ++ const danglingComma = hasImportMap ? ',' : ''; ++ const importMap = JSON.stringify(map, null, 2).replace('}', '').replace('{', ''); ++ ++ if (Object.entries(map).length === 0) { ++ return html; ++ } + - return merged; -+ // TODO looks like this was never working correctly!? :o -+ // console.log({ hasImportMap, html, map, danglingComma, importMap }); + if (hasImportMap) { + return html.replace('"imports": {', ` + "imports": { + ${importMap}${danglingComma} + `); + } else { -+ // TODO this needs tp account for import map shim polyfill config + return html.replace('', ` + -+ -+ `) ++ `); + } } @@ -692,7 +698,7 @@ index 20de63b..6e98d05 100644 if (process.env.__GWD_COMMAND__ === 'develop') { // eslint-disable-line no-underscore-dangle diff --git a/node_modules/@greenwood/cli/src/plugins/resource/plugin-content-as-data.js b/node_modules/@greenwood/cli/src/plugins/resource/plugin-content-as-data.js new file mode 100644 -index 0000000..5d299de +index 0000000..36185b1 --- /dev/null +++ b/node_modules/@greenwood/cli/src/plugins/resource/plugin-content-as-data.js @@ -0,0 +1,54 @@ @@ -711,12 +717,12 @@ index 0000000..5d299de + } + + async shouldIntercept(url, request, response) { -+ return response.headers.get('Content-Type')?.indexOf(this.contentType[0]) >= 0; ++ return response.headers.get('Content-Type')?.indexOf(this.contentType[0]) >= 0 && process.env.__GWD_COMMAND__ === 'develop'; // eslint-disable-line no-underscore-dangle + } + + async intercept(url, request, response) { + const body = await response.text(); -+ const newBody = mergeImportMap(body, importMap); ++ const newBody = mergeImportMap(body, importMap, this.compilation.config.polyfills.importMaps); + + // TODO how come we need to forward headers, shouldn't mergeResponse do that for us? + return new Response(newBody, { @@ -752,7 +758,7 @@ index 0000000..5d299de +export { greenwoodPluginContentAsData }; \ No newline at end of file diff --git a/node_modules/@greenwood/cli/src/plugins/resource/plugin-node-modules.js b/node_modules/@greenwood/cli/src/plugins/resource/plugin-node-modules.js -index 286c2de..a336aed 100644 +index 286c2de..bd662ae 100644 --- a/node_modules/@greenwood/cli/src/plugins/resource/plugin-node-modules.js +++ b/node_modules/@greenwood/cli/src/plugins/resource/plugin-node-modules.js @@ -10,7 +10,7 @@ import replace from '@rollup/plugin-replace'; @@ -764,32 +770,31 @@ index 286c2de..a336aed 100644 let importMap; -@@ -98,15 +98,16 @@ class NodeModulesResource extends ResourceInterface { +@@ -75,7 +75,6 @@ class NodeModulesResource extends ResourceInterface { + async intercept(url, request, response) { + const { context, config } = this.compilation; + const { importMaps } = config.polyfills; +- const importMapType = importMaps ? 'importmap-shim' : 'importmap'; + const importMapShimScript = importMaps ? '' : ''; + let body = await response.text(); + const hasHead = body.match(/\(.*)<\/head>/s); +@@ -97,15 +96,10 @@ class NodeModulesResource extends ResourceInterface { + ? await walkPackageJson(userPackageJson) : importMap || {}; - // apply import map and shim for users -- body = body.replace('', ` -- -- ${importMapShimScript} +- // apply import map and shim for users ++ body = mergeImportMap(body, importMap, importMaps); + body = body.replace('', ` + + ${importMapShimScript} - -- `); -+ body = mergeImportMap(body, importMap); -+ // body = body.replace('', ` -+ // -+ // ${importMapShimScript} -+ // -+ // `); + `); return new Response(body); - } diff --git a/node_modules/@greenwood/cli/src/plugins/resource/plugin-standard-html.js b/node_modules/@greenwood/cli/src/plugins/resource/plugin-standard-html.js index 06223cf..b372e38 100644 --- a/node_modules/@greenwood/cli/src/plugins/resource/plugin-standard-html.js From ac57a5802dd6bee224306103a88ae868539c9a98 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Thu, 19 Sep 2024 19:26:55 -0400 Subject: [PATCH 22/50] update Docker and self-hosting steps based on new demo repo --- src/pages/guides/hosting/index.md | 54 +++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/pages/guides/hosting/index.md b/src/pages/guides/hosting/index.md index 5926f469..63f942dc 100644 --- a/src/pages/guides/hosting/index.md +++ b/src/pages/guides/hosting/index.md @@ -58,7 +58,7 @@ You can of course run your Greenwood application anywhere you can install NodeJS 1. Build your application with `greenwood build` 1. Upload / Copy the contents of the _public/_ directory onto your webserver / webroot 1. Run `npx @greenwood/cli serve` from your webroot -1. Forward traffic to the server `localhost:8080` (default port is `8080`) +1. Forward traffic to the server running at `localhost:8080` (default port is `8080`) ## Serverless @@ -68,15 +68,49 @@ For deploying SSR pages and API routes to serverless hosting providers, you can ## Docker -Many self-hosting solutions support Docker, like our [demo repo](https://github.com/ProjectEvergreen/greenwood-demo-platform-fly) using [**Fly.io**](https://fly.io/). This is a basic example of running Greenwood in a Docker container, which can easily be adapted to any Docker-based hosting provider you need. +[Docker](https://www.docker.com/) is a widely supported developer tool for standardizing the deployment pipeline for applications. You can easily adapt the [baseline NodeJS Docker image](https://github.com/docker/docker-nodejs-sample) (like we did) and fine-tune it for Greenwood with just a couple steps. We have [a demo repo](https://github.com/ProjectEvergreen/greenwood-demo-platform-docker) that you can clone or fork and customize to your needs. +These steps are based on a Greenwood application generated by `@greenwood/init` and then having the `docker init` command run with these options. ```shell -# Adjust NODE_VERSION as desired -ARG NODE_VERSION=18.20.2 -FROM node:${NODE_VERSION}-slim as base - -# NodeJS app lives here -WORKDIR /app - -# TBD +? What application platform does your project use? Node (auto detected) +? What version of Node do you want to use? 18.20.2 (auto detected) +? Which package manager do you want to use? npm (auto detected) +? Do you want to run "npm run build" before starting your server? Yes (auto detected) +? What directory is your build output to? (comma-separate if multiple) public +? What command do you want to use to start the app? npm run serve +? What port does your server listen on? 8080 ``` + +1. Add `**/.greenwood` to _.dockerignore_ +1. If using npm, make sure to mount the _.npmrc_ file in both the **deps** _and_ **build** stages, e.g. + ```Dockerfile + RUN --mount=type=bind,source=package.json,target=package.json \ + --mount=type=bind,source=package-lock.json,target=package-lock.json \ + --mount=type=cache,target=/root/.npm \ + --mount=type=bind,source=.npmrc,target=.npmrc \ + npm ci --omit=dev + ``` +1. Install the `@greenwood/cli` in the **deps** stage, e.g. + ```Dockerfile + RUN --mount=type=bind,source=package.json,target=package.json \ + --mount=type=bind,source=package-lock.json,target=package-lock.json \ + --mount=type=bind,source=.npmrc,target=.npmrc \ + --mount=type=cache,target=/root/.npm \ + npm ci --omit=dev + + # add this line + RUN npm i @greenwood/cli@latest + ``` +1. Copy the _.greenwood/_ directory into the container + ```Dockerfile + COPY --from=deps /usr/src/app/node_modules ./node_modules + COPY --from=build /usr/src/app/public ./public + # add this line + COPY --from=build /usr/src/app/.greenwood ./.greenwood + ``` +1. Run the serve command + ```Dockerfile + CMD npm run serve + ``` + +> Many self-hosting solutions support Docker, like our [demo repo](https://github.com/ProjectEvergreen/greenwood-demo-platform-fly) using [**Fly.io**](https://fly.io/). From 1b1564513fc118d2d02bc8af98a610c1e2ddb3c1 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Thu, 19 Sep 2024 19:35:17 -0400 Subject: [PATCH 23/50] docker for desktop guide screenshot --- .../guides/greenwood-docker-desktop.webp | Bin 0 -> 40752 bytes src/pages/guides/hosting/index.md | 59 ++++++++++-------- 2 files changed, 32 insertions(+), 27 deletions(-) create mode 100644 src/assets/guides/greenwood-docker-desktop.webp diff --git a/src/assets/guides/greenwood-docker-desktop.webp b/src/assets/guides/greenwood-docker-desktop.webp new file mode 100644 index 0000000000000000000000000000000000000000..29826b91bc8b365cbe0ea703470fc4487b571648 GIT binary patch literal 40752 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 ZBDY*9x8L{-&<0oh$BIB_v^IpB0030fdp`gG literal 0 HcmV?d00001 diff --git a/src/pages/guides/hosting/index.md b/src/pages/guides/hosting/index.md index 63f942dc..0d046faf 100644 --- a/src/pages/guides/hosting/index.md +++ b/src/pages/guides/hosting/index.md @@ -68,9 +68,12 @@ For deploying SSR pages and API routes to serverless hosting providers, you can ## Docker -[Docker](https://www.docker.com/) is a widely supported developer tool for standardizing the deployment pipeline for applications. You can easily adapt the [baseline NodeJS Docker image](https://github.com/docker/docker-nodejs-sample) (like we did) and fine-tune it for Greenwood with just a couple steps. We have [a demo repo](https://github.com/ProjectEvergreen/greenwood-demo-platform-docker) that you can clone or fork and customize to your needs. +[Docker](https://www.docker.com/) is a widely supported developer tool for standardizing the deployment pipeline for applications. You can easily adapt the [baseline NodeJS Docker image](https://github.com/docker/docker-nodejs-sample) (like we did) and fine-tune it for Greenwood with just a couple steps. We have [a demo repo](https://github.com/ProjectEvergreen/greenwood-demo-platform-docker) that you can clone or fork and customize to your needs. + +![Greenwood running in Docker Desktop](/assets/guides/greenwood-docker-desktop.webp) These steps are based on a Greenwood application generated by `@greenwood/init` and then having the `docker init` command run with these options. + ```shell ? What application platform does your project use? Node (auto detected) ? What version of Node do you want to use? 18.20.2 (auto detected) @@ -83,34 +86,36 @@ These steps are based on a Greenwood application generated by `@greenwood/init` 1. Add `**/.greenwood` to _.dockerignore_ 1. If using npm, make sure to mount the _.npmrc_ file in both the **deps** _and_ **build** stages, e.g. - ```Dockerfile - RUN --mount=type=bind,source=package.json,target=package.json \ - --mount=type=bind,source=package-lock.json,target=package-lock.json \ - --mount=type=cache,target=/root/.npm \ - --mount=type=bind,source=.npmrc,target=.npmrc \ - npm ci --omit=dev - ``` + ```Dockerfile + RUN --mount=type=bind,source=package.json,target=package.json \ + --mount=type=bind,source=package-lock.json,target=package-lock.json \ + --mount=type=cache,target=/root/.npm \ + --mount=type=bind,source=.npmrc,target=.npmrc \ + npm ci --omit=dev + ``` 1. Install the `@greenwood/cli` in the **deps** stage, e.g. - ```Dockerfile - RUN --mount=type=bind,source=package.json,target=package.json \ - --mount=type=bind,source=package-lock.json,target=package-lock.json \ - --mount=type=bind,source=.npmrc,target=.npmrc \ - --mount=type=cache,target=/root/.npm \ - npm ci --omit=dev - - # add this line - RUN npm i @greenwood/cli@latest - ``` + + ```Dockerfile + RUN --mount=type=bind,source=package.json,target=package.json \ + --mount=type=bind,source=package-lock.json,target=package-lock.json \ + --mount=type=bind,source=.npmrc,target=.npmrc \ + --mount=type=cache,target=/root/.npm \ + npm ci --omit=dev + + # add this line + RUN npm i @greenwood/cli@latest + ``` + 1. Copy the _.greenwood/_ directory into the container - ```Dockerfile - COPY --from=deps /usr/src/app/node_modules ./node_modules - COPY --from=build /usr/src/app/public ./public - # add this line - COPY --from=build /usr/src/app/.greenwood ./.greenwood - ``` + ```Dockerfile + COPY --from=deps /usr/src/app/node_modules ./node_modules + COPY --from=build /usr/src/app/public ./public + # add this line + COPY --from=build /usr/src/app/.greenwood ./.greenwood + ``` 1. Run the serve command - ```Dockerfile - CMD npm run serve - ``` + ```Dockerfile + CMD npm run serve + ``` > Many self-hosting solutions support Docker, like our [demo repo](https://github.com/ProjectEvergreen/greenwood-demo-platform-fly) using [**Fly.io**](https://fly.io/). From d00213e40075f0c39d49b0b9eee9b33cd36e2ad2 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Thu, 19 Sep 2024 21:15:43 -0400 Subject: [PATCH 24/50] cloudflare hosting guide refresh --- src/pages/guides/hosting/cloudflare.md | 103 ++++--------------------- 1 file changed, 16 insertions(+), 87 deletions(-) diff --git a/src/pages/guides/hosting/cloudflare.md b/src/pages/guides/hosting/cloudflare.md index 3829fe61..545d89bc 100644 --- a/src/pages/guides/hosting/cloudflare.md +++ b/src/pages/guides/hosting/cloudflare.md @@ -7,100 +7,29 @@ tocHeading: 2 # Cloudflare -[Cloudflare Workers](https://workers.cloudflare.com/) is an excellent option as a CDN for deploying and hosting your static Greenwood site. This will require a paid account with Cloudflare with a linked domain for custom domain and subdomains. +[**Cloudflare**](https://workers.cloudflare.com/) is a a great option for hosting your Greenwood application for both static and serverless hosting. -> There is no adapter plugin yet for serverless hosting, though it is on [our roadmap](https://github.com/ProjectEvergreen/greenwood/issues/1143). +> You can see a complete hybrid project example in our [demonstration repo](https://github.com/ProjectEvergreen/greenwood-demo-adapter-cloudflare). -## Setup +## Pages -1. You will need to globally install Cloudflare's CLI tool _Wrangler_ - ```shell - npm install -g global @cloudflare/wrangler - ``` -1. In the root of your project directory initialize _Wrangler_ - ```shell - wrangler init - ``` -1. Authenticate your cloudflare account with: - ```shell - wrangler config - ``` -1. A _wrangler.toml_ file will be generated at the root of your project directory. You will want to update accordingly: +For static hosting with [Cloudflare Pages](https://developers.cloudflare.com/pages), the easiest option is to follow their steps for integration with your [git hosting provider](https://developers.cloudflare.com/pages/get-started/git-integration/). With this, Cloudflare will automatically build and deploy your project when you push changes to your repo, as well as creating deploy previews for PRs. - ```toml - name = "demo" # workers.dev subdomain name automatically named for the directory - type = "webpack" - account_id = "abcd12345...." # your account id +> As part of the wizard, make sure you set a **build command** for your project, e.g. `npm run build` - [env.production] - workers_dev = true +You'll also want to add a _wrangler.toml_ file to the root of your project - [site] - bucket = "./public" # where greenwood generated the compiled code - entry-point = "workers-site" - ``` +```toml +# https://developers.cloudflare.com/pages/functions/wrangler-configuration/ +name = "" +pages_build_output_dir = "./public" +compatibility_date = "2023-10-12" +``` -1. Run a Greenwood build - ```shell - greenwood build - ``` -1. Then push your code to Cloudflare workers - ```shell - wrangler publish - ``` +That's it! That's all you should need to get started deploying Greenwood to Cloudflare Pages. ☁️ -When completed a url for workers subdomain will be printed in your terminal. 🏆 +## Workers -## Enabling GitHub Actions +Coming soon! -To have automatic deployments whenever you push updates to your repo, you will need to configure GitHub Actions to accomplish this, instead of having to manually run the `build` and `publish` commands each time you wish to update your site. - -1. Add the email address (`CF_WORKERS_EMAIL`) and API key (`CF_WORKERS_KEY`) associated with your account to the repositories [GitHub secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). -1. At the root of your project add '.github/workflows/deploy.yml' - - ```yml - name: Deploy Cloudflare Workers - - # configure your branch accordingly - on: - push: - branches: - # configure your branch accordingly - - main - - jobs: - build: - runs-on: ubuntu-20.04 - - # match to your version of NodeJS - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 - with: - node-version: 18.20.2 - - - name: Navigate to repo - run: | - cd $GITHUB_WORKSPACE - - # or replace with yarn, pnpm, etc - - name: Install Dependencies - run: | - npm ci - - # use your greenwood build script - - name: Run Build - run: | - npm run build - - - name: Publish to Cloudflare Workers - uses: cloudflare/wrangler-action@1.1.0 - with: - apiKey: ${{ secrets.CF_WORKERS_KEY }} - email: ${{ secrets.CF_WORKERS_EMAIL }} - environment: "production" - ``` - -1. Push your updates to your repo and the action will begin automatically. - -This will create a new worker with the name from the _.toml_ file -production (i.e. &&demo-production&&). Make sure your custom url is attached to this worker. +> Although there is no adapter plugin (yet) for this it is a priority on [our roadmap](https://github.com/ProjectEvergreen/greenwood/issues/1143). From b7491c559d2ea8637bc9e59c0dc80631cca2ab25 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Fri, 20 Sep 2024 17:00:54 -0400 Subject: [PATCH 25/50] refresh AWS guide --- src/pages/guides/hosting/aws.md | 172 ++++++++++++++++---------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/src/pages/guides/hosting/aws.md b/src/pages/guides/hosting/aws.md index a7f5d5a2..9345f42c 100644 --- a/src/pages/guides/hosting/aws.md +++ b/src/pages/guides/hosting/aws.md @@ -8,94 +8,94 @@ tocHeading: 2 # AWS -Greenwood can be automatically deployed to [**AWS**](https://aws.amazon.com/) for static hosting using [**S3**](https://aws.amazon.com/s3/) and [**Cloudfront**](https://aws.amazon.com/cloudfront/) with GitHub Actions. This requires having an **AWS** account. +Greenwood can be automatically deployed to [**AWS**](https://aws.amazon.com/) for static hosting using [**S3**](https://aws.amazon.com/s3/) and [**Cloudfront**](https://aws.amazon.com/cloudfront/) with GitHub Actions. -> There is no adapter plugin yet for serverless hosting, though it is on [our roadmap](https://github.com/ProjectEvergreen/greenwood/issues/1142). +> You can see a complete hybrid project example in our [demonstration repo](https://github.com/ProjectEvergreen/greenwood-demo-adapter-aws). -## Setup S3 - -1. Create a new bucket in S3 -1. Upload the contents of your 'public' directory (drag and drop all the files and folders, using the interface only grabs files). -1. Within your bucket click the "Properties" tab and select "Static website hosting" -1. Check "Use this bucket to host a website" and enter `index.html` to the "index document" input -1. Go to "Permissions" tab and edit "Block Public Access" to turn those off and save -1. Still in (or click on) the "Permissions" tab, click "Bucket Policy" and add the following snippet (putting in your buckets name) and save. - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "PublicReadGetObject", - "Effect": "Allow", - "Principal": "*", - "Action": "s3:GetObject", - "Resource": "arn:aws:s3:::your-bucket-name-here/*" - } - ] - } - ``` +## Static Hosting + +In this section, we'll share the steps for up S3 and Cloudfront together for static web hosting. + +1. Configure S3 by following [these steps](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/GettingStarted.SimpleDistribution.html) +1. Once you have followed those steps, run `greenwood build` in your project and upload the contents of the _public/_ directory to the bucket +1. Finally, setup Cloudfront to use this bucket as an origin by [following these steps](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/GettingStarted.SimpleDistribution.html#GettingStartedCreateDistribution): + +> Keep an eye out for prompts from AWS to enable IAM rules for your function and make sure to invalidate the Cloudfront distribution between tests, since error pages / responses will get cached. + +You should now be able to access your site at `http://.cloudfront.net/`! 🏆 + +Now at this point, if you have any routes like `/search/`, you'll notice they are not working unless _index.html_ is appended to the path. To enable routing (URL rewriting) for cleaner URLs, follow the _Configure Trigger_ section of [this guide](https://aws.amazon.com/blogs/compute/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/) on the Lambda function as a [**Lambda@Edge**](https://aws.amazon.com/lambda/edge/) function to run on every incoming request. + +Below is a sample Edge function for doing the rewrites: + +```js +export const handler = async (event, context, callback) => { + const { request } = event.Records[0].cf; + + // re-write "clean" URLs to have index.html appended + // to support routing for Cloudfront <> S3 + if (request.uri.endsWith('/')) { + request.uri = `${request.uri}index.html`; + } + + callback(null, request); +}; +``` + +> At this point, you'll probably want to use Route 53 to [put your domain in front of your Cloudfront distribution](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-cloudfront-distribution.html). + + +## Serverless + +Coming soon! + +> There is no adapter plugin yet for serverless hosting, though it is on [our roadmap](https://github.com/ProjectEvergreen/greenwood/issues/1142). -Your site will now be at the address visible in the "Static website hosting" card. - -## Setup CloudFront - -1. Navigate to CloudFront in your AWS account. -1. Click "get started" in the web section. -1. In the "Origin Domain Name" input, select the bucket you are setting up. -1. Further down that form find "Default Root Object" and enter `index.html` -1. Click "Create Distribution", then just wait for the Status to update to "deployed". - -Your site is now hosted on S3 with a CloudFront CDN! 🏆 - -## Enable GitHub Actions - -1. You'll want to add your AWS Secret Access Key (`AWS_SECRET_ACCESS_KEY`) and Access Key ID (`AWS_SECRET_ACCESS_KEY_ID`) to the repositories as [GitHub secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). -1. At the root of your repo add a GitHub Action called _.github/workflows/deploy.yml_ and adapt as needed for your own branch and package manager. (Using npm here) - - ```yml - name: Upload Website to S3 - - on: - push: - branches: - # configure your branch accordingly - - main - - jobs: - build: - runs-on: ubuntu-20.04 - - # match to your version of NodeJS - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 - with: - node-version: 18.20.2 - - - name: Navigate to repo - run: | - cd $GITHUB_WORKSPACE - - # or replace with yarn, pnpm, etc - - name: Install Dependencies - run: | - npm ci - - # use your greenwood build script - - name: Run Build - run: | - npm run build - - - name: Deploy to S3 - uses: opspresso/action-s3-sync@master - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_SECRET_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: "us-east-2" - FROM_PATH: "./public" - # your target s3 bucket name goes here - DEST_PATH: "s3://your-s3-bucket-name" - OPTIONS: "--acl public-read" +## GitHub Actions + +If you're using GitHub, you can use GitHub Actions to automate the pushing of build files on commits to a GitHub repo. This action will also invalidate your Cloudfront cache on each publish. + +1. In your AWS account, create and / or add an AWS Secret Access Key (`AWS_SECRET_ACCESS_KEY`) and Access Key ID (`AWS_SECRET_ACCESS_KEY_ID`) and add them to your repository as [GitHub secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). +1. We also recommend adding your bucket name as secret too, e.g. `AWS_BUCKET_NAME` +1. At the root of your repo add a GitHub Action called _.github/workflows/publish.yml_ and adapt as needed for your own branch, build commands, and package manager. + ```yml + name: Upload Website to S3 + + on: + push: + branches: + - main + + jobs: + build: + runs-on: ubuntu-20.04 + + # match to your version of NodeJS + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v3 + with: + node-version: 18.20.2 + + - name: Install Dependencies + run: | + npm ci + + # use your greenwood build script + - name: Run Build + run: | + npm run build + + - name: Upload to S3 and invalidate CDN + uses: opspresso/action-s3-sync@master + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_SECRET_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + # make sure this matches your bucket's region + AWS_REGION: "us-east-1" + FROM_PATH: "./public" + # your target s3 bucket name goes here + DEST_PATH: s3://${{ secrets.AWS_BUCKET_NAME }} ``` -1. Push your updates to your repo and the action will begin automatically. +Now when you push changes to your repo, the action will run an the build files will automatically be uploaded. \ No newline at end of file From af9517c48607784f1582911742b8640c410d4cb5 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Mon, 23 Sep 2024 12:11:03 -0400 Subject: [PATCH 26/50] WIP getting started guide --- .../guides/getting-started/going-further.md | 35 +++ src/pages/guides/getting-started/index.md | 78 ++++++- .../guides/getting-started/key-concepts.md | 208 ++++++++++++++++++ .../guides/getting-started/next-steps.md | 20 ++ .../guides/getting-started/quick-start.md | 20 -- .../guides/getting-started/scaffolding.md | 9 - .../guides/getting-started/walkthrough.md | 14 +- src/pages/guides/hosting/aws.md | 88 ++++---- src/styles/theme.css | 26 ++- 9 files changed, 417 insertions(+), 81 deletions(-) create mode 100644 src/pages/guides/getting-started/going-further.md create mode 100644 src/pages/guides/getting-started/key-concepts.md create mode 100644 src/pages/guides/getting-started/next-steps.md delete mode 100644 src/pages/guides/getting-started/quick-start.md delete mode 100644 src/pages/guides/getting-started/scaffolding.md diff --git a/src/pages/guides/getting-started/going-further.md b/src/pages/guides/getting-started/going-further.md new file mode 100644 index 00000000..00d8e0f8 --- /dev/null +++ b/src/pages/guides/getting-started/going-further.md @@ -0,0 +1,35 @@ +--- +order: 3 +layout: guides +tocHeading: 2 +--- + +# Going Further + +Now that you've had a chance to see some of the [basics](/guides/getting-started/key-concepts/) Greenwood has to offer and having [walked through](/guides/getting-started/walkthrough/) putting together a basic site, we want to take a moment to briefly showcase some of the additional capabilities and patterns you can leverage to help you build whatever type of project you want with Greenwood. + +TODO + + diff --git a/src/pages/guides/getting-started/index.md b/src/pages/guides/getting-started/index.md index d0bc1cd6..6d9c3931 100644 --- a/src/pages/guides/getting-started/index.md +++ b/src/pages/guides/getting-started/index.md @@ -1,16 +1,90 @@ --- order: 1 layout: guides +tocHeading: 2 ---

    Getting Started

    - How to get started using Greenwood... + Greenwood aims to leverage the web platform as much as possible, with just a little extra added on top for some nice conveniences. The contents of this section will provide a high level overview of the basics of Greenwood and a light introduction to some of its advanced capabilities and patterns.
    -TODO +## What to Expect + +This _Getting Started_ guide will walk you through creating a basic static content (blog) site, touching upon the following topics: + +1. Creating markdown pages +1. Shared layouts and styles +1. Web Components for templating + +## Prerequisites + +You will need the following installed on your machine: + +1. [**NodeJS LTS**](https://nodejs.org/en/download/package-manager) (required) - We recommend using a Node version manager (like NVM) to install the latest stable version of Node +1. [**Git**](https://git-scm.com/) (optional) - Can be useful for [cloning and inspecting](https://github.com/ProjectEvergreen/greenwood-getting-started) the companion repo for this guide, or otherwise managing your Greenwood project through version control + +You can verify that both NodeJS and NPM are installed correctly by checking their version from the command line: + +```bash +$ node -v +v18.12.1 + +$ npm -v +8.19.2 +``` + +## Setup + +With NodeJS installed, you'll want to prepare a workspace for your project and use our `init` package to scaffold out a new project into a directory of your choosing: + +```shell +# initialize a new Greenwood project into the my-app directory +$ npx @greenwood/init@latest my-app +$ cd my-app + +# clean up the src/ directory +$ rm -rf src/ +``` + +Or you can also initialize a repository manually by installing the Greenwood CLI yourself, like so: + +```shell +# make and change into your workspace directory +$ mkdir my-app +$ cd my-app + +# initialize a _package.json (you can accept all defaults) +$ npm init + +# install Greenwood as a devDependency +$ npm i -D @greenwood/cli@latest +``` + +Then setup some npm scripts in your _package.json_ for running Greenwwod and make sure to set the `type` to **module** + +```json +{ + "type": "module", + "scripts": { + "dev": "greenwood develop", + "build": "greenwood build", + "serve": "greenwood serve" + } +} +``` + +## Jumping Right In + +If you want to jump to the final results right now, you can browse [the companion repo](https://github.com/ProjectEvergreen/greenwood-getting-started) or play around with the companion repo in the Stackblitz below. 👇 + + + +## Next Section + +With that all out of the way, let's move onto the [next section](/guides/getting-started/key-concepts/). diff --git a/src/pages/guides/getting-started/key-concepts.md b/src/pages/guides/getting-started/key-concepts.md new file mode 100644 index 00000000..d9eeb584 --- /dev/null +++ b/src/pages/guides/getting-started/key-concepts.md @@ -0,0 +1,208 @@ +--- +order: 1 +title: Key Concepts +layout: guides +tocHeading: 2 +--- + +# Key Concepts + +Now that we have our project [ready to go](/guides/getting-started/#setup), let's prepare by reviewing a few key concepts to be aware of for Greenwood and this guide in general. + +## File Based Routing + +Greenwood leverages file-based routing to map files in the _pages/_ directory of your project's workspace to URLs that can be accessed from a browser. + +As an example, this project structure + +```shell +src/ + pages/ + index.html + blog/ + index.html + first-post.md + second-post.md +``` + +Would yield the following four routes: + +- `/` - mapped from _index.html_ +- `/blog/` - mapped from _blog/index.html_ +- `/blog/first-post/` - mapped from _blog/first-post.md_ +- `/blog/first-post/` - mapped from _blog/second-post.md_ + +> Notice we can mix and match HTML and markdown authored content in our filesystem. We can apply this to server rendering (SSR) as well. 👀 + +## Pages + +For the sake of this guide, pages can just be HTML, using just... normal HTML. You can include any ` + + + + + +
    +

    Welcome to my website!

    +
    + + +``` + +Our file structure would now look like this + +```shell +src/ + theme.css + components/ + header.js + pages/ + index.html + blog/ + index.html + first-post.md + second-post.md +``` + +> Greenwood is smart enough to follow the references from the ` + + + + +
    + +
    + + +``` + +And now our _index.html_ can just be this + +```html + + + +

    Welcome to my website!

    + + +``` + +And so now we can see the _layouts/_ directory added to our project. + +```shell +src/ + theme.css + components/ + header.js + pages/ + index.html + blog/ + index.html + first-post.md + second-post.md + layouts/ + app.html +``` + +## Markdown and Frontmatter + +Although it can be used in HTML files too, frontmatter is a YAML based set of configuration that can be used to provide additional metadata for markdown files. As demonstrated up to this point, Greenwood supports markdown as demonstrated so far, and so if wanted a layout to specifically wrap our blog posts, we can specify the name of a layout file in our markdown files. + +```md +## + +## layout: 'blog' + +## My First Blog Post + +This is my first post, I hope you like it. +``` + +And now we can create a layout just for these particular pages, which themselves will get wrapped by the _app.html_ layout, if applicable. + +```html + + + + + + +
    ← Back to all blog posts + + +``` + +And so putting it all together, our project structure would now look like this: + +```shell +src/ + theme.css + components/ + header.js + pages/ + index.html + blog/ + index.html + first-post.md + second-post.md + layouts/ + app.html + blog.html +``` + +## Next Section + +Ok, so with the key concepts of workspaces, layouts and pages covered, you're now ready to start [creating content](/getting-started/creating-content/) and developing your first Greenwood site! diff --git a/src/pages/guides/getting-started/next-steps.md b/src/pages/guides/getting-started/next-steps.md new file mode 100644 index 00000000..bfd3e941 --- /dev/null +++ b/src/pages/guides/getting-started/next-steps.md @@ -0,0 +1,20 @@ +--- +order: 4 +layout: guides +tocHeading: 2 +--- + +# Next Steps + +TODO + + diff --git a/src/pages/guides/getting-started/quick-start.md b/src/pages/guides/getting-started/quick-start.md deleted file mode 100644 index f1634cab..00000000 --- a/src/pages/guides/getting-started/quick-start.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -order: 1 -title: Quick Start -layout: guides -tocHeading: 2 ---- - -# Quick Start - -## Init - -Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. - -## Stackblitz - -Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. - -## Repo - -Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. diff --git a/src/pages/guides/getting-started/scaffolding.md b/src/pages/guides/getting-started/scaffolding.md deleted file mode 100644 index 488d9ba7..00000000 --- a/src/pages/guides/getting-started/scaffolding.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -order: 3 -layout: guides -tocHeading: 2 ---- - -# Scaffolding - -Let's talk about the `init` package and its options. diff --git a/src/pages/guides/getting-started/walkthrough.md b/src/pages/guides/getting-started/walkthrough.md index e40450c4..72d660cf 100644 --- a/src/pages/guides/getting-started/walkthrough.md +++ b/src/pages/guides/getting-started/walkthrough.md @@ -6,4 +6,16 @@ tocHeading: 2 # Walkthrough -Let's start a new project! + diff --git a/src/pages/guides/hosting/aws.md b/src/pages/guides/hosting/aws.md index 9345f42c..58d8677c 100644 --- a/src/pages/guides/hosting/aws.md +++ b/src/pages/guides/hosting/aws.md @@ -24,7 +24,7 @@ In this section, we'll share the steps for up S3 and Cloudfront together for sta You should now be able to access your site at `http://.cloudfront.net/`! 🏆 -Now at this point, if you have any routes like `/search/`, you'll notice they are not working unless _index.html_ is appended to the path. To enable routing (URL rewriting) for cleaner URLs, follow the _Configure Trigger_ section of [this guide](https://aws.amazon.com/blogs/compute/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/) on the Lambda function as a [**Lambda@Edge**](https://aws.amazon.com/lambda/edge/) function to run on every incoming request. +Now at this point, if you have any routes like `/search/`, you'll notice they are not working unless _index.html_ is appended to the path. To enable routing (URL rewriting) for cleaner URLs, follow the _Configure Trigger_ section of [this guide](https://aws.amazon.com/blogs/compute/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/) on the Lambda function as a [**Lambda@Edge**](https://aws.amazon.com/lambda/edge/) function to run on every incoming request. Below is a sample Edge function for doing the rewrites: @@ -34,17 +34,16 @@ export const handler = async (event, context, callback) => { // re-write "clean" URLs to have index.html appended // to support routing for Cloudfront <> S3 - if (request.uri.endsWith('/')) { + if (request.uri.endsWith("/")) { request.uri = `${request.uri}index.html`; } - callback(null, request); + callback(null, request); }; ``` > At this point, you'll probably want to use Route 53 to [put your domain in front of your Cloudfront distribution](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-cloudfront-distribution.html). - ## Serverless Coming soon! @@ -53,49 +52,50 @@ Coming soon! ## GitHub Actions -If you're using GitHub, you can use GitHub Actions to automate the pushing of build files on commits to a GitHub repo. This action will also invalidate your Cloudfront cache on each publish. +If you're using GitHub, you can use GitHub Actions to automate the pushing of build files on commits to a GitHub repo. This action will also invalidate your Cloudfront cache on each publish. 1. In your AWS account, create and / or add an AWS Secret Access Key (`AWS_SECRET_ACCESS_KEY`) and Access Key ID (`AWS_SECRET_ACCESS_KEY_ID`) and add them to your repository as [GitHub secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). 1. We also recommend adding your bucket name as secret too, e.g. `AWS_BUCKET_NAME` 1. At the root of your repo add a GitHub Action called _.github/workflows/publish.yml_ and adapt as needed for your own branch, build commands, and package manager. - ```yml - name: Upload Website to S3 - - on: - push: - branches: - - main - - jobs: - build: - runs-on: ubuntu-20.04 - - # match to your version of NodeJS - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 - with: - node-version: 18.20.2 - - - name: Install Dependencies - run: | - npm ci - - # use your greenwood build script - - name: Run Build - run: | - npm run build - - - name: Upload to S3 and invalidate CDN - uses: opspresso/action-s3-sync@master - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_SECRET_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - # make sure this matches your bucket's region - AWS_REGION: "us-east-1" - FROM_PATH: "./public" - # your target s3 bucket name goes here - DEST_PATH: s3://${{ secrets.AWS_BUCKET_NAME }} + + ```yml + name: Upload Website to S3 + + on: + push: + branches: + - main + + jobs: + build: + runs-on: ubuntu-20.04 + + # match to your version of NodeJS + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v3 + with: + node-version: 18.20.2 + + - name: Install Dependencies + run: | + npm ci + + # use your greenwood build script + - name: Run Build + run: | + npm run build + + - name: Upload to S3 and invalidate CDN + uses: opspresso/action-s3-sync@master + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_SECRET_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + # make sure this matches your bucket's region + AWS_REGION: "us-east-1" + FROM_PATH: "./public" + # your target s3 bucket name goes here + DEST_PATH: s3://${{ secrets.AWS_BUCKET_NAME }} ``` -Now when you push changes to your repo, the action will run an the build files will automatically be uploaded. \ No newline at end of file +Now when you push changes to your repo, the action will run an the build files will automatically be uploaded. diff --git a/src/styles/theme.css b/src/styles/theme.css index 14df479e..449896f1 100644 --- a/src/styles/theme.css +++ b/src/styles/theme.css @@ -150,9 +150,25 @@ li { } } -.page-content blockquote { - padding: var(--size-1); - background-color: var(--color-accent) !important; - border-left: var(--size-1) solid var(--color-secondary); - font-style: italic; +.page-content { + & blockquote { + padding: var(--size-1); + background-color: var(--color-accent) !important; + border-left: var(--size-1) solid var(--color-secondary); + font-style: italic; + } + + & iframe { + width: 100%; + height: 800px; + display: none; + } +} + +@media (min-width: 768px) { + .page-content { + & iframe { + display: block; + } + } } From 4a64d7ed3c5998f815c8fec4ad11f1c08fc2a647 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Mon, 23 Sep 2024 14:41:20 -0400 Subject: [PATCH 27/50] desktop side and ToC nav styling --- src/components/header/header.module.css | 5 ++- src/components/side-nav/side-nav.module.css | 2 +- .../table-of-contents.module.css | 23 +++++++++- src/layouts/guides.html | 42 +++++++++++-------- src/pages/guides/index.md | 2 +- 5 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/components/header/header.module.css b/src/components/header/header.module.css index 11d834c2..d4b191ac 100644 --- a/src/components/header/header.module.css +++ b/src/components/header/header.module.css @@ -1,8 +1,9 @@ .container { display: flex; justify-content: space-between; - padding: 0 var(--size-4); + padding: 0 var(--size-4) var(--size-2); margin: 0; + border-bottom: 2px dotted var(--color-gray); } .logoLink { @@ -113,7 +114,7 @@ @media screen and (min-width: 480px) { .container { - padding: 0 var(--size-10); + padding: 0 var(--size-10) var(--size-2); } } diff --git a/src/components/side-nav/side-nav.module.css b/src/components/side-nav/side-nav.module.css index 30857a58..4f1cbf97 100644 --- a/src/components/side-nav/side-nav.module.css +++ b/src/components/side-nav/side-nav.module.css @@ -67,7 +67,7 @@ margin: var(--size-1); } -@media (min-width: 1024px) { +@media (min-width: 1200px) { .fullMenu { display: block; } 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 13ead05f..76a7b644 100644 --- a/src/components/table-of-contents/table-of-contents.module.css +++ b/src/components/table-of-contents/table-of-contents.module.css @@ -49,12 +49,33 @@ text-decoration: underline; } -@media (min-width: 1024px) { +@media (min-width: 1200px) { .fullMenu { display: block; + border-left: 2px solid var(--color-gray); + border-top: 2px solid var(--color-gray); + border-bottom: 2px solid var(--color-gray); + box-shadow: var(--shadow-2); + padding: var(--size-2) 0 0 var(--size-7); + + & a, + & li { + font-size: var(--font-size-1); + } } .compactMenu { display: none; } } + +@media (min-width: 1440px) { + .fullMenu { + display: block; + + & a, + & li { + font-size: var(--font-size-3); + } + } +} diff --git a/src/layouts/guides.html b/src/layouts/guides.html index c0b7a5b7..f83c4f3c 100644 --- a/src/layouts/guides.html +++ b/src/layouts/guides.html @@ -15,7 +15,6 @@ } .page-content .content { - max-width: 100ch; padding: 0 var(--size-4); & h1 { @@ -56,14 +55,15 @@ margin: 0 0 var(--size-4); } - @media (min-width: 1024px) { + @media (min-width: 1200px) { .page-content { margin: var(--size-6) auto; - width: 90%; + } - & p { - font-size: var(--font-size-1); - } + .content { + display: inline-block; + width: 45%; + font-size: var(--font-size-1); } hr { @@ -72,29 +72,37 @@ app-side-nav { display: inline-block; - width: 25%; + width: 20%; + min-width: 25%; vertical-align: top; + padding: var(--size-4) 0 var(--size-10) var(--size-fluid-5); + background-color: var(--color-gray); + border-radius: var(--radius-3); } app-toc { float: right; display: inline-block; - width: 15%; + width: 20%; + position: sticky; + top: var(--size-4); } + } - .content { - display: inline-block; - width: 55%; + @media (min-width: 1440px) { + .page-content .content { + width: 49%; + font-size: var(--font-size-3); + } + + app-side-nav { + padding: var(--size-4) 0 var(--size-10) var(--size-fluid-7); } } @media (min-width: 1440px) { - .page-content { - width: 85%; - - & p { - font-size: var(--font-size-3); - } + .page-content .content { + width: 54%; } } diff --git a/src/pages/guides/index.md b/src/pages/guides/index.md index 03103724..3e91ee43 100644 --- a/src/pages/guides/index.md +++ b/src/pages/guides/index.md @@ -14,7 +14,7 @@ _Want to build with Lit and deploy to Vercel?_ **No problem.** _Deploy a SPA to AWS?_ **No problem.**

    -_Run HTMX on a self-hsoted server?_ **You know you can.** +_Run HTMX on a self-hosted server?_ **You know you can.** From 4fe21239f310f8fc531b0fa2a1ddfb71ab190f13 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Mon, 23 Sep 2024 16:42:25 -0400 Subject: [PATCH 28/50] formatting --- src/layouts/guides.html | 4 ++-- src/pages/guides/getting-started/key-concepts.md | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/layouts/guides.html b/src/layouts/guides.html index f83c4f3c..95af4a1f 100644 --- a/src/layouts/guides.html +++ b/src/layouts/guides.html @@ -75,7 +75,7 @@ width: 20%; min-width: 25%; vertical-align: top; - padding: var(--size-4) 0 var(--size-10) var(--size-fluid-5); + padding: var(--size-4) 0 0 var(--size-fluid-5); background-color: var(--color-gray); border-radius: var(--radius-3); } @@ -96,7 +96,7 @@ } app-side-nav { - padding: var(--size-4) 0 var(--size-10) var(--size-fluid-7); + padding: var(--size-4) 0 0 var(--size-fluid-7); } } diff --git a/src/pages/guides/getting-started/key-concepts.md b/src/pages/guides/getting-started/key-concepts.md index d9eeb584..f57fa130 100644 --- a/src/pages/guides/getting-started/key-concepts.md +++ b/src/pages/guides/getting-started/key-concepts.md @@ -161,16 +161,21 @@ src/ Although it can be used in HTML files too, frontmatter is a YAML based set of configuration that can be used to provide additional metadata for markdown files. As demonstrated up to this point, Greenwood supports markdown as demonstrated so far, and so if wanted a layout to specifically wrap our blog posts, we can specify the name of a layout file in our markdown files. + + ```md -## - -## layout: 'blog' + +--- +layout: 'blog' +--- ## My First Blog Post This is my first post, I hope you like it. ``` + + And now we can create a layout just for these particular pages, which themselves will get wrapped by the _app.html_ layout, if applicable. ```html From 97721982ce2c313124fb37ad8a972adfbf10dbef Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Mon, 23 Sep 2024 17:27:06 -0400 Subject: [PATCH 29/50] misc tweaks --- src/components/side-nav/side-nav.module.css | 4 ++++ .../table-of-contents/table-of-contents.module.css | 6 +++++- src/layouts/guides.html | 7 ++++--- src/pages/guides/getting-started/key-concepts.md | 4 ++-- src/pages/guides/index.md | 2 +- src/styles/theme.css | 1 + 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/side-nav/side-nav.module.css b/src/components/side-nav/side-nav.module.css index 4f1cbf97..f61fbec8 100644 --- a/src/components/side-nav/side-nav.module.css +++ b/src/components/side-nav/side-nav.module.css @@ -48,6 +48,10 @@ .compactMenuSectionList { list-style: none; margin: 0 0 var(--size-6) 0; + + & a:hover { + opacity: 0.7; + } } .compactMenuSectionList .active { 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 76a7b644..cf5cac30 100644 --- a/src/components/table-of-contents/table-of-contents.module.css +++ b/src/components/table-of-contents/table-of-contents.module.css @@ -55,13 +55,17 @@ border-left: 2px solid var(--color-gray); border-top: 2px solid var(--color-gray); border-bottom: 2px solid var(--color-gray); - box-shadow: var(--shadow-2); + box-shadow: var(--shadow-4); padding: var(--size-2) 0 0 var(--size-7); & a, & li { font-size: var(--font-size-1); } + + & a:hover { + opacity: 0.7; + } } .compactMenu { diff --git a/src/layouts/guides.html b/src/layouts/guides.html index 95af4a1f..be5657db 100644 --- a/src/layouts/guides.html +++ b/src/layouts/guides.html @@ -76,8 +76,8 @@ min-width: 25%; vertical-align: top; padding: var(--size-4) 0 0 var(--size-fluid-5); - background-color: var(--color-gray); - border-radius: var(--radius-3); + background-color: var(--color-gray-background); + border-radius: 0 var(--radius-3) var(--radius-3) 0; } app-toc { @@ -86,6 +86,7 @@ width: 20%; position: sticky; top: var(--size-4); + margin: 0 var(--size-1) 0 0; } } @@ -96,7 +97,7 @@ } app-side-nav { - padding: var(--size-4) 0 0 var(--size-fluid-7); + padding: var(--size-4) var(--size-1) 0 var(--size-fluid-6); } } diff --git a/src/pages/guides/getting-started/key-concepts.md b/src/pages/guides/getting-started/key-concepts.md index f57fa130..1114fc92 100644 --- a/src/pages/guides/getting-started/key-concepts.md +++ b/src/pages/guides/getting-started/key-concepts.md @@ -65,7 +65,7 @@ As an example ## Scripts and Styles -As demonstrated above, we can create an inline ` + + `; + + this.attachShadow({ mode: "open" }); + this.shadowRoot.appendChild(template.content.cloneNode(true)); + } + } +} + +customElements.define("app-footer", FooterComponent); +``` + +What we've done is: + +1. Create a `class FooterComponent` that `extends HTMLElement` +1. If no shadow root is detected, create a `