From 9354b3c19aacc513a32db22a9013af540112db4a Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Mon, 13 Jan 2025 21:10:17 -0500 Subject: [PATCH] apply ctc component to all guides content --- src/pages/guides/ecosystem/htmx.md | 101 ++-- src/pages/guides/ecosystem/index.md | 126 +++-- src/pages/guides/ecosystem/storybook.md | 410 +++++++++------- src/pages/guides/ecosystem/tailwind.md | 206 +++++--- src/pages/guides/ecosystem/web-test-runner.md | 384 ++++++++------- src/pages/guides/getting-started/index.md | 49 +- .../guides/getting-started/key-concepts.md | 72 +++ .../guides/getting-started/walkthrough.md | 438 +++++++++++------- src/pages/guides/hosting/aws.md | 115 +++-- src/pages/guides/hosting/cloudflare.md | 19 +- src/pages/guides/hosting/github.md | 122 +++-- src/pages/guides/hosting/index.md | 99 ++-- src/pages/guides/hosting/netlify.md | 45 +- src/pages/guides/hosting/vercel.md | 38 +- .../tutorials/full-stack-web-components.md | 393 +++++++++------- src/pages/guides/tutorials/theme-packs.md | 335 ++++++++------ 16 files changed, 1825 insertions(+), 1127 deletions(-) diff --git a/src/pages/guides/ecosystem/htmx.md b/src/pages/guides/ecosystem/htmx.md index 46a5bf93..65dd5014 100644 --- a/src/pages/guides/ecosystem/htmx.md +++ b/src/pages/guides/ecosystem/htmx.md @@ -16,9 +16,24 @@ tocHeading: 2 As with most libraries, just install **htmx.org** as a dependency using your favorite package manager: -```shell -npm i htmx.org -``` + + + + ```shell + npm i htmx.org + ``` + + ```shell + yarn add htmx.org + ``` + + ```shell + pnpm add htmx.org + ``` + + + + ## Example @@ -28,43 +43,57 @@ As a basic example, let's create a `
` in the client side that can send a r First we'll create our frontend including htmx in a ` - - - - - - -
- -

- - -``` + + + + + ```html + + + + + + +
+ + +
+ +

+ + + ``` + +
+ + ### 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", - }), - }); -} -``` + + + + + ```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. 🎯 diff --git a/src/pages/guides/ecosystem/index.md b/src/pages/guides/ecosystem/index.md index 9bca7b8b..4cf85311 100644 --- a/src/pages/guides/ecosystem/index.md +++ b/src/pages/guides/ecosystem/index.md @@ -9,46 +9,92 @@ layout: guides In most cases an `npm install` should be all you need to use any third party library and then include it in a ` - - - -``` + + + + ```shell + npm i jquery + ``` + + ```shell + yarn add jquery + ``` + + ```shell + pnpm add jquery + ``` + + + + + + + + + + ```html + + + + + + + ``` + + + + 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!

- - -``` + + + + ```shell + npm i open-props + ``` + + ```shell + yarn add open-props + ``` + + ```shell + pnpm add open-props + ``` + + + + + + + + + + ```html + + + + + + + + + +

Welcome to my website!

+ + + ``` + +
+ + diff --git a/src/pages/guides/ecosystem/storybook.md b/src/pages/guides/ecosystem/storybook.md index ddb8ad4a..2fa6404c 100644 --- a/src/pages/guides/ecosystem/storybook.md +++ b/src/pages/guides/ecosystem/storybook.md @@ -14,9 +14,17 @@ tocHeading: 2 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 -``` + + + + + ```shell + npx storybook@latest init + ``` + + + + As part of the prompts, we suggest the following answers to project type (**web_components**) and builder (**Vite**): @@ -43,48 +51,68 @@ We were not able to detect the right builder for your project. Please select one You should now be good to start writing your first story! 📚 -```js -// src/components/footer/footer.js -export default class Footer extends HTMLElement { - connectedCallback() { - this.innerHTML = ` - - `; + + + + + ```js + export default class Footer extends HTMLElement { + connectedCallback() { + this.innerHTML = ` +
+

Greenwood

