Write You a Static Site Generator
With NodeJS ⚡️
Adam Kelly
TY Student at Skerries Community College
@adamisntdead
tinyurl.com/node-static
Have A Question? 🤔
What is a Static Site Generator?
Why Would You Use a Static Site Generator?
- Simplicity
- Security
- Performance
- Flexibility
Writing a Static Site Generator ✏️
Why Would You Write Your Own Static Site Generator?
- You choose your stack
- Speed
- An in-depth knowledge of the internals
- Easier to debug and solve problems
- Reduce reliance on external/upstream projects
Why Would You Use NodeJS
- Ecosystem
- Fast
- Async
- Isomorphism
Two Big Questions
What Does Your Content Look Like
How Are You Going To Edit Your Content
Smaller Choices
Picking Your Tools
Configuration
JSON
YAML
Content / Layouts
Markdown
Handlebars
Pug
Generic Tools
fs-extra
fast-glob
gray-matter
4 Main Parts Of A Static Site Generator
- Cleaning
- Reading
- Rendering
- Writing
The Code
My Choices
- JSON for configuration
- Markdown for content
- YAML for frontmatter
- Handlebars for layouts
Configuration
{
"title": "Your Awesome title",
"description": "Write an awesome description here!",
"author": "Author Name",
"static": "assets",
"public": "public"
}
const fs = require('fs-extra')
async function getConfig() {
// Reading the 'config.json' file,
// and parsing the JSON
return await fs.readJSON('config.json')
}
The Main Generator
class Site {
constructor(config) {
this.config = config
this.pages = []
this.output = []
}
// Main Method - Builds the site
build() {
...
}
...
}
getConfig()
.then(config => new Site(config))
.then(site => site.build())
.catch(err => console.log(err))
The 4 Main Stages
class Site {
...
async build() {
await this.reset()
await this.read()
await this.render()
await this.write()
}
async reset() {
...
}
async read() {
...
}
async render() {
...
}
async write() {
...
}
}
async reset() {
// Delete the public / output directory
await fs.remove(this.config.public)
// Create a new, empty public directory
await fs.mkdirp(this.config.public)
}
const matter = require('gray-matter')
const slug = require('slug')
const glob = require('fast-glob')
...
async read() {
// Read the files in the content directory
const files = await glob('**/*', { cwd: 'content' })
for (const filename of files) {
const fileContent = await fs.readFile(`content/${filename}`, 'utf8')
// Getting the frontmatter
const { content, data } = matter(fileContent)
const title = data.title
const permalink = slug(title).toLowerCase()
// Add the page to the 'this.pages' object
this.pages.push({
title,
permalink,
content,
...frontmatter.data
})
}
}
async render() {
const site = { pages: this.pages, ...this.config }
for (const page in this.pages) {
let content = Handlebars.compile(page.content)({ site, page })
if (page.markdown) {
content = marked(renderedContent)
}
if (page.layout) {
const layout = await fs.readFile(`layouts/${page.layout}.hbs`, 'utf8')
const template = Handlebars.compile(layout)
content = template({ site, page, content })
}
this.output.push({
path: path.join(this.config.public, page.permalink, 'index.html'),
content
})
}
}
async write() {
const outputs = []
for (const output in this.output) {
outputs.push(fs.outputFile(output.path, output.content))
}
return Promise.all(outputs)
}
Thats It!
Extending Your Generator
- Asset Pipelines
- CLI
- Watching For Changes (Incremental Builds)
- Plugins
J
Javascript
APIs
A
Markup
M
Best Practices
What Makes The JAM Stack Special
Best Practices
- Everything On A CDN
- Atomic Deploys
- Instant Cache Invalidation
- Version Control Everything
- Automated Builds
Node And The JAM Stack
JAM Tools
E-Commerce
Gumroad
Shopify Buy Button
Stripe
GoCommerce
Forms
Formspree
Typeform
Netlify Forms
Search
Algolia
Google Custom Search
Front End Javascript Search
Login
Auth0
Netlify Identity
Comments
Disqus
Facebook Comments
Other Options
CMS
Contentful
Prismic
Storyblok
Dropbox
Netlify CMS
When Would I Not JAM?
Thank You!
@adamisntdead
tinyurl.com/node-static
Write You A Static Site Generator
By Adam Kelly
Write You A Static Site Generator
- 819