Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3b6b925
initial working implementation of standalone markdown plugin
thescientist13 Mar 15, 2025
7aa3ff2
move ToC heading loading to markdown plugin
thescientist13 Mar 17, 2025
aed74de
minimal interop with graphql plugin
thescientist13 Mar 20, 2025
fcaffec
adopt correct repository field convention in package.json
thescientist13 Apr 27, 2025
873a5a2
migrate standard markdown plugin test case to markdown plugin package
thescientist13 Apr 27, 2025
b8fdb70
use String constructor
thescientist13 Apr 27, 2025
d1f3be0
move markdown plugins test case to markdown package
thescientist13 Apr 27, 2025
d8642c7
migrate WCC prerendering in markdown test case to markdown plugin
thescientist13 Apr 27, 2025
bff0645
migrate markdown 404 page test case to markdown plugin
thescientist13 Apr 27, 2025
6fa5002
misc rebase sync changes
thescientist13 Aug 16, 2025
ff8564b
refactoring markdown out of all CLI test cases
thescientist13 Aug 17, 2025
8d21b0c
refine 404 handling
thescientist13 Aug 17, 2025
119b391
add context plugin test cases to markdown plugin
thescientist13 Aug 17, 2025
22c2307
refine production server response statusText
thescientist13 Aug 17, 2025
98cdb3e
handle markdown and custom elements p tag wrapping
thescientist13 Aug 17, 2025
d8dc871
enable test logging for failing test case
thescientist13 Aug 17, 2025
127b3e4
refactor preServe transforming in development server
thescientist13 Aug 19, 2025
a19c42b
clean up resource plugin filtering in serve lifecycle
thescientist13 Aug 19, 2025
1e220f1
one more attempt at dev server test case failures
thescientist13 Aug 19, 2025
9f11d4b
refactor away from preServe lifecycle
thescientist13 Aug 26, 2025
c5c49fe
cleanup
thescientist13 Aug 26, 2025
a2842dc
more preServe cleanup
thescientist13 Aug 26, 2025
00ad749
add clone
thescientist13 Aug 26, 2025
dd8d956
bump alpha version
thescientist13 Aug 26, 2025
8186ef0
update lock file
thescientist13 Aug 26, 2025
f0ba508
rollback preServe related changes
thescientist13 Aug 26, 2025
c053f1a
revert back to async reduce pattern in serve lifecycle for dev server…
thescientist13 Aug 30, 2025
feb3df5
restore commander
thescientist13 Aug 30, 2025
4c1bb57
fix static router test cases
thescientist13 Aug 31, 2025
8fe7348
restore more test cases
thescientist13 Aug 31, 2025
7af354b
refactor servePage option for custom page formats
thescientist13 Aug 31, 2025
e986403
remove frontmatter dependency
thescientist13 Aug 31, 2025
95a9c7b
document markdown plugin options
thescientist13 Aug 31, 2025
eb5a27b
restore frontmatter plugin
thescientist13 Aug 31, 2025
c9abc33
standalone markdown processing function
thescientist13 Sep 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions greenwood.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { greenwoodPluginPolyfills } from "@greenwood/plugin-polyfills";
import { greenwoodPluginPostCss } from "@greenwood/plugin-postcss";
import { greenwoodPluginImportRaw } from "@greenwood/plugin-import-raw";
import { greenwoodPluginRendererPuppeteer } from "@greenwood/plugin-renderer-puppeteer";
import { greenwoodPluginMarkdown } from "@greenwood/plugin-markdown";
import rollupPluginAnalyzer from "rollup-plugin-analyzer";

