--- templateEngineOverride: false ---
Dr. Greg Bernstein
Updated October 4th, 2020
Down load and unzip the example files. Put the example repo in the proper state of the last commit to branch basic
with the commands:
git reset --hard
git checkout basic
Full instructions in Using the Static Gen Repo
A static web page is a web page that is delivered to the user exactly as stored, in contrast to dynamic web pages which are generated by a web application.
Read: 6 Reasons Why You Should Go for a Static Website
“An extremely simple, pluggable static site generator.”
package.json
file{"name": "my-first-metal-site", "version": "0.0.1"}
npm install --save metalsmith
From Metalsmith:
The task of a static site generator is to produce static build files that can be deployed to a web server. These files are built from source files.
From Metalsmith basic processing flow:
From Metalsmith architecture/plugins:
Metalsmith is built on this reasoning. It takes the information from the source files from a source directory and it writes the manipulated information to files into a destination directory. All manipulations, however, it exclusively leaves to plugins.
From Metalsmith architecture/plugins:
Manipulations can be anything: translating templates, transpiling code, replacing variables, wrapping layouts around content, grouping files, moving files and so on. This is why we say »Everything is a Plugin«. And of course, several manipulations can be applied one after another. Obviously, in this case the sequence matters.
Example repo branch: basic
├── README.md
├── build.js
├── package-lock.json
├── package.json
└── src
├── MyTest.txt
├── anotherTest.txt
├── strange.dat1
├── test1.txt
└── weird.dat1
Build.js
Filevar Metalsmith = require('metalsmith');
Metalsmith(__dirname) // __dirname defined by node.js:
.source('./src') // source directory
.destination('./build') // destination directory
.clean(true) // clean destination before
.build(function(err) { // build process
if (err) throw err; // error handling is required
});
node build.js
├── README.md
├── build
│ ├── MyTest.txt
│ ├── anotherTest.txt
│ ├── strange.dat1
│ ├── test1.txt
│ └── weird.dat1
├── build.js
├── package-lock.json
├── package.json
└── src
├── MyTest.txt
├── anotherTest.txt
├── strange.dat1
├── test1.txt
└── weird.dat1
src
directory into the build
directoryWe can ignore files:
var Metalsmith = require('metalsmith');
Metalsmith(__dirname) // __dirname defined by node.js:
.source('./src') // source directory
.destination('./build') // destination directory
.clean(true) // clean destination before
.ignore("*.dat1") // ignore this file type
.build(function(err) { // build process
if (err) throw err; // error handling is required
});
node build.js
├── README.md
├── build
│ ├── MyTest.txt
│ ├── anotherTest.txt
│ ├── test1.txt
├── build.js
├── package-lock.json
├── package.json
└── src
├── MyTest.txt
├── anotherTest.txt
├── strange.dat1
├── test1.txt
└── weird.dat1
From branch: basicPlug. What kind of code is this?
console.log("Starting Processing!");
Metalsmith(__dirname) // __dirname defined by node.js:
.source('./src') // source directory
.destination('./build') // destination directory
.clean(true) // clean destination before
.ignore("*.dat1") // Use to ignore files and directories
.build(function(err, files) { // build process
if (err) {
throw err; // error handling is required
} else {
console.log(`Finished Processing: ${Object.keys(files)}`);
}});
Some examples with standard JS built-in objects:
var myString = "Website Development. Code the Web!"
myString.toLowerCase().includes("web")
var myDate = new Date()
myDate.toISOString().toLowerCase().split("t")
To provide for chaining return a reference to the object, below we use this
to help us:
myThing = {
count: 0,
speak(x) {
console.log(`Hi ${x},`);
this.count++;
return this;
},
yell(x) {
console.log(`HELLO ${x.toUpperCase()}!`)
this.count++;
return this;
}
}
// Try chaining...
myThing.speak("web dev").yell("web dev");
const Metalsmith = require('metalsmith');
var ms = Metalsmith(__dirname);
// Set up Options:
ms.metadata({ // add any variable you want
hello: "World", // use them in layout, other plugins
myCourse: "Website Development",
});
ms.source('./src'); // source directory
ms.destination('./build'); // destination directory
ms.clean(true); // clean destination before
ms.ignore("*.dat1"); // Use to ignore files and directories
// Run it!
ms.build(function(err, files) {
if (err) {
throw err;
} else {
console.log(`Finished Processing: ${Object.keys(files)}`);
}});
myPluginHello.js
: A plugin that really doesn’t do anything
function plugin() {
return function(files, metalsmith, done){
setImmediate(done); // For asynchronous operation, schedules done callback
console.log("Hello class from myPluginHello.js!");
};
}
module.exports = plugin; // How we "export" from a module in Node.js
See the Metalsmith use method below
const Metalsmith = require('metalsmith');
// Here is how we "import" from another file:
const plugHello = require('./myPluginHello.js');
Metalsmith(__dirname)
.source('./src') // source directory
.destination('./build') // destination directory
.use(plugHello()) // Use our Hello plugin note the ()!
.build(function(err, files) { // build process
if (err) {
throw err; // error handling is required
} else {
console.log(`Finished Processing: ${Object.keys(files)}`);
}});
Our plugin function is given files
, metalsmith
, and done
objects. Let’s look at these with a plugin!
// myPlugin.js
function infoPlugin() {
return function(files, metalsmith, done){
setImmediate(done); // For asynchronous operation, schedules done callback
console.log(JSON.stringify(files, replacer4Buffer, 2));
Object.keys(files).forEach(function(file){
let data = files[file];
// This is where you would really do your
// file processing. The contents is a Node.js *Buffer*.
//console.log(data.contents.toString());
});
};
}
// Helps JSON.stringify deal with Node.js *Buffer*
function replacer4Buffer(key, value) {
if (key === 'contents') {
return this[key].toString();
} else {
return value;
}
}
module.exports = infoPlugin; // How we "export" from a module in Node.js
The plugins are run in the order they are called from use
const Metalsmith = require('metalsmith');
const pluginInfo = require('./myPlugin.js');
const plugHello = require('./myPluginHello.js');
console.log("Starting Processing!");
Metalsmith(__dirname) // __dirname defined by node.js:
.source('./src') // source directory
.destination('./build') // destination directory
.use(plugHello()) // Use our Hello plugin
.use(pluginInfo()) // Then our Info plugin
.build(function(err, files) { // build process
if (err) {
throw err; // error handling is required
} else {
console.log(`Finished Processing: ${Object.keys(files)}`);
}});
All the files in src
get put in the files
JavaScript object indexed by their file name. This includes their content!
metalsmith
Of biggest interest is the metadata
function goodbyePlugin() {
return function(files, metalsmith, done){
setImmediate(done);
console.log("Hello class from myPluginGoodbye.js!");
// Uncomment below to see everything in metalsmith var
//console.log(JSON.stringify(metalsmith, null, 2));
console.log("Look at the global metadata object:")
console.log(metalsmith.metadata());
};
}
module.exports = goodbyePlugin;
Use the metadata
function to set any global metadata
const Metalsmith = require('metalsmith');
const pluginInfo = require('./myPlugin.js');
const plugHello = require('./myPluginHello.js');
const plugGoodbye = require('./myPluginGoodbye.js');
console.log("Starting Processing!");
Metalsmith(__dirname) // __dirname defined by node.js:
.metadata({ // add any variable you want
hello: "World", // use them in layout, other plugins
myCourse: "Website Development",
})
.source('./src') // source directory
.destination('./build') // destination directory
.clean(true) // clean destination before
.ignore("*.dat1") // Use to ignore files and directories
.use(plugHello()) // Use our Hello plugin
.use(plugGoodbye())
.use(pluginInfo())
.build(function(err, files) { // build process
if (err) {
throw err; // error handling is required
} else {
console.log(`Finished Processing: ${Object.keys(files)}`);
}});
Global metadata available to all plugins
Go to NPM and search:
metalsmith-markdown
Example repo branch: markdown
npm install --save metalsmith-markdown
src
directorybuild.js
file to include Markdown processingbuild.js
Fileconst Metalsmith = require('metalsmith');
const markdown = require('metalsmith-markdown');
const infoPlugin = require('./myPlugin.js'); // Only for understanding
Metalsmith(__dirname)
.source('./src')
.destination('./build')
.clean(true)
.ignore("*.dat1")
.use(infoPlugin()) // Look at files before processing
.use(markdown())
.use(infoPlugin()) // Look at files after processing
.build(function(err, files) {
if (err) throw err;
else {
console.log("Output files:");
console.log(Object.keys(files));
}
});
node build.js
result:
├── README.md
├── build
│ ├── MeMe.html
│ ├── MyRestaurant.html
│ ├── MyTest.txt
│ ├── anotherTest.txt
│ └── test1.txt
├── build.js
├── package-lock.json
├── package.json
└── src
├── MeMe.md
├── MyRestaurant.md
├── MyTest.txt
├── anotherTest.txt
├── strange.dat1
├── test1.txt
└── weird.dat1
Snooping with the infoPlugin
:
<body>
, <html>
, or <head>
elements.Example repo branch: layout
We need metalsmith-layouts
and jstransformer-nunjucks
npm install --save metalsmith-layouts
npm install --save jstransformer-nunjucks
├── README.md
├── build
│ ├── MeMe.html
│ ├── MyRestaurant.html
│ ├── MyTest.txt
│ ├── anotherTest.txt
│ └── test1.txt
├── build.js
├── layouts
│ └── base.njk
├── node_modules (not shown!!!)
├── package-lock.json
├── package.json
└── src
├── MeMe.md
├── MyRestaurant.md
├── MyTest.txt
├── anotherTest.txt
├── strange.dat1
├── test1.txt
└── weird.dat1
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>All the same</title>
</head>
<body>
<main>
{{contents | safe}} <!-- A Nunjucks filter that stops HTML escaping -->
</main>
</body>
</html>
build.js
var Metalsmith = require('metalsmith');
var markdown = require('metalsmith-markdown');
var layouts = require('metalsmith-layouts');
Metalsmith(__dirname) // __dirname defined by node.js:
// name of current working directory
.metadata({ // add any variable you want
// use them in layout, other plugins
author: "Dr. B",
myClass: "Web Systems",
})
.source('./src') // source directory
.destination('./build') // destination directory
.clean(true) // clean destination before
.ignore("*.dat1") // Use to ignore files and directories
.use(markdown())
.use(layouts({
default: "base.njk",
directory: "layouts"
}))
.build(function(err) { // build process
if (err) throw err; // error handling is required
});
metadata
call with values available to all pluginsMeMe.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>All the same</title>
</head>
<body>
<main>
<h1 id="all-about-me">All About Me</h1>
<p>This is a test file for <em>Markdown</em> processing.</p>
<p>Bullet list:</p>
<ul>
<li>Why would anyone follow someone on twitter?</li>
<li>How can anyone say anything of importance in so little space?</li>
<li>Repeating something many times <em>doesn't</em> make it true.</li>
</ul>
<p><strong>Emphasis</strong></p>
<ol>
<li>Schools need to teach practical "critical thinking"</li>
<li>Otherwise students will learn about con-men, salesmen, and politicians the <strong><em>hard way</em></strong>.</li>
<li>Do you really think that most financial institutions want you to be <em>financially literate</em>?</li>
<li>Avoid <strong>most</strong> debt.</li>
</ol>
<!-- A Nunjucks filter that stops HTML escaping -->
</main>
</body>
</html>
description
meta data for the pageYAML (YAML Ain’t Markup Language) is a human-readable data serialization language. It is commonly used for configuration files,
Example repo branch: yml
---
title: The Family Restaurant
description: The family restaurant was Bernstein's Fish Grotto. I worked there and was motivated to never go into the restaurant business!
---
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{{title}}</title>
<meta name="author" content="{{author}}" >
<meta name="description" content="{{description}}" >
</head>
<body>
<main>
{{contents | safe}} <!-- A Nunjucks filter that stops HTML escaping -->
</main>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>All About Me</title>
<meta name="author" content="Dr. B" >
<meta name="description" content="A rant to test out Markdown processing." >
</head>
<body>
<main>
<h1 id="all-about-me">All About Me</h1>
<p>This is a test file for <em>Markdown</em> processing.</p>
<p>Bullet list:</p>
<ul>
<li>Why would anyone follow someone on twitter?</li>
<li>How can anyone say anything of importance in so little space?</li>
<li>Repeating something many times <em>doesn't</em> make it true.</li>
</ul>
<p><strong>Emphasis</strong></p>
<ol>
<li>Schools need to teach practical "critical thinking"</li>
<li>Otherwise students will learn about con-men, salesmen, and politicians the <strong><em>hard way</em></strong>.</li>
<li>Do you really think that most financial institutions want you to be <em>financially literate</em>?</li>
<li>Avoid <strong>most</strong> debt.</li>
</ol>
<!-- A Nunjucks filter that stops HTML escaping -->
</main>
</body>
</html>
Metalsmith uses the gray-matter library
src
directory I added the HomeWork2.html
and hw2.css
files.node build.js
we get the following:├── build
│ ├── HomeWork2.html
│ ├── MeMe.html
│ ├── MyRestaurant.html
│ ├── MyTest.txt
│ ├── anotherTest.txt
│ ├── hw2.css
│ └── test1.txt
contents of hw2.css:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title></title>
<meta name="author" content="Dr. B" >
<meta name="description" content="" >
</head>
<body>
<main>
body {background-color:#92d0e0;}
main {background-color: white;
max-width: 800px;
margin-left: auto;
margin-right: auto;
margin-top: 2em;
margin-bottom: 2em;
padding: 1em;
border: solid #00000080 7px;}
header {text-align: center;
font-family: sans-serif;}
ol {padding-left: 1em}
li {padding-left: 0.5em}
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
<!-- A Nunjucks filter that stops HTML escaping -->
</main>
</body>
</html>
contents of MyTest.txt
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title></title>
<meta name="author" content="Dr. B" >
<meta name="description" content="" >
</head>
<body>
<main>
This is regular text.
I'm putting some *markdown* in here to see if it gets
**processed**. <!-- A Nunjucks filter that stops HTML escaping -->
</main>
</body>
</html>
*.md
fileslayouts
pattern: ["*", "!*.txt", "!*.css", "!HomeWork2.html"]
to stop processing of the text, css and our custom HTML file.contents
that holds the contents of the file.From Metalsmith home page my-file.md
:
---
title: A Catchy Title
draft: false
---
An unfinished article...
The corresponding JS Object from the Metalsmith home page:
{
'relative_to_sourcepath/my-file.md': {
title: 'A Catchy Title',
draft: false,
contents: 'An unfinished article...',
mode: '0664',
stats: {
/* keys with information on the file */
}
}
}
The JS files
object for all files from the Metalsmith home page:
files = {
"relative_to_sourcepath/file1.md": {
title: 'A Catchy Title',
draft: false,
contents: 'An unfinished article...',
mode: '0664',
stats: {
/* keys with information on the file */
}
},
"relative_to_sourcepath/file2.md": {
title: 'An Even Better Title',
draft: false,
contents: 'One more unfinished article...',
mode: '0664',
stats: {
/* keys with information on the file */
}
}
}
From Metalsmith home page:
/**
* Metalsmith plugin to hide drafts from the output.
*/
function plugin() {
return function(files, metalsmith, done){
setImmediate(done);
Object.keys(files).forEach(function(file){
var data = files[file];
if (data.draft) delete files[file];
});
};
}
I have used these
Interesting looking
Example repo branch: nav
[{"title": "Markdown Test", "url": "MeMe.html"},
{"title": "My Restaurant", "url": "MyRestaurant.html"},
{"title": "A Homework Set", "url": "HomeWork2.html"}
]
build.js
var Metalsmith = require('metalsmith');
var markdown = require('metalsmith-markdown');
var layouts = require('metalsmith-layouts');
var nav = require("./nav.json");
Metalsmith(__dirname) // __dirname defined by node.js:
// name of current working directory
.metadata({ // add any variable you want
// use them in layout, other plugins
author: "Dr. B",
links: nav, // Add navigation information
})
.source('./src') // source directory
.destination('./build') // destination directory
.clean(true) // clean destination before
.ignore("*.dat1") // Use to ignore files and directories
.use(markdown())
.use(layouts({
default: "base.njk",
directory: "layouts",
pattern: ["*", "!*.txt", "!*.css", "!HomeWork2.html"]
}))
.build(function(err) { // build process
if (err) throw err; // error handling is required
});
base.njk
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{{title}}</title>
<meta name="author" content="{{author}}" >
<meta name="description" content="{{description}}" >
<link href="simple.css" rel="stylesheet">
</head>
<body>
<main>
{% include "nav.njk" %} <!-- This file builds the navigation-->
{{contents | safe}} <!-- A Nunjucks filter that stops HTML escaping -->
</main>
</body>
</html>
nav.njk
file<nav>
<ul>
{% for item in links %}
<li><a href={{item.url}}>{{item.title}}</a></li>
{% endfor %}
</ul>
</nav>
simple.css
node build.js
build
directory