Combine trees

Now that we have a basic site working in tree form, we can manipulate that tree to suit our needs without needing to adjust our server and build infrastructure. For example, we can readily combine trees to create larger trees.

In the course of this tutorial, we’ve created three parallel tree implementations, backed by an object, a folder tree, and a function. We can combine those trees to make a larger tree with three branches, each of which is defined differently, but each of which can be explored using the same AsyncTree interface.

Using ObjectTree to combine trees

Our ObjectTree class turns out to be a useful tool to combine the three trees of HTML pages from htmlFiles.js, htmlFn.js, and htmlObject.js. We take the exports from those three files, then use them as values in an object. The keys of that object will name the tree’s branches — files, function, and object — letting us route requests into the appropriate branch with those names.

We can define this combination by updating siteTree.js:

/* src/combine/siteTree.js */

import htmlFiles from "./htmlFiles.js";
import htmlFn from "./htmlFn.js";
import htmlObject from "./htmlObject.js";
import indexPages from "./indexPages.js";
import ObjectTree from "./ObjectTree.js";

const combined = new ObjectTree({
  files: htmlFiles,
  function: htmlFn,
  object: htmlObject,

export default indexPages(combined);

We apply our indexPages transform to give the overall tree an index page. Having done that, we can drop the use of indexPages in the individual trees. For example, the object-backed tree in htmlObject.js no longer needs to define index pages:

/* src/combine/htmlObject.js */

import tree from "./object.js";
import transform from "./transform.js";

export default transform(tree);

To contrast the values coming from each branch of this tree, we can update the object, files, and function trees so that they each define different names.

Our combined tree is quite large:

g files ->files files function ->function function object ->object object index.html <!DOCTYPE html> <html> <body> <ul>… ->index.html index.html files/Frank.html <p>Hello, <strong>Frank</strong>.</p> files->files/Frank.html Frank.html files/Grace.html <p>Hello, <strong>Grace</strong>.</p> files->files/Grace.html Grace.html files/Henry.html <p>Hello, <strong>Henry</strong>.</p> files->files/Henry.html Henry.html files/more files->files/more more files/index.html <!DOCTYPE html> <html> <body> <ul>… files->files/index.html index.html files/more/Isabella.html <p>Hello, <strong>Isabella</strong>.</p> files/more->files/more/Isabella.html Isabella.html files/more/James.html <p>Hello, <strong>James</strong>.</p> files/more->files/more/James.html James.html files/more/index.html <!DOCTYPE html> <html> <body> <ul>… files/more->files/more/index.html index.html function/Kelly.html <p>Hello, <strong>Kelly</strong>.</p> function->function/Kelly.html Kelly.html function/Larry.html <p>Hello, <strong>Larry</strong>.</p> function->function/Larry.html Larry.html function/Michelle.html <p>Hello, <strong>Michelle</strong>.</p> function->function/Michelle.html Michelle.html function/index.html <!DOCTYPE html> <html> <body> <ul>… function->function/index.html index.html object/Alice.html <p>Hello, <strong>Alice</strong>.</p> object->object/Alice.html Alice.html object/Bob.html <p>Hello, <strong>Bob</strong>.</p> object->object/Bob.html Bob.html object/Carol.html <p>Hello, <strong>Carol</strong>.</p> object->object/Carol.html Carol.html object/more object->object/more more object/index.html <!DOCTYPE html> <html> <body> <ul>… object->object/index.html index.html object/more/David.html <p>Hello, <strong>David</strong>.</p> object/more->object/more/David.html David.html object/more/Eve.html <p>Hello, <strong>Eve</strong>.</p> object/more->object/more/Eve.html Eve.html object/more/index.html <!DOCTYPE html> <html> <body> <ul>… object/more->object/more/index.html index.html

Each of the three main branches of this tree is defined in a different way, with different pros and cons. When constructing a real site, this flexibility lets us pick the most appropriate implementation for any part of the site. And when our needs inevitably change, we can switch those implementations around without needing to change any of our surrounding infrastructure.

Serving and building

Run the updated server from inside the src/combine directory.

$ cd ../combine
$ node serve
Server running at http://localhost:5000. Press Ctrl+C to stop.

Browse the combined site.

Node that the function route can handle arbitrary requests. E.g., if you browse to function/Sara.html, you’ll see a page for Sara.

Stop the server and run the build process to generate the pages for the full, combined tree.

$ node build
$ ls dist
files      function   index.html object

The build process is copying the virtual tree into real files. Among other things, this will create files for all the values in the keys provided by the dynamic function route. But now that the files are static, the pages provided by that function route are essentially frozen.

If it’s necessary that your site handle dynamic requests, then you would have to serve the live site tree (not static files). It would also be possible to blend approaches: use a build process to generate static files for those things that can be statically generated, and use a live server for the remaining portion.


Next: Tree operations »