The way of the Metalsmith
As stated in the previous post, Metalsmith is a wonderful tool for anyone wanting to build a static site with the node platform.
In this post, we’ll learn a bit more about how Metalsmith processes the source directory. I assume that you already have a working installation. You can find how to do it by reading its official web site.
Setup
- Let’s create a directory for our tests (the name does not matter)
- Create a subdirectory named
src
. This is the default name of the source directory for Metalsmith. - Put a bunch of directories and files into it. Be creative here !
- Create a script named
index.js
(or whatever name you like) at the same level then the source directory. For now leave it empty.
Before running any command, let’s try to have a grasp of what Metalsmith will do when we run it.
Metalsmith will build in memory a key-value map named files
which will have one entry by file found under the source directory. Each entry is built like this:
- the key is the relative path of the file (relative to the source directory)
- the value is an object containing all the data associated with this file (including the file content)
Then every plugin will be called in turn with this map (among other arguments) and have a chance to modify it. Ultimately, the content of the map is written to the build directory.
As you can see, Metalsmith does little by itself, that’s why its motto is:
‘An extremely simple, pluggable static site generator.’
Let’s try it now ! Please put the following in the script:
Metalsmith(__dirname)
.build()
__dirname
is a node.js variable set to the directory where the script resides. You can then execute the script by doing node index.js
.
Nice we just achieved to copy the source directory into the destination directory… There are simpler tools to do that, isn’t it ?
Creating and using a simple logging plugin
To have a better understanding of what’s going on, it would be nice to see the content of the files
map at any moment. Let’s build a function that will log it to the console, and plug that function into our (for now empty) chain of plugins:
var logFilesMap = function(files, metalsmith, done) {
Object.keys(files).forEach(function (file) {
var fileObject = files[file];
console.log("key -------> ", file);
console.log("value -----> ", fileObject);
});
};
Metalsmith(__dirname)
.use(logFilesMap)
.build()
Each plugin is inserted in the plugin chain by invoking the use
method of Metalsmith. And each plugin is invoked by Metalsmith with 3 parameters:
files
: the in-memory map of the files found under the source directorymetalsmith
: the instance of metalsmith that is running the pluginsdone
: the plugin has to call this function when his work is… done. This allows Metalsmith to call the next plugin. The plugin may also use it to indicate that an error occured (more on that topic later)
Running the previous code shows what are the built-in attributes found in fileObject
that Metalsmith associates to each and every file:
contents
: buffer receiving the content of the file. Yes, all the contents of all the files are in memory. But you’re not the NSA, so you may not have too much data to remember, no ?mode
: octal representation of the file’s permissions in the file system.
We’re not gonna make it far with so few informations, and not every data can be computed with a plugin. So it would be nice to have a way to add data while writing our files… Good news, it’s possible ! To achieve this goal, Metalsmith uses front-matter which lets us add YAML metadata to a file if we want to.
Let’s try it by adding YAML metadata to (at least) one of the files, then run the script again. You should see the metadata in the corresponding value.
Managing errors
But what happens if a plugin can’t do its work properly because, for example, it lacks the correct configuration or the YAML metadata expected ? It probably shouldn’t hand over to the next plugin, because there are chances that the remaining plugins are relying on the work of previous plugins.
Hopefully, there is a way to tell Metalsmith that the work is over: just pass an error to the done()
callback. To see it in action, let’s add to our script a plugin that always fails, and add it before the logging plugin:
...
var mojoLost = function(files, metalsmith, done) {
done(new Error('I lost my mojo!'));
}
Metalsmith(__dirname)
.use(mojoLost)
.use(logFilesMap)
.build()
Running that, you shouldn’t see any logging information. Actually, you shouldn’t see anything… Wait, does it mean that if a plugin fails, there is no obvious way to be notified ?
The final callback
Obviously no, because no matter if all went smoothly or if a plugin failed, Metalsmith gives us the opportunity to set a callback that will end the workflow. It’s just that we didn’t set it for the moment, because we didn’t pass any argument to the build()
method.
Let’s fix it now by modifying our script this way:
...
.build(function(err) {
if (err) {
console.log(err);
}
else {
console.log('Here is you new sword, my Lord');
}
})
As you can see, this callback is passed an error argument in the case something went wrong. If you execute the script again, you should now see an error message complaining that the mojo is lost. And if you remove the use of the mojoLost
plugin, you’ll see a message informing you that your new sword is ready. Good fight !
With this final piece in place, we have a much better understanding of the whole puzzle. Here is a diagram resuming how metalsmith works:
Conclusion
In this post, we learned:
- what’s the metalsmith way of processing files
- how the plugins can manage errors that prevent them to do their work properly
- how you can be notified of the end of the metalmith process
In the next post, we’ll write a more complex metalsmith plugin.