+ +
+ `; + } } -} -customElements.define("app-footer", Footer); -``` + 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({}); -``` + + + ```js + import "./footer.js"; + + export default { + title: "Components/Footer", + }; + + const Template = () => ""; + + export const Primary = Template.bind({}); + ``` + + + + ## Static Assets To help with resolving any static assets used in your stories, you can configure [`staticDirs`](https://storybook.js.org/docs/api/main-config/main-config-static-dirs) to point to your Greenwood workspace. -```js -const config = { - //... + - staticDirs: ["../src"], -}; + -export default config; -``` + ```js + const config = { + staticDirs: ["../src"], + }; + + export default config; + ``` + + + + ## Import Attributes @@ -92,64 +120,72 @@ As [Vite does not support Import Attributes](https://github.com/vitejs/vite/issu In this example we are handling for CSS Module scripts: -```js -import { defineConfig } from "vite"; -import fs from "fs/promises"; -import path from "path"; -// 1) import the greenwood plugin and lifecycle helpers -import { greenwoodPluginStandardCss } from "@greenwood/cli/src/plugins/resource/plugin-standard-css.js"; -import { readAndMergeConfig } from "@greenwood/cli/src/lifecycles/config.js"; -import { initContext } from "@greenwood/cli/src/lifecycles/context.js"; - -// 2) initialize Greenwood lifecycles -const config = await readAndMergeConfig(); -const context = await initContext({ config }); -const compilation = { context, config }; - -// 3) initialize the plugin -const standardCssResource = greenwoodPluginStandardCss.provider(compilation); - -// 4) customize Vite -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; - } - }, - }; -} + + + + + ```js + import { defineConfig } from "vite"; + import fs from "fs/promises"; + import path from "path"; + // 1) import the greenwood plugin and lifecycle helpers + import { greenwoodPluginStandardCss } from "@greenwood/cli/src/plugins/resource/plugin-standard-css.js"; + import { readAndMergeConfig } from "@greenwood/cli/src/lifecycles/config.js"; + import { initContext } from "@greenwood/cli/src/lifecycles/context.js"; + + // 2) initialize Greenwood lifecycles + const config = await readAndMergeConfig(); + const context = await initContext({ config }); + const compilation = { context, config }; + + // 3) initialize the plugin + const standardCssResource = greenwoodPluginStandardCss.provider(compilation); + + // 4) customize Vite + 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({ - // 5) add it the plugins option - plugins: [transformConstructableStylesheetsPlugin()], -}); -``` + export default defineConfig({ + // 5) add it the plugins option + plugins: [transformConstructableStylesheetsPlugin()], + }); + ``` + + + + Phew, should be all set now. @@ -159,45 +195,53 @@ If you're using one of Greenwood's [resource plugins](/docs/plugins/), you'll ne 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 create a wrapping Vite plugin to handle this transformation. -```js -import { defineConfig } from "vite"; -import fs from "fs/promises"; -// 1) import the greenwood plugin and lifecycle helpers -import { greenwoodPluginImportRaw } from "@greenwood/plugin-import-raw"; -import { readAndMergeConfig } from "@greenwood/cli/src/lifecycles/config.js"; -import { initContext } from "@greenwood/cli/src/lifecycles/context.js"; - -// 2) initialize Greenwood lifecycles -const config = await readAndMergeConfig(); -const context = await initContext({ config }); -const compilation = { context, config }; - -// 3) initialize the plugin -const rawResource = greenwoodPluginImportRaw()[0].provider(compilation); - -// 4) 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; - } - }, - }; -} + + + + + ```js + import { defineConfig } from "vite"; + import fs from "fs/promises"; + // 1) import the greenwood plugin and lifecycle helpers + import { greenwoodPluginImportRaw } from "@greenwood/plugin-import-raw"; + import { readAndMergeConfig } from "@greenwood/cli/src/lifecycles/config.js"; + import { initContext } from "@greenwood/cli/src/lifecycles/context.js"; + + // 2) initialize Greenwood lifecycles + const config = await readAndMergeConfig(); + const context = await initContext({ config }); + const compilation = { context, config }; + + // 3) initialize the plugin + const rawResource = greenwoodPluginImportRaw()[0].provider(compilation); + + // 4) 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({ - // 5) add it the plugins option - plugins: [transformRawImports()], -}); -``` + export default defineConfig({ + // 5) add it the plugins option + plugins: [transformRawImports()], + }); + ``` + + + + ## Content as Data @@ -206,54 +250,82 @@ If you are using any of Greenwood's Content as Data [Client APIs](/docs/content- This can be accomplished with the [**storybook-addon-fetch-mock**](https://storybook.js.org/addons/storybook-addon-fetch-mock) addon and configuring it with the right `matcher.url` and `matcher.response` 1. First, install the **storybook-addon-fetch-mock** addon - ```shell - $ npm i -D storybook-addon-fetch-mock - ``` + + + + + + ```shell + npm i -D storybook-addon-fetch-mock + ``` + + ```shell + yarn add storybook-addon-fetch-mock --save-dev + ``` + + ```shell + pnpm add -D storybook-addon-fetch-mock + ``` + + + + + 1. Then add it to your _.storybook/main.js_ configuration file as an **addon** - ```js - const config = { - // ... + + + - addons: [ - // your plugins here... - "storybook-addon-fetch-mock", - ], - }; + ```js + const config = { + addons: [ + "storybook-addon-fetch-mock", + ], + }; - export default config; - ``` + export default config; + ``` + + + + 1. Then in your story files, configure your Story to return mock data - ```js - import "./blog-posts-list.js"; - import pages from "../../stories/mocks/graph.json"; - - export default { - // ... - - // configure fetchMock - parameters: { - fetchMock: { - mocks: [ - { - matcher: { - url: "http://localhost:1984/___graph.json", - response: { - // this is an example of mocking out getContentByRoute - body: pages.filter((page) => page.route.startsWith("/blog/")), - }, - }, - }, - ], - }, - }, - }; - - const Template = () => ""; - - export const Primary = Template.bind({}); - ``` + + + + + ```js + import "./blog-posts-list.js"; + import pages from "../../stories/mocks/graph.json"; + + export default { + parameters: { + fetchMock: { + mocks: [ + { + matcher: { + url: "http://localhost:1984/___graph.json", + response: { + // this is an example of mocking out getContentByRoute + body: pages.filter((page) => page.route.startsWith("/blog/")), + }, + }, + }, + ], + }, + }, + }; + + const Template = () => ""; + + export const Primary = Template.bind({}); + ``` + + + + > To quickly get a "mock" graph to use in your stories, you can run `greenwood build` and copy the _graph.json_ file from the build output directory. diff --git a/src/pages/guides/ecosystem/tailwind.md b/src/pages/guides/ecosystem/tailwind.md index b156bfc4..da7e56d2 100644 --- a/src/pages/guides/ecosystem/tailwind.md +++ b/src/pages/guides/ecosystem/tailwind.md @@ -16,90 +16,170 @@ As Tailwind is a PostCSS plugin, you'll need to take a couple of extra steps to 1. Let's install Tailwind and needed dependencies into our project, including Greenwood's [PostCSS plugin](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-postcss) - ```shell - npm install -D @greenwood/plugin-postcss tailwindcss autoprefixer - ``` + + + + + ```shell + npm i -D @greenwood/plugin-postcss tailwindcss autoprefixer + ``` + + ```shell + yarn add @greenwood/plugin-postcss tailwindcss autoprefixer --save-dev + ``` + + ```shell + pnpm add -D @greenwood/plugin-postcss tailwindcss autoprefixer + ``` + + + + 1. Now run the Tailwind CLI to initialize our project with Tailwind - ```shell - npx tailwindcss init - ``` + + + + + ```shell + $ npx tailwindcss init + ``` + + + + 1. Create _**two**_ PostCSS configuration files (two files are needed in Greenwood to support 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 + module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, + }; + ``` + + + + + + + + + + ```js + 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 + @tailwind base; + @tailwind components; + @tailwind utilities; + ``` + + + + 1. And include that in your layouts or pages - ```html - - - - - - - - - - ``` + + + + + ```html + + + + + + + + + ``` + + + + Now you're ready to start using Tailwind! 🎯 -```html - - - - - - - -

Welcome to my website!

- - -``` + + + + + ```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 88df4fda..45655888 100644 --- a/src/pages/guides/ecosystem/web-test-runner.md +++ b/src/pages/guides/ecosystem/web-test-runner.md @@ -16,100 +16,155 @@ For the sake of this guide, we will be covering a minimal setup but you are free 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 + ``` + + ```shell + yarn add @web/test-runner @web/test-runner-junit-reporter --save-dev + ``` + + ```shell + pnpm add -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 + ``` + + ```shell + yarn add @esm-bundle/chai --save-dev + ``` + + ```shell + pnpm add -D @esm-bundle/chai + ``` + + + + 1. Next, create a basic _web-test-runner.config.js_ configuration file - ```js - 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", - }, - }; - ``` + + + + + ```js + 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", + }, + }; + ``` + + + + ## Usage With everything install and configured, you should now be good to start writing your tests! 🏆 -```js -// src/components/footer/footer.js -export default class Footer extends HTMLElement { - connectedCallback() { - this.innerHTML = ` - - `; + + + + + ```js + // src/components/footer/footer.js + export default class Footer extends HTMLElement { + connectedCallback() { + this.innerHTML = ` +
+

