Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose mustache partials #33

Merged
merged 7 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion brut/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "brut",
"version": "0.0.31",
"version": "0.0.32",
"private": false,
"license": "MIT",
"repository": {
Expand Down
86 changes: 75 additions & 11 deletions brut/src/buildPages.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @typedef {import('.').Config} Config */

import fs from "fs-extra";
import { resolve } from "path";
import { resolve, basename, extname } from "path";
import { cwd } from "process";
import { load } from "js-yaml";
import { unified } from "unified";
Expand All @@ -14,7 +14,7 @@ import { minify as minifier } from "html-minifier-terser";

const { writeFile, readFile, readdir, ensureDir } = fs;

/** @typedef {Object<string, string>} Frontmatter */
/** @typedef {{[x: string]: string}} Frontmatter */

/**
* Extracts frontmatter with `---` or `<!-- -->` delimiters from a string.
Expand Down Expand Up @@ -93,20 +93,21 @@ async function minify(html) {

/**
* Get the page's build script and run it on the html.
* @param {string} content
* @param {string} content html content
* @param {Frontmatter} frontmatter
* @param {string} path
* @param {string} path page slug (for `/posts/first.html`, this would be `/posts/first/`)
* @param {{[x: string]: string}} templates key-value representation of all templates
* @param {{[x: string]: string}} partials key-value representation of all partials
* @returns {Promise<string>}
*/
async function buildPage(content, frontmatter, path) {
async function buildPage(content, frontmatter, path, templates, partials) {
// 1. inject into the template
const hasTemplate = !!frontmatter.template;
if (hasTemplate) {
const template = await readFile(cwd() + frontmatter.template, "utf-8");
content = mustache.render(
template,
frontmatter, // variables
{ content } // partials
templates[frontmatter.template],
frontmatter, // variables a.k.a. view
{ content, ...partials } // partials
);
}
// 2. run build script
Expand All @@ -120,6 +121,7 @@ async function buildPage(content, frontmatter, path) {
}

/**
* Recursively list files in a directory
* @param {string} dir
* @returns {Promise<string[]>}
*/
Expand All @@ -134,13 +136,69 @@ async function getFiles(dir) {
return files.flat();
}

/**
* Loads templates from the filesystem and returns them as a key-value object
* such as `{ default: "template-source" }`
* @param {string} templatesDir
* @returns {Promise<{[x: string]: string}>}
}
*/
async function loadTemplates(templatesDir) {
const templatePaths = await getFiles(templatesDir);
const templates = /** @type {{[x: string]: string}} */ ({});
await Promise.all(
templatePaths.map(async (templatePath) => {
const templateName = basename(templatePath).replace(
extname(templatePath),
""
);
const templateSource = await readFile(templatePath, "utf-8");
templates[templateName] = templateSource;
})
);
return templates;
}

/**
* Loads partials from the filesystem and returns them as a key-value object
* such as `{ nav: "partial-source" }`
* @param {string} partialsDir
* @returns {Promise<{[x: string]: string}>}
}
*/
async function loadPartials(partialsDir) {
const partialPaths = await getFiles(partialsDir);
const partials = /** @type {{[x: string]: string}} */ ({});
await Promise.all(
partialPaths.map(async (partialPath) => {
const partialName = basename(partialPath).replace(
extname(partialPath),
""
);
const partialSource = await readFile(partialPath, "utf-8");
partials[partialName] = partialSource;
})
);
return partials;
}

/**
* @param {Config} config
* @returns {Promise<void>}
*/
export default async function buildPages({ outDir: outDirRoot, pagesDir }) {
export default async function buildPages({
outDir: outDirRoot,
pagesDir,
templatesDir,
partialsDir,
}) {
try {
const paths = await getFiles(pagesDir);
const [templates, partials] = await Promise.all([
loadTemplates(templatesDir),
loadPartials(partialsDir),
]);

await Promise.all(
paths.map(async (path) => {
// only process markdown or html pages
Expand All @@ -164,7 +222,13 @@ export default async function buildPages({ outDir: outDirRoot, pagesDir }) {
}
// 4. build page and write to fs
const relPath = outDir.replace(outDirRoot, "");
const result = await buildPage(html, frontmatter, relPath);
const result = await buildPage(
html,
frontmatter,
relPath,
templates,
partials
);
// TEMP: handle 404 pages for Cloudflare Pages
if (outDir === `${outDirRoot}/404`) {
await writeFile(`${outDir}.html`, result);
Expand Down
8 changes: 8 additions & 0 deletions brut/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const { emptyDir } = fs;
/**
* @typedef {Object} ConfigObject
* @property {string} [pagesDir] The top-level directory containing pages. Defaults to `/pages`.
* @property {string} [templatesDir] The top-level directory containing templates. Defaults to `/templates`.
* @property {string} [partialsDir] The top-level directory containing partials. Defaults to `/partials`.
* @property {string} [publicDir] The top-level directory containing static assets to copy to the `outDir`. Defaults to `/public`.
* @property {string} [outDir] The top-level directory for the build output. Defaults to `/dist`.
*/
Expand All @@ -22,6 +24,12 @@ async function getConfig() {
pagesDir: configObject.pagesDir
? `${cwd()}${configObject.pagesDir}`
: `${cwd()}/pages`,
templatesDir: configObject.templatesDir
? `${cwd()}${configObject.templatesDir}`
: `${cwd()}/templates`,
partialsDir: configObject.partialsDir
? `${cwd()}${configObject.partialsDir}`
: `${cwd()}/partials`,
publicDir: configObject.publicDir
? `${cwd()}${configObject.publicDir}`
: `${cwd()}/public`,
Expand Down
2 changes: 1 addition & 1 deletion www/pages/404.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
template: /templates/default.html
template: default
---

## Not found
2 changes: 1 addition & 1 deletion www/pages/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
template: /templates/default.html
template: default
-->

<h1>Welcome to Brut</h1>
Expand Down
2 changes: 1 addition & 1 deletion www/pages/posts.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
template: /templates/default.html
template: default
buildScript: /scripts/buildPostsPage.js
---

Expand Down
2 changes: 1 addition & 1 deletion www/pages/posts/second-post.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
template: /templates/default.html
template: default
title: "Second Post"
date: "2022-02-12"
---
Expand Down
2 changes: 1 addition & 1 deletion www/pages/posts/unpopular-opinion.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
template: /templates/default.html
template: default
title: "Unpopular Opinion"
date: "2021-11-12"
---
Expand Down
8 changes: 8 additions & 0 deletions www/partials/nav.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/posts">Blog</a></li>
</ul>
</nav>
</header>
7 changes: 1 addition & 6 deletions www/templates/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,7 @@
/>
</head>
<body>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/posts">Blog</a></li>
</ul>
</nav>
{{> nav}}
<main>{{> content}}</main>
</body>
</html>
Loading