It's very convenient when a static website generator can automatically watch the source files, automatically rebuilding any changed pages, and automatically updating a browser tab. Having the web page automatically rebuilt is almost as good as a WYSIWYG editor. Some statically generated website projects require complex directory configurations. A new Node.js package, spun off from the AkashaCMS project, aims to support such complex directory relationships, while automatically watching files in those directories, and emitting events that can drive automated rebuilding of changed or added files.
I began AkashaCMS development in 2013 with the goal of supporting a high traffic website on low end shared hosting, while using modern JavaScript and CSS techniques. Missing from the long list of features was the ability to automatically rebuild pages to get a full fledged preview and to detect errors early. Implementing that feature led to a major rewrite to AkashaCMS, with the core being a new standalone module that could be used by any static website generator.
That module, Stacked Directories, handles complex directory hierarchy relationships, the ability to override files, and automated scanning for changes (additions, changes and deletions) in directory hierarchies. In use, the application supplies a list of directories to watch and their relationship to locations in the rendered output directory.
The scope of the Stacked Directories module is
- Being configured with one or more directories whose files to watch
- An order of precedence between directories, such that a given file can exist in multiple directories, and the file to use is in the directory with the highest precedence
- Locating directories within a virtual filesystem space
- Detecting changes to files, addition of new files, and deletion of existing files, emitting corresponding events
What we mean by virtual filesystem space is the directory into which a statically generated website is constructed. Some such projects require aggregating content from multiple source directories, where some are meant to be rendered into a subdirectory of the website
This package does not support rendering content into HTML/CSS/etc, nor does it include a feature for automatically reloading a browser after a page is rebuilt. Instead it is meant for Stacked Directories to be built into a larger system that has these and other features. Inside the Stacked Directories repository is a sample static website generator, called SimpleCMS, that shows one way to use this package to support automatically rebuilding website content.
Overriding any file in static website project configuration
The image above shows one core feature of Stacked Directories. It comes from a core principle in AkashaCMS, namely that each file can be overridden. But, what does that mean?
Consider a module for implementing Blogs within a website. One feature of blogs is an index presented in reverse chronological order (the River of News for the blog). This mean the blog module would include templates to assist formatting blog post content, the River of News and other blog paraphernalia. The question is, how can the static website generator support customizing those templates? A theme package might want to customize those templates, as might the website owner.
That's where file overriding comes into play.
In AkashaCMS there is four stacks of directories: a) documents are where the document files live, b) assets are files like images or CSS or JavaScript which are simply copied into the output directory, c) layouts are for templates covering the whole page, and d) partials are for templates used in formatting specific elements. The theming task just described is handled with partial templates.
The image above describes the partials directory stack and the role of one file, news-river.html.njk
. This template is written using Nunjucks syntax, and has something to do with formatting a River of News. In this image each box is one directory in the partials directory stack. The top-most box in this image could be the website itself. The second box could be a packaged theme supplying theme-specific implementations of templates supplied by other plugins. The fourth box could be the blogging module. That module supplies the default news-river.html.njk
template, while the both the theme module and the website override it with their own implementation.
What's reported to the application is the top-most file in the stack.
That's one use-case, overriding files to support customizability.
Assembling static website content from multiple sources
Another use case is to assemble content from several sources. The marketing team, the technical publications team, the support team, all have content to display on the website. But each team has its own management, and its own schedule, and will likely have its own Git repository to store the content files. Those thoughts led to another concept:
A statically generated website will be rendered into an output directory. We treat that directory as a virtual path hierarchy, with different source directories in effect being mounted onto a position in this virtual hierarchy. The source directory for /news
, for example, does not need to be named news
and can have any name. The rendering system should arrange for the rendered files to land into the news
directory of the rendered output directory.
A related example is the front-end frameworks used on a website. For example, the Bootstrap framework in turn requires PopperJS, and you might want to use an icon library like Font Awesome. Each framework has its own distribution that needs to land in directories like /vendor/bootstrap
or /vendor/fontawesome
.
The last feature to consider is to watch the files, to automatically rebuild anything which changes, and to automatically update a web browser tab with the rebuilt content. Clearly such a feature needs to navigate the various directories involved, mapping files to the correct location while rendering, doing everything at lightning speed because modern humans are very impatient and want everything right now.
The AkashaCMS sub-project, Stacked Directories, aims to handle tracking files to support the needs just described. It was designed for use in AkashaCMS, but can be used by any other project. Heck, it might even be useful for applications other than static website generators.
Installing the Stacked Directories package
The Stacked Directories package is only published via the npm repository. Hence, installation into a Node.js project is:
$ npm install @akashacms/stacked-dirs --save
Feel free to transliterate this command to your preferred package manager, like Yarn.
Source code is at: https://github.com/akashacms/stacked-directories
Project documentation: https://akashacms.github.io/stacked-directories/
Configuring a directory/file watcher instance
Now that we have it installed in a project directory, let's see how to use the Stacked Directories module.
To begin with, in Node.js source code:
import { DirsWatcher } from '@akashacms/stacked-dirs';
...
const docsWatcher = new DirsWatcher('documents');
DirsWatcher
is a Class containing the the code, and behind the scenes it manages a Chokidar instance. You create as many instances of this class as needed by your application. For example, AkashaCMS uses four instances, one to track Document files, another to track Assets, another to track Page Layout Templates, and the last to track Partial Templates. The string passed to the constructor, in this case documents
, serves as the name of this DirsWatcher instance. The class doesn't do anything with the name, so you can use this in any way you like.
By itself the DirsWatcher instance does not do anything. That means it does not start watching any directories, until it is told which directories to watch, which we have not yet done. Once you've supplied a list of directories, it will begin scanning those directories, and emitting events. We'll go over the events in the next section.
The structure one must follow is:
import { DirsWatcher } from '@akashacms/stacked-dirs';
...
const docsWatcher = new DirsWatcher('documents');
// Configure event listeners -- see next section
docsWatcher.watch([
// list of directory descriptors
]);
The watch method is where we supply the list of directories to watch. In order to catch all events, it is necessary to set up the event listeners before calling watch.
Each directory descriptor is an object with some or all of these fields:
mounted
- The file system directory to referencemountPoint
- The location within the virtual file spaceignore
- One or more "glob" patterns indicating files to ignore
For example:
assetsWatcher.watch([
{
mounted: 'assets', mountPoint: '/',
ignore: [
'**/.DS_Store',
'**/.placeholder'
]
},
{ mounted: 'node_modules/bootstrap/dist', mountPoint: 'vendor/bootstrap' },
{ mounted: 'node_modules/jquery/dist', mountPoint: 'vendor/jquery' },
{ mounted: 'node_modules/popper.js/dist', mountPoint: 'vendor/popper.js' }
]);
This has four entries, and shows how each field is used.
- The mounted field is a location in the filesystem, and can either be relative or absolute.
- The mountPoint field is a location within the virtual file space
- A mountPoint value of
/
means this directory is mounted on the root of the virtual space. - The ignore field lists "glob" patterns for files to ignore. This type of pattern is used in many tools, for the precise format look at the Minimatch package for Node.js.
This example demonstrates mounting directories into a virtual space. But it doesn't explicitly demonstrate directory stacking. Consider what happens if these files existed:
Mounted | File | Virtual path |
---|---|---|
assets |
vendor/bootstrap/bootstrap.min.js |
vendor/bootstrap/bootstrap.min.js |
node_modules/bootstrap/dist |
bootstrap.min.js |
vendor/bootstrap/bootstrap.min.js |
This is two directories in our list containing files with the same virtual path. The virtual path is computed by concatenating the mountPoint value to the path within the directory. But, you might be wondering just what is a virtual path?
The virtual path is not a path in a source directory in the filesystem. Instead, it is the path within the rendered output directory. The rendered output directory is what will ultimately be deployed to the server. There is one aspect of the virtual path which is not handled in Stacked Directories, and this is the file name conversion which happens while rendering the file. For example, a Markdown file has the extension .md
but is rendered to a file with the .html
extension. But it was decided that was out-of-scope for Stacked Directories to know the conversion algorithm, because it does not render the files.
What Stacked Directories will do is to find all files with the same virtual path, and report on them together. The object, which we'll discuss later, shows the first matching file as the primary result, and elsewhere it lists all matching files.
The file in assets
, in this example, can be said to hide or overlay the file in node_modules/bootstrap/dist
.
The more explicit directory stacking demonstration would involve putting the following at the front of the above array:
{
mounted: 'assets-overlay', mountPoint: '/',
ignore: [
'**/.DS_Store',
'**/.placeholder'
]
}
In this case you have two directories on the same mount point. Because the assets-overlay
directory is in front of assets
, its files will hide any corresponding file in assets
.
Events emitted by DirsWatcher instances
Now that we've seen how to configure Stacked Directories, let's talk about the events emitted from DirsWatcher instances.
The DirsWatcher class is a subclass of EventEmitter. That means it has all the familiar methods for emitting events, and for applications to subscribe to those events.
The full recommended application structure is:
const watcher = new DirsWatcher('watcher-name');
watcher.on('change', (name, vpinfo) => { ... });
watcher.on('add', (name, vpinfo) => { ... });
watcher.on('unlink', (name, vpinfo) => { ... });
watcher.on('ready', (name) => { ... });
await watcher.watch([ ... ]);
In other words there are, currently, four events:
add
-- DirsWatcher has seen a newly added file in a watched directorychange
-- DirsWatcher has seen a change in an existing file in a watched directoryunlink
-- DirsWatcher has detected an existing file is no longer thereready
-- DirsWatcher has finished its initial scan of the watched directories
The initial scan happens when the DirsWatcher instance is first started. It scans the directories, and emits add
events for each file it finds. Once it has scanned all files, it emits a ready
event. Afterward it only emits events if a new file is added, an existing file is changed, or deleted.
The vpinfo
object contains a predigested data object describing the file stack for a given file. Here's an example:
{
fspath: 'documents-overlay/affiliate.html.md',
vpath: 'affiliate.html.md',
mime: 'text/markdown',
mounted: 'documents-overlay',
mountPoint: '/',
pathInMounted: 'affiliate.html.md',
stack: [
{
fspath: 'documents-overlay/affiliate.html.md',
vpath: 'affiliate.html.md',
mime: 'text/markdown',
mounted: 'documents-overlay',
mountPoint: '/',
pathInMounted: 'affiliate.html.md'
},
{
fspath: 'documents-example/affiliate.html.md',
vpath: 'affiliate.html.md',
mime: 'text/markdown',
mounted: 'documents-example',
mountPoint: '/',
pathInMounted: 'affiliate.html.md'
}
]
}
In this case the file affiliate.html.md
appears in two directories in the stack. The top-level elements describe the front-most entry of the stack. The stack
field contains description of every file in the stack.
The fields mean:
fspath
-- This is the pathname in the host filesystemvpath
-- The path within the virtual filesystemmime
-- The MIME type for the filemounted
-- The mounted field of the directory entry where this file was found.mountPoint
-- The virtual filesystem location where mounted appearspathInMounted
-- The filename relative to themounted
directorystack
-- The full list of files in all directories that have the samevpath
value
Earlier we said that DirsWatcher does not compute the rendered virtual path. That computation must be handled in the application. In AkashaCMS, the class which receives DirsWatcher events computes the rendered virtual path, adding it as the renderedPath
field.
SimpleCMS - A simple static website generator built in a couple hours using DirsWatcher
To finish this up, let's see how DirsWatcher would be used in an application. It is being used in AkashaRender, the core of AkashaCMS, but there's too much code there to make an effective example. Instead, in the Github repository you'll find SimpleCMS, which is an ultra-trimmed-down static website generator. This example was built in a couple hours and serves as a tool for ad-hoc testing of DirsWatcher.
In the repository you find a directory named test
, and another named example
. The test
directory has a normal unit test suite, which includes some directories of sample files. The example
directory contains simplecms
which is the implementation of SimpleCMS, as well as project
which is a partial project which we can render using SimpleCMS.
In example/simplecms/index.mjs
find the following code:
import { DirsWatcher } from '@akashacms/stacked-dirs';
// import { DirsWatcher } from '../../lib/watcher.mjs';
import path from 'path';
import { promises as fs } from 'fs';
import { render, renderedPath } from './render.mjs';
import yaml from 'js-yaml';
This imports the required packages and functions. You'll notice there are two lines for importing DirsWatcher. In the actual example code you'll find the second of those two, but in a regular application you'll find the first.
There is a module we won't discuss in this article, render.mjs
, that contains a simple rendering engine.
// Read the configuration from a YAML file
if (process.argv.length < 2 || !process.argv[2]) {
console.error('USAGE: node index.mjs config.yaml');
process.exit(1);
}
let ymltxt = await fs.readFile(process.argv[2], 'utf8');
let cfg = yaml.load(ymltxt);
let batchmode = cfg.batchmode;
const docsDirectories = cfg.dirs.documents;
export const renderedOutput = cfg.dirs.output;
export const layoutsDir = cfg.dirs.layout;
export const partialsDir = cfg.dirs.partial;
// Do initializations in the Render module
import { init } from './render.mjs';
init(layoutsDir, partialsDir);
////////////// END OF CONFIGURATION SECTION
This reads a YAML file, that must be named on the command line, which will contain configuration settings. This section ends by calling the init
function in render.mjs
. That module contains code for rendering files. Because this article is focusing on using DirsWatcher, we won't show what's in that file.
const docsWatcher = new DirsWatcher('documents');
docsWatcher.on('ready', async (name) => {
console.log(`documents ready ${name}`);
if (batchmode) await close();
})
.on('change', async (name, info) => {
console.log(`documents change ${name} ${info.vpath}`, info);
try {
await render(info);
} catch (err) {
console.error(`documents change ERROR `, err.stack);
}
})
.on('add', async (name, info) => {
console.log(`documents add ${name} ${info.vpath}`, info);
try {
await render(info);
} catch (err) {
console.error(`documents add ERROR `, err.stack);
}
})
.on('unlink', async (name, info) => {
console.log(`documents unlink ${name} ${info.vpath}`, info);
// TODO Convert the path into a path within renderedOutput
try {
await fs.unlink(path.join(renderedOutput, renderedPath(info.vpath)));
} catch (err) {
console.error(`documents unlink ERROR `, err.stack);
}
});
docsWatcher.watch(docsDirectories);
async function close() {
await docsWatcher.close();
}
This is where we configure and use the DirsWatcher instance.
SimpleCMS has a very simplified view of the world, as reflected by there being only one DirsWatcher instance. This instance is used to track document files. For page layout templates and other templates, those are single directories named in the layoutsDir
and partialsDir
variables.
The next thing to notice is the response to events. For both add and change events, it simply calls the render
function. However, for unlink events it deletes the corresponding file from the rendered output directory. Finally, for ready events it closes the DirsWatcher, if in batch mode, and the side effect of this is to cause the script to exit.
In a more fully featured CMS you might approach this differently. For example it's useful to save the data into an index, one purpose being to allow the rendering of one page reference data stored in another page. Also, instead of simply invoking the render
function, there should be a Worker Queue employed to ensure the amount of simultaneous work does not swamp the system.
The add and change events mean there is either a new content file, or a changed content file, which we must render into a file in the output directory.
In DirsWatcher, these events are not triggered for every addition or change. Instead, they're only triggered if the addition or change is for the front-most file in the directory stack. Consider the affiliate.html.md
example shown earlier. The file in documents-overlay
is the front-most file in the stack. There are four scenarios of interest:
- The file
documents-overlay/affiliate.html.md
changes, in which case the change event is triggered. - There is a directory in the stack with higher precedence than
documents-overlay
, in which a file namedaffiliate.html.md
is added. In this case the add event is triggered. - The file
documents-example/affiliate.html.md
is changed. In this case it is not the front-most file in the stack, and therefore no event is triggered. - Another directory is in the stack at lower precedence than
documents-overlay
, in which a file namedaffiliate.html.md
is added. In this case no event is triggered, because that new file is not the front-most file in the stack.
The render
function primarily is about converting a file (like a Markdown file) into a format useful in a website. It supports both Markdown files, rendering to HTML, optionally with a layout template. It also supports compiling LESS files to CSS. The render
function also supports simply copying a file into the rendered output directory, because not all file types require conversion.
The unlink event requires some deeper thought. Suppose your directory stack has two files for a given virtual path, like the affiliate.html.md
example shown earlier. There are three scenarios to consider:
- The file
documents-overlay/affiliate.html.md
is deleted. In this case the filedocuments-example/affiliate.html.md
is now the front-most file in the stack. You could say that this file has been unveiled or unhidden. The result is that a change event is triggered, showingdocuments-example/affiliate.html.md
as the primary file. - The file
documents-example/affiliate.html.md
is deleted. In this case it is not the front-most file in the stack, and no event is triggered. - If there is only one file in the stack, and that file is deleted. In this case there are no other files with the same virtual path, and therefore an unlink event is triggered.
We see in the unlink handler a function, renderedPath
, being called. This function takes a virtual path name (vpath
) and converts it to the pathname for that file in the output directory. For example a vpath
of epub/chap5/b/chap5b.html.md
must become epub/chap5/b/chap5b.html
in the output directory. Specifically, this function rewrites the basename portion of the path (the part after the last slash) using the following algorithm:
Extension | Result |
---|---|
path/to/file.html.md |
path/to/file.html |
path/to/file.md |
path/to/file.html |
path/to/file.css.less |
path/to/file.css |
path/to/file.less |
path/to/file.css |
The double-extension pattern (.html.md
) is because that's the file name pattern used in AkashaCMS. The idea was to document in the file name the format of the file, and the output format.
The last event to discuss is ready. As said earlier, this event is triggered when the initial file scan is completed. The result is that an add event has been sent for every file in every directory in the stack.
If the goal of your application is to constantly watch for files to rebuild, then your DirsWatcher instance should simply continue running. But, if the goal is to simply build the website then exit, the ready event is a good time in which to consider exiting the process. With SimpleCMS, the render function has been called for all files, and therefore all files have been rendered or copied into the output directory.
The implementation calls DirsWatcher.close
, the effect of which is to shut down the module that watches for file changes (Chokidar). The Node.js process will continue executing so long as there are active event listeners. What this means is that as soon as all calls to render
finish executing, there will be no more event listeners, and Node.js will exit.
Running a simple SimpleCMS example
In the Stacked Directories repository there are several directories of test files. In the example/simplecms
directory we have a configuration file listing those test directories as input directories. Namely:
# batchmode: false # For continuous execution
batchmode: true
dirs:
documents:
- mounted: '../project/documents-overlay'
mountPoint: '/'
- mounted: '../../test/documents-example'
mountPoint: '/'
- mounted: '../../test/documents-epub-skeleton'
mountPoint: 'epub'
output: ../project/out
layout: ../project/layouts
partial: ../../test/partials-base
This configuration object directly corresponds to the code shown earlier.
In example/simplecms/package.json
you find this scripts
section:
"scripts": {
"start": "node index.mjs"
},
With this, we can run SimpleCMS by running npm start
, but we have to do this to avoid it crashing:
$ npm start -- cfg.yaml
With npm, anything after the --
is appended to the executed command line. Therefore this is equivalent to running node index.mjs cfg.yaml
, with the advantage that package.json
remembers the command string for you.
With this configuration the document files come from the named directories. The documents-overlay
directory is meant to let us experiment with overlaying files to learn how this works. The documents-epub-skeleton
directory instead shows mounting into a subdirectory.
The rendering will be output to project/out
, and the project/layouts
directory contains layout templates. The various subdirectories of the test
directory contain files formatted for AkashaCMS projects, and they won't be directly useful with SimpleCMS.
Lastly, because batchmode
is set to true
, the command will read all the files, render them, then exit.
If you run this once, you'll see a tracing showing the Add events, followed by a call to renderMarkdown. This happens for every file, and then quickly it finishes and you're at the command-line. The newly created out
directory contains .html
files and it will be instructive to inspect them. It's useful to compare the template, and the content file, against the output file.
Study test/documents-example/markdown.html.md
, for example. At the top is this:
---
layout: default.html.ejs
title: Markdown example
tags: Markdown
---
This Frontmatter is a YAML-formatted block that's parsed using the Grey-Matter package. The layout
tag declares which layout template to use. The other lines end up as metadata that is supplied when the layout template is rendered. Currently SimpleCMS supports either EJS or Nunjucks templates.
The body of this file is a standard Markdown test document that exercises most of Markdown's features. You'll find in project/out/markdown.html
the resulting template, which you can compare against the default.html.ejs
template to see how it all fits together.
To switch to continuous rendering mode, make this change in cfg.yaml
:
batchmode: false # For continuous execution
# batchmode: true
This changes the response to the Ready event such that the DirsWatcher instance is not closed. That causes SimpleCMS to not exit, and to continue responding to DirsWatcher events as described above.
Let's start with the file example/project/documents-overlay/affiliate.html.md
. This overlays another file, test/documents-example/affiliate.html.md
.
Making any change to that causes this response:
documents change documents affiliate.html.md {
fspath: '../project/documents-overlay/affiliate.html.md',
vpath: 'affiliate.html.md',
mime: 'text/markdown',
mounted: '../project/documents-overlay',
mountPoint: '/',
pathInMounted: 'affiliate.html.md',
stack: [
{
fspath: '../project/documents-overlay/affiliate.html.md',
vpath: 'affiliate.html.md',
mime: 'text/markdown',
mounted: '../project/documents-overlay',
mountPoint: '/',
pathInMounted: 'affiliate.html.md'
},
{
fspath: '../../test/documents-example/affiliate.html.md',
vpath: 'affiliate.html.md',
mime: 'text/markdown',
mounted: '../../test/documents-example',
mountPoint: '/',
pathInMounted: 'affiliate.html.md'
}
]
}
renderedPath affiliate.html.md ==> affiliate.html
renderMarkdown affiliate.html.md ==> ../project/out/affiliate.html
We see a Change event, with the expected vpinfo object, describing the two files in the stack. Then it computes the renderedPath, and finally renders the Markdown.
Changing its name to example/project/documents-overlay/affiliate-foo.html.md
results in these events:
documents change documents affiliate.html.md {
fspath: '../../test/documents-example/affiliate.html.md',
vpath: 'affiliate.html.md',
mime: 'text/markdown',
mounted: '../../test/documents-example',
mountPoint: '/',
pathInMounted: 'affiliate.html.md',
stack: [
{
fspath: '../../test/documents-example/affiliate.html.md',
vpath: 'affiliate.html.md',
mime: 'text/markdown',
mounted: '../../test/documents-example',
mountPoint: '/',
pathInMounted: 'affiliate.html.md'
}
]
}
renderedPath affiliate.html.md ==> affiliate.html
renderMarkdown affiliate.html.md ==> ../project/out/affiliate.html
documents add documents affiliate-foo.html.md {
fspath: '../project/documents-overlay/affiliate-foo.html.md',
vpath: 'affiliate-foo.html.md',
mime: 'text/markdown',
mounted: '../project/documents-overlay',
mountPoint: '/',
pathInMounted: 'affiliate-foo.html.md',
stack: [
{
fspath: '../project/documents-overlay/affiliate-foo.html.md',
vpath: 'affiliate-foo.html.md',
mime: 'text/markdown',
mounted: '../project/documents-overlay',
mountPoint: '/',
pathInMounted: 'affiliate-foo.html.md'
}
]
}
renderedPath affiliate-foo.html.md ==> affiliate-foo.html
renderMarkdown affiliate-foo.html.md ==> ../project/out/affiliate-foo.html
We first get a Change event, followed by an Add event. The Change event describes the front-most file for affiliate.html.md
as test/documents-example/affiliate.html.md
. In other words, that file is no longer hidden by the file in documents-overlay
.
The Add event is because the file that had been documents-overlay/affiliate.html.md
is now named documents-overlay/affiliate-foo.html.md
. In other words, DirsWatcher saw that as a newly added files.
To SimpleCMS this looked like documents-overlay/affiliate.html.md
had been deleted, revealing test/documents-example/affiliate.html.md
, and then documents-overlay/affiliate-foo.html.md
was added.
We've demonstrated part of the events described earlier, so what happens if we simply add a new file?
documents add documents new-file.md {
fspath: '../project/documents-overlay/new-file.md',
vpath: 'new-file.md',
mime: 'text/markdown',
mounted: '../project/documents-overlay',
mountPoint: '/',
pathInMounted: 'new-file.md',
stack: [
{
fspath: '../project/documents-overlay/new-file.md',
vpath: 'new-file.md',
mime: 'text/markdown',
mounted: '../project/documents-overlay',
mountPoint: '/',
pathInMounted: 'new-file.md'
}
]
}
renderedPath new-file.md ==> new-file.html
renderMarkdown new-file.md ==> ../project/out/new-file.html
This is a newly created file, and you can inspect the results in the project/out
directory. As expected, an Add event was triggered, followed by the renderMarkdown
call to render the file.
Then, if we simply delete this file:
documents unlink documents new-file.md {
fspath: '../project/documents-overlay/new-file.md',
vpath: 'new-file.md',
mime: 'text/markdown',
mounted: '../project/documents-overlay',
mountPoint: '/',
pathInMounted: 'new-file.md'
}
renderedPath new-file.md ==> new-file.html
An Unlink event is triggered, and notice how the vpinfo object does not have a stack
field. It was unable to find a matching file anywhere in the directory stack, because the file was deleted. The result of this is to compute the filename of the rendered file, and to call fs.unlink
to remove the rendered file. And, indeed, project/out/new-file.html
will be gone.
Summary
The DirsWatcher class can easily be the basis for a custom static website generator application. The SimpleCMS example is not that far from being a useful system.
DirsWatcher is ready to go and appears fairly stable. It is the result of several years of developing code for AkashaCMS, and it has been tested deeply using AkashaCMS modules.
comments powered by Disqus