Greenwood

+ +
+ `; + } } -} -customElements.define("app-footer", Footer); -``` + 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); - }); + + + ```js + describe("Components/Footer", () => { + let footer; - it("should have the expected heading", () => { - const header = footer.querySelectorAll("footer .heading"); + before(async () => { + footer = document.createElement("app-footer"); + document.body.appendChild(footer); - expect(header.length).equal(1); - expect(header[0].textContent).to.equal("Greenwood"); + await footer.updateComplete; }); - it("should have the expected logo image", () => { - const logo = footer.querySelectorAll("footer img[src]"); + describe("Default Behavior", () => { + it("should not be null", () => { + expect(footer).not.equal(undefined); + expect(footer.querySelectorAll("footer").length).equal(1); + }); - expect(logo.length).equal(1); - expect(logo[0]).not.equal(undefined); + it("should have the expected heading", () => { + const header = footer.querySelectorAll("footer .heading"); + + expect(header.length).equal(1); + expect(header[0].textContent).to.equal("Greenwood"); + }); + + it("should have the expected logo image", () => { + const logo = footer.querySelectorAll("footer img[src]"); + + expect(logo.length).equal(1); + expect(logo[0]).not.equal(undefined); + }); }); - }); - after(() => { - footer.remove(); - footer = null; + after(() => { + footer.remove(); + footer = null; + }); }); -}); -``` + ``` + + + + ## Static Assets @@ -122,26 +177,31 @@ If you are seeing logging about static assets returning 404 You can create a custom middleware in your _web-test-runner.config.js_ to resolve these requests to your local workspace: -```js -import path from "path"; -// ... + + + -export default { - // ... + ```js + import path from "path"; - middleware: [ - function resolveAssets(context, next) { - const { url } = context.request; + export default { + middleware: [ + function resolveAssets(context, next) { + const { url } = context.request; - if (url.startsWith("/assets")) { - context.request.url = path.join(process.cwd(), "src", url); - } + if (url.startsWith("/assets")) { + context.request.url = path.join(process.cwd(), "src", url); + } - return next(); - }, - ], -}; -``` + return next(); + }, + ], + }; + ``` + + + + ## Resource Plugins @@ -149,46 +209,52 @@ If you're using one of Greenwood's [resource plugins](/docs/plugins/), you'll ne 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 create a wrapping WTR plugin to handle this transformation. -```js -import fs from "fs/promises"; -// 1) import the greenwood plugin and lifecycle helpers -import { greenwoodPluginImportRaw } from "@greenwood/plugin-import-raw"; -import { readAndMergeConfig } from "@greenwood/cli/src/lifecycles/config.js"; -import { initContext } from "@greenwood/cli/src/lifecycles/context.js"; - -// 2) initialize Greenwood lifecycles -const config = await readAndMergeConfig(); -const context = await initContext({ config }); -const compilation = { context, config }; - -// 3) initialize the plugin -const rawResourcePlugin = greenwoodPluginImportRaw()[0].provider(compilation); - -export default { - // ... - - // 4) add it the plugins option - plugins: [ - { - name: "import-raw", - async transform(context) { - const { url } = context.request; + + + + + ```js + import fs from "fs/promises"; + // 1) import the greenwood plugin and lifecycle helpers + import { greenwoodPluginImportRaw } from "@greenwood/plugin-import-raw"; + import { readAndMergeConfig } from "@greenwood/cli/src/lifecycles/config.js"; + import { initContext } from "@greenwood/cli/src/lifecycles/context.js"; + + // 2) initialize Greenwood lifecycles + const config = await readAndMergeConfig(); + const context = await initContext({ config }); + const compilation = { context, config }; + + // 3) initialize the plugin + const rawResourcePlugin = greenwoodPluginImportRaw()[0].provider(compilation); + + export default { + // 4) add it the plugins option + 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" }, + }; + } + }, + }, + ], + }; + ``` - 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" }, - }; - } - }, - }, - ], -}; -``` + ## Content as Data @@ -196,48 +262,56 @@ If you are using any of Greenwood's Content as Data [Client APIs](/docs/content- This can be done by overriding `window.fetch` and providing the desired response needed based on the API being called: -```js -import { expect } from "@esm-bundle/chai"; -import graph from "../../stories/mocks/graph.json" with { type: "json" }; -import "./blog-posts-list.js"; + -// override fetch to return a promise that resolves to our mock data -window.fetch = function () { - return new Promise((resolve) => { - // this is an example of mocking out getContentByRoute - resolve(new Response(JSON.stringify(graph.filter((page) => page.route.startsWith("/blog/"))))); - }); -}; + -// now we can test components as normal -describe("Components/Blog Posts List", () => { - let list; + ```js + import { expect } from "@esm-bundle/chai"; + import graph from "../../stories/mocks/graph.json" with { type: "json" }; + import "./blog-posts-list.js"; - before(async () => { - list = document.createElement("app-blog-posts-list"); - document.body.appendChild(list); + // override fetch to return a promise that resolves to our mock data + window.fetch = function () { + return new Promise((resolve) => { + // this is an example of mocking out getContentByRoute + resolve(new Response(JSON.stringify(graph.filter((page) => page.route.startsWith("/blog/"))))); + }); + }; - await list.updateComplete; - }); + // now we can test components as normal + describe("Components/Blog Posts List", () => { + let list; + + before(async () => { + list = document.createElement("app-blog-posts-list"); + document.body.appendChild(list); - describe("Default Behavior", () => { - it("should not be null", () => { - expect(list).not.equal(undefined); + await list.updateComplete; }); - it("should render list items for all our blog posts", () => { - expect(list.querySelectorAll("ul").length).to.be.equal(1); - expect(list.querySelectorAll("ul li").length).to.be.greaterThan(1); + describe("Default Behavior", () => { + it("should not be null", () => { + expect(list).not.equal(undefined); + }); + + it("should render list items for all our blog posts", () => { + expect(list.querySelectorAll("ul").length).to.be.equal(1); + expect(list.querySelectorAll("ul li").length).to.be.greaterThan(1); + }); + + // ... }); - // ... + after(() => { + list.remove(); + list = null; + }); }); + ``` - after(() => { - list.remove(); - list = null; - }); -}); -``` + + + > To quickly get a "mock" graph to use in your stories, you can run `greenwood build` and copy the _graph.json_ file from the build output directory. diff --git a/src/pages/guides/getting-started/index.md b/src/pages/guides/getting-started/index.md index 57b2e26c..40843f72 100644 --- a/src/pages/guides/getting-started/index.md +++ b/src/pages/guides/getting-started/index.md @@ -37,16 +37,23 @@ $ npm -v 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/ -``` + ```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: +Or you can also manually initialize a repository setting up and installing the Greenwood CLI yourself, like so: ```shell # make and change into your workspace directory @@ -62,16 +69,24 @@ $ npm i -D @greenwood/cli@latest Then setup some npm scripts in your _package.json_ for running Greenwood and make sure to set the `type` to **module**: -```json -{ - "type": "module", - "scripts": { - "dev": "greenwood develop", - "build": "greenwood build", - "serve": "greenwood serve" + + + + + ```json + { + "type": "module", + "scripts": { + "dev": "greenwood develop", + "build": "greenwood build", + "serve": "greenwood serve" + } } -} -``` + ``` + + + + ## Jump Right In diff --git a/src/pages/guides/getting-started/key-concepts.md b/src/pages/guides/getting-started/key-concepts.md index 67e3e314..50064dc9 100644 --- a/src/pages/guides/getting-started/key-concepts.md +++ b/src/pages/guides/getting-started/key-concepts.md @@ -38,6 +38,18 @@ Would yield the following routes: For the sake of this guide, pages can just be HTML, using just... normal HTML! You can include any ` - - - -
- -
- - - - -``` + + + + + ```html + + + My Blog + + + + + + + + +
+ +
+ + + + + ``` + +
+ + > Now you can do the same for an ``. See the [companion repo](https://github.com/ProjectEvergreen/greenwood-getting-started/) for a complete working example. Voila! All our pages now have a header and footer! 🎉 @@ -275,10 +361,24 @@ From there, we can reference this in our App layout with a ` - - - -