/** @type {import('@greenwood/cli').Config} */
Expand All @@ -14,6 +15,9 @@ export default {
activeContent: true,
prerender: true,
plugins: [
greenwoodPluginMarkdown({
plugins: ["@mapbox/rehype-prism", "rehype-slug", "rehype-autolink-headings", "remark-github"],
}),
greenwoodPluginGraphQL(),
greenwoodPluginPolyfills({
lit: true,
Expand All @@ -39,7 +43,4 @@ export default {
},
},
],
markdown: {
plugins: ["@mapbox/rehype-prism", "rehype-slug", "rehype-autolink-headings", "remark-github"],
},
};
19 changes: 10 additions & 9 deletions greenwood.config.test-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { greenwoodPluginImportCommonJs } from "@greenwood/plugin-import-commonjs
import { greenwoodPluginImportJsx } from "@greenwood/plugin-import-jsx";
import { greenwoodPluginImportRaw } from "@greenwood/plugin-import-raw";
import { greenwoodPluginIncludeHTML } from "@greenwood/plugin-include-html";
import { greenwoodPluginMarkdown } from "@greenwood/plugin-markdown";
import { greenwoodPluginPolyfills } from "@greenwood/plugin-polyfills";
import { greenwoodPluginPostCss } from "@greenwood/plugin-postcss";
import { greenwoodPluginRendererLit } from "@greenwood/plugin-renderer-lit";
Expand Down Expand Up @@ -46,15 +47,6 @@ const config: Config = {
isolation: true,
layoutsDirectory: "/my-layouts",
optimization: "default",
markdown: {
plugins: [
"@mapbox/rehype-prism",
{
name: "rehype-autolink-headings",
options: { behavior: "append" },
},
],
},
pagesDirectory: "/my-pages",
plugins: [
greenwoodPluginAdapterAws(),
Expand All @@ -68,6 +60,15 @@ const config: Config = {
greenwoodPluginImportJsx(),
greenwoodPluginImportRaw,
greenwoodPluginIncludeHTML(),
greenwoodPluginMarkdown({
plugins: [
"@mapbox/rehype-prism",
{
name: "rehype-autolink-headings",
options: { behavior: "append" },
},
],
}),
greenwoodPluginPolyfills(),
greenwoodPluginPostCss(),
greenwoodPluginRendererLit(),
Expand Down
10 changes: 0 additions & 10 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,13 @@
"koa": "^3.0.1",
"koa-body": "^6.0.1",
"livereload": "^0.9.1",
"markdown-toc": "^1.2.0",
"node-html-parser": "^7.0.1",
"rehype-raw": "^7.0.0",
"rehype-stringify": "^10.0.1",
"remark-frontmatter": "^5.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.1",
"rollup": "^4.26.0",
"unified": "^11.0.5",
"wc-compiler": "~0.17.0"
},
"devDependencies": {
"@aws-sdk/client-cloudfront": "^3.726.0",
"@beforesemicolon/markup": "^1.14.1",
"@mapbox/rehype-prism": "^0.5.0",
"@shoelace-style/shoelace": "^2.18.0",
"@spectrum-css/card": "^9.3.0",
"@spectrum-web-components/action-menu": "^1.0.1",
Expand All @@ -92,8 +84,6 @@
"pwa-helpers": "^0.9.1",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"rehype-autolink-headings": "^4.0.0",
"rehype-slug": "^3.0.0",
"signal-utils": "^0.21.1",
"simpledotcss": "^1.0.0",
"tslib": "^2.8.1"
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/lib/layout-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,9 @@ async function mergeContentIntoLayout(
: childRoot.querySelector("html") && childBody
? childBody
: !childRoot.querySelector("html")
? childContents
? childRoot.querySelector("body")
? childBody
: childContents
: "";

// we wrap SSR content in comments so we can extract it during prerendering to avoid double pre-rendering
Expand Down
1 change: 0 additions & 1 deletion packages/cli/src/lifecycles/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ const defaultConfig = {
},
isolation: false,
layoutsDirectory: "layouts",
markdown: { plugins: [] },
optimization: optimizations[0],
pagesDirectory: "pages",
plugins: greenwoodPlugins,
Expand Down
51 changes: 12 additions & 39 deletions packages/cli/src/lifecycles/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import fs from "node:fs/promises";
import fm from "front-matter";
import { checkResourceExists, requestAsObject } from "../lib/resource-utils.js";
import { activeFrontmatterKeys } from "../lib/content-utils.js";
import toc from "markdown-toc";
import { Worker } from "node:worker_threads";

function getLabelFromRoute(_route) {
Expand All @@ -27,7 +26,7 @@ function getLabelFromRoute(_route) {
}

function getIdFromRelativePathPath(relativePathPath, extension) {
return relativePathPath.replace(extension, "").replace("./", "").replace(/\//g, "-");
return relativePathPath.replace(`.${extension}`, "").replace("./", "").replace(/\//g, "-");
}

function trackCollectionsForPage(page, collections) {
Expand Down Expand Up @@ -79,29 +78,26 @@ const generateGraph = async (compilation) => {
pages = nextPages.pages;
apiRoutes = nextPages.apiRoutes;
} else {
const extension = `.${filenameUrl.pathname.split(".").pop()}`;
const extension = `${filenameUrl.pathname.split(".").pop()}`;
const relativePagePath = filenameUrl.pathname.replace(pagesDir.pathname, "./");
const isApiRoute = relativePagePath.startsWith("./api");
const req = isApiRoute
? new Request(filenameUrl, { headers: { Accept: "text/javascript" } })
: new Request(filenameUrl, { headers: { Accept: "text/html" } });
let isCustom = null;

for (const plugin of customPageFormatPlugins) {
if (plugin.shouldServe && (await plugin.shouldServe(filenameUrl, req))) {
if (plugin.servePage && plugin.extensions.includes(extension)) {
isCustom = plugin.servePage;
break;
}
}

const isStatic = isCustom === "static" || extension === ".md" || extension === ".html";
const isDynamic = isCustom === "dynamic" || extension === ".js" || extension === ".ts";
const isStatic = isCustom === "static" || extension === "html";
const isDynamic = isCustom === "dynamic" || extension === "js" || extension === "ts";
const isPage = isStatic || isDynamic;
let route = `${relativePagePath.replace(".", "").replace(`${extension}`, "")}`;
let route = `${relativePagePath.replace(".", "").replace(`.${extension}`, "")}`;
let fileContents;

if (isApiRoute) {
if (extension !== ".js" && extension !== ".ts" && !isCustom) {
if (extension !== "js" && extension !== "ts" && !isCustom) {
console.warn(`${filenameUrl} is not a supported API file extension, skipping...`);
return;
}
Expand All @@ -123,12 +119,14 @@ const generateGraph = async (compilation) => {
getIdFromRelativePathPath(relativePagePath, extension).replace("api-", ""),
),
pageHref: new URL(relativePagePath, pagesDir).href,
outputHref: new URL(relativePagePath, outputDir).href.replace(extension, ".js"),
outputHref: new URL(relativePagePath, outputDir).href.replace(`.${extension}`, ".js"),
route: `${basePath}${route}`,
isolation,
});
} else if (isPage) {
let root = filename.split("/")[filename.split("/").length - 1].replace(extension, "");
let root = filename
.split("/")
[filename.split("/").length - 1].replace(`.${extension}`, "");
// should we even have a default page layout?
// https://github.com/ProjectEvergreen/greenwood/issues/1271
let layout = "page";
Expand Down Expand Up @@ -223,36 +221,11 @@ const generateGraph = async (compilation) => {
}
}

/*
* Custom front matter - Variable Definitions
* --------------------------------------------------
* collection: the name of the collection for the page (as a string or array)
* order: the order of this item within the collection
* tocHeading: heading size to use a Table of Contents for a page
* tableOfContents: json object containing page's table of contents (list of headings)
*/

// prune "reserved" attributes that are supported by Greenwood
// prune "reserved" frontmatter that are supported by Greenwood
[...activeFrontmatterKeys, "layout"].forEach((key) => {
delete customData[key];
});

// set flag whether to gather a list of headings on a page as menu items
customData.tocHeading = customData.tocHeading || 0;
customData.tableOfContents = [];

if (fileContents && customData.tocHeading > 0 && customData.tocHeading <= 6) {
// parse markdown for table of contents and output to json
customData.tableOfContents = toc(fileContents).json;

// parse table of contents for only the pages user wants linked
if (customData.tableOfContents.length > 0 && customData.tocHeading > 0) {
customData.tableOfContents = customData.tableOfContents.filter(
(item) => item.lvl === customData.tocHeading,
);
}
}

/*
* Page Properties
*----------------------
Expand Down
57 changes: 33 additions & 24 deletions packages/cli/src/lifecycles/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,13 @@ async function getDevServer(compilation) {
const app = new Koa();
const compilationCopy = Object.assign({}, compilation);
const resourcePlugins = [
// Greenwood default standard resource and import plugins
...compilation.config.plugins
.filter((plugin) => {
return plugin.type === "resource" && plugin.isGreenwoodDefaultPlugin;
return plugin.type === "resource";
})
.map((plugin) => {
return plugin.provider(compilationCopy);
}),

// custom user resource plugins
...compilation.config.plugins
.filter((plugin) => {
return plugin.type === "resource" && !plugin.isGreenwoodDefaultPlugin;
})
.map((plugin) => plugin.provider(compilationCopy)),
];

app.use(koaBody());
Expand All @@ -57,7 +49,7 @@ async function getDevServer(compilation) {
await next();
});

// handle creating responses from urls
// handle serving responses from urls
app.use(async (ctx, next) => {
try {
const url = new URL(ctx.url);
Expand All @@ -67,7 +59,7 @@ async function getDevServer(compilation) {
let response = new Response(null, { status });

for (const plugin of resourcePlugins) {
if (plugin.shouldServe && (await plugin.shouldServe(url, request))) {
if (!plugin.servePage && plugin.shouldServe && (await plugin.shouldServe(url, request))) {
const current = await plugin.serve(url, request);
const merged = mergeResponse(response.clone(), current.clone());

Expand Down Expand Up @@ -101,17 +93,26 @@ async function getDevServer(compilation) {
headers: new Headers(header),
});

let response = initResponse;
for (const plugin of resourcePlugins) {
// we explicitly use "async" reduce here to avoid race conditions / Body consumed errors
// when looping through and sharing responses between plugins
const response = await resourcePlugins.reduce(async (responsePromise, plugin) => {
const intermediateResponse = await responsePromise;
if (
plugin.shouldPreIntercept &&
(await plugin.shouldPreIntercept(url, request, response.clone()))
(await plugin.shouldPreIntercept(url, request, intermediateResponse.clone()))
) {
const current = await plugin.preIntercept(url, request, response.clone());

response = mergeResponse(response.clone(), current.clone());
const current = await plugin.preIntercept(
url,
request,
await intermediateResponse.clone(),
);
const merged = mergeResponse(intermediateResponse.clone(), current);

return Promise.resolve(merged);
} else {
return Promise.resolve(await responsePromise);
}
}
}, Promise.resolve(initResponse.clone()));

ctx.body = response.body ? Readable.from(response.body) : "";
ctx.message = response.statusText;
Expand All @@ -138,17 +139,22 @@ async function getDevServer(compilation) {
headers: new Headers(header),
});

let response = initResponse;
for (const plugin of resourcePlugins) {
// we explicitly use "async" reduce here to avoid race conditions / Body consumed errors
// when looping through and sharing responses between plugins
const response = await resourcePlugins.reduce(async (responsePromise, plugin) => {
const intermediateResponse = await responsePromise;
if (
plugin.shouldIntercept &&
(await plugin.shouldIntercept(url, request, response.clone()))
(await plugin.shouldIntercept(url, request, intermediateResponse.clone()))
) {
const current = await plugin.intercept(url, request, response.clone());
const current = await plugin.intercept(url, request, await intermediateResponse.clone());
const merged = mergeResponse(intermediateResponse.clone(), current);

response = mergeResponse(response.clone(), current.clone());
return Promise.resolve(merged);
} else {
return Promise.resolve(await responsePromise);
}
}
}, Promise.resolve(initResponse.clone()));

ctx.body = response.body ? Readable.from(response.body) : "";
ctx.message = response.statusText;
Expand Down Expand Up @@ -250,6 +256,7 @@ async function getStaticServer(compilation, composable) {
}
} else {
ctx.body = "Not Found";
ctx.message = "Not Found";
ctx.status = 404;
ctx.set("Content-Type", "text/plain");
}
Expand Down Expand Up @@ -323,6 +330,7 @@ async function getStaticServer(compilation, composable) {

ctx.body = body;
ctx.status = 200;
ctx.message = "OK";
ctx.set("Content-Type", "text/html");
}
} catch (e) {
Expand Down Expand Up @@ -392,6 +400,7 @@ async function getHybridServer(compilation) {
}

ctx.body = html;
ctx.message = "OK";
ctx.set("Content-Type", "text/html");
ctx.status = 200;
} else if (isApiRoute) {
Expand Down
Loading
Loading