Right now, the experience of browsing our tree of generated HTML is a little unsatisfying because there are no index pages — we have to know what pages exist and manually enter a valid URL.
We can fix that by adding another tree transform. Wrapping a tree with this transform will:
- add “index.html” as one of the inner tree’s keys if it’s not already defined
- define a default value for “index.html” if one isn’t already defined
The default value will be an HTML page listing the tree’s other keys as links.
Generate a single index page
First let’s write a function that returns a reasonable default index page for a tree that doesn’t define one.
/* In src/index/indexPages.js */
// Return a default index page for the given tree.
async function indexPage(tree) {
// Collect all the keys in the tree.
const keys = [];
for (const key of await tree.keys()) {
// Ignore the key `index.html`
if (key !== "index.html") {
keys.push(key);
}
}
// Map the keys to HTML links.
const links = keys.map((key) => {
const basename = key.replace(/\..+$/, "");
return ` <li><a href="${key}">${basename}</a></li>`;
});
// Incorporate the link HTML into a simple page.
const html = `<!DOCTYPE html>
<html>
<body>
<ul>
${links.join("\n")}
</ul>
</body>
</html>`;
return html;
}
If the little more
branch of our HTML tree looks like this:
Then invoking indexPage
on this branch will return:
<!DOCTYPE html>
<html>
<body>
<ul>
<li><a href="David.html">David</a></li>
<li><a href="Eve.html">Eve</a></li>
</ul>
</body>
</html>
Transform a tree by adding index pages
Using the default indexPage
function above, let’s now create a tree transform. This will accept any async tree, and return a new tree with an index.html
key.
/* In src/index/indexPages.js */
export default function indexPages(tree) {
return {
async get(key) {
let value = await tree.get(key);
if (value === undefined && key === "index.html") {
// No index page defined; create one
return indexPage(tree);
} else {
// If we're returning an async subtree, add index pages to it too.
const isAsyncDictionary =
typeof value?.get === "function" && typeof value?.keys === "function";
return isAsyncDictionary ? indexPages(value) : value;
}
},
async keys() {
// Yield the keys of the tree and remember what they are.
const keys = new Set(await tree.keys());
// Add index.html to the set of keys.
keys.add("index.html");
return keys;
},
};
}
If we use this to transform the more
branch of the HTML tree, the transformed tree now includes an index.html
page:
Incorporate the index page transform
We can apply this indexPages
transform on top of our object, file, and function-based HTML trees. For example, the file-based tree now looks like:
/* src/index/htmlFiles.js */
import tree from "./files.js";
import indexPages from "./indexPages.js";
import transform from "./transform.js";
export default indexPages(transform(tree));
These transforms are just functions, so we can apply as many tree transforms as we want.
In this case, the order of function calls matters: when the indexPages
transform is iterating through the keys of a tree, we want it to see keys that end in .html
so that it create links to the HTML pages. If we applied the indexPages
transform first, it would create links to the .md
files.
From inside the src/index
directory, serve the HTML tree again.
$ cd../index
$ node serve
Server running at http://localhost:5000. Press Ctrl+C to stop.
Browse the site to see index pages at each level of the hierarchy.
This server can be good enough for development, and we could easily deploy it in this state. But since this HTML content is currently all static, it would be nice to render the HTML content as .html
files we can deploy directly.
Before moving on, in the terminal window, stop the server by pressing Ctrl+C.
Next: Copy a tree »