Search Page

- -
- - -
- -
- - -``` + + + + +

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} -
- `; + + + + + ```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 ` - - - - - - -``` + + + + + ```html + + + + Greenwood Demo - Full Stack Web Components + + + + + + + + ``` + + + + So with everything put together, this is what the final project structure would look like. diff --git a/src/pages/guides/tutorials/theme-packs.md b/src/pages/guides/tutorials/theme-packs.md index 2c43a876..9d809d6e 100644 --- a/src/pages/guides/tutorials/theme-packs.md +++ b/src/pages/guides/tutorials/theme-packs.md @@ -41,78 +41,108 @@ 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 = () => [ + ```json { - 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), - ], - }; + "name": "my-theme-pack", + "version": "0.1.0", + "description": "My Custom Greenwood Theme Pack", + "main": "my-theme-pack.js", + "type": "module", + "files": ["dist/"] + } + ``` + + + + + + + + + + ```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 }; -``` + export { myThemePack }; + ``` -_src/layouts/blog-post.html_ + -```html - - + - - - - + - - - - - -``` + -_src/styles/theme.css_ + ```html + + -```css -* { - color: red; -} -``` + + + + -_src/pages/index.md_ + + + + + + ``` -```md ---- -layout: "blog-post" ---- + -# Title of blog post + -Lorum Ipsum, this is a test. -``` + + + + + ```css + * { + color: red; + } + ``` + + + + + + + + + + ```md + --- + layout: "blog-post" + --- + + # Title of blog post + + Lorum Ipsum, this is a test. + ``` + + + + ## Development @@ -125,75 +155,90 @@ 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], - }; + + + + + ```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 }; -``` + export { myThemePackPlugin }; + ``` + + + + And our final _greenwood.config.js_ would look like this, which adds a "one-off" [resource plugin](/docs/reference/plugins-api/#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 = ["*"]; - } + ```js + import fs from "fs"; + import { myThemePackPlugin } from "./my-theme-pack.js"; + import { ResourceInterface } from "@greenwood/cli/src/lib/resource-interface.js"; - async shouldResolve(url) { - return ( - process.env.__GWD_COMMAND__ === "develop" && - url.pathname.indexOf(`/node_modules/${packageName}/`) >= 0 - ); - } + const packageName = JSON.parse(fs.readFileSync("./package.json", "utf-8")).name; - async resolve(url) { - const { userWorkspace } = this.compilation.context; - const { pathname, searchParams } = url; - const workspaceUrl = pathname.split(`/node_modules/${packageName}/dist/`)[1]; - const params = searchParams.size > 0 ? `?${searchParams.toString()}` : ""; + class MyThemePackDevelopmentResource extends ResourceInterface { + constructor(compilation, options) { + super(compilation, options); + this.extensions = ["*"]; + } - return new Request(new URL(`./${workspaceUrl}${params}`, userWorkspace)); + async shouldResolve(url) { + return ( + process.env.__GWD_COMMAND__ === "develop" && + url.pathname.indexOf(`/node_modules/${packageName}/`) >= 0 + ); + } + + async resolve(url) { + const { userWorkspace } = this.compilation.context; + const { pathname, searchParams } = url; + const workspaceUrl = pathname.split(`/node_modules/${packageName}/dist/`)[1]; + const params = searchParams.size > 0 ? `?${searchParams.toString()}` : ""; + + return new Request(new URL(`./${workspaceUrl}${params}`, userWorkspace)); + } } -} -export default { - plugins: [ - ...myThemePackPlugin({ - __isDevelopment: true, - }), - { - type: "resource", - name: "my-theme-pack:resource", - provider: (compilation, options) => new MyThemePackDevelopmentResource(compilation, options), - }, - ], -}; -``` + 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. @@ -203,36 +248,54 @@ You're all ready for development now! 🙌 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" + ```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. Add a `prepublish` script to your _package.json_ to create the _dist/_ directory with all the needed 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 @@ -244,7 +307,7 @@ For users, they would just need to do the following: 1. Install the plugin from npm ```shell - $ npm install my-theme-pack --save-dev + $ npm i -D my-theme-pack ``` 1. Add the plugin to their _greenwood.config.js_ @@ -295,7 +358,7 @@ ex. - ... + ```