Last active
September 22, 2017 20:00
-
-
Save stevenkaspar/9702bee7d1530e628ac383edf47da753 to your computer and use it in GitHub Desktop.
Gulp Static Site Generator - writeup at https://stevenkaspar.github.io/posts/gulp-based-static-site-generator.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
const gulp = require('gulp'); | |
const shell = require('gulp-shell') | |
const spawn = require('child_process').spawn; | |
const fs = require('fs'); | |
const mkdirp = require('mkdirp'); | |
const pug = require('pug'); | |
const marked = require('marked'); | |
const path = require('path'); | |
const site_base = `${__dirname}/_site`; | |
const templates_base = `${site_base}/_templates`; | |
const required_config = [ | |
{ | |
key: 'slug', | |
type: 'string' | |
}, | |
{ | |
key: 'title', | |
type: 'string' | |
}, | |
{ | |
key: 'template', | |
type: 'string' | |
} | |
]; | |
const walk_filters = [ | |
'_templates', | |
'private' | |
]; | |
const base_locals = { | |
header_links: { | |
'Home': __dirname + '/index.html', | |
'About': __dirname + '/about.html' | |
}, | |
stylesheet: __dirname + '/dist/app.css' | |
} | |
let validateConfig = config => { | |
for(let required_key of required_config){ | |
if(!config[required_key.key] || typeof config[required_key.key] !== required_key.type){ | |
throw new Error(`Config Error => ${required_key.key} is required and needs to be type ${required_key.type}`); | |
} | |
} | |
} | |
// parses a file with its expected config | |
let parseFile = file_path => { | |
// read contents | |
const contents = fs.readFileSync(file_path, 'utf8'); | |
// parse contents | |
const match = /^---{(.|\n)+^}---$/mg.exec(contents); | |
if(match === null){ | |
throw new Error(`Post config not found for ${file_path}`); | |
} | |
try { | |
// get config that should be in valid JSON format | |
const config = JSON.parse(match[0].substring(3, match[0].length - 3)); | |
validateConfig(config); | |
// everything after the JSON config is the content of the file | |
const content = contents.substring(match[0].length); | |
return { | |
config: config, | |
content: { | |
md: content, | |
html: marked(content) | |
} | |
}; | |
} | |
catch(e){ | |
console.warn(`${file_path} => ${e.message}`); | |
console.warn('This is most likely an error in your JSON config at the top of your file'); | |
console.warn(match[0]); | |
} | |
return null; | |
} | |
let createDestStructure = (root, structure, locals) => { | |
for(let dir_name in structure.children){ | |
const dir_path = `${root}/${dir_name}`; | |
// create the directory | |
mkdirp.sync(dir_path); | |
createDestStructure(`${dir_path}`, structure.children[dir_name], locals); | |
} | |
// for each file we need to write the file in its final HTML form | |
for(let file of structure.files){ | |
const fn = pug.compileFile(`${templates_base}/${file.config.template}`, { | |
pretty: true | |
}); | |
// compile all pages with the same locals so that we can use pug extends properly | |
const compiled_page = fn(Object.assign({}, file, locals)); | |
// finally write the file in it's fully compiled HTML format | |
fs.writeFileSync(`${root}/${file.config.slug}.html`, compiled_page, 'utf8'); | |
} | |
} | |
let createPugDestStructure = (root, structure, locals) => { | |
for(let dir_name in structure.children){ | |
const dir_path = `${root}/${dir_name}`; | |
// create the directory | |
mkdirp.sync(dir_path); | |
createPugDestStructure(`${dir_path}`, structure.children[dir_name], locals); | |
} | |
// for each file we need to write the file in its final HTML form | |
for(let file_path of structure.files){ | |
const fn = pug.compileFile(file_path, { | |
pretty: true | |
}); | |
const compiled_page = fn(locals); | |
fs.writeFileSync(`${root}/${path.basename(file_path, '.pug')}.html`, compiled_page, 'utf8'); | |
} | |
} | |
let getStructBit = () => { | |
return { | |
children: {}, | |
files: [] | |
} | |
} | |
gulp.task('build-site-pages', done => { | |
const walk = require('walk'); | |
const source = `${site_base}`; | |
let md_structure = getStructBit(); | |
let pug_structure = getStructBit(); | |
/** | |
* Defines the npm walk options | |
* | |
* We are only really concerned about the file listener because it will handle | |
* creating the directory | |
*/ | |
const walk_options = { | |
filters: walk_filters, | |
listeners: { | |
file: function (root, fileStats, next) { | |
/** | |
* The md process and the pug process are basically the same and should | |
* probably be consolidated somewhat | |
*/ | |
if(fileStats.name.split('.').slice(-1)[0] === 'md'){ | |
const project_path = root.replace(source, '').split('/').filter(p => p.length); | |
let current_level = md_structure; | |
if(project_path.length === 0){ | |
const parsed_file = parseFile(`${root}/${fileStats.name}`); | |
if(parsed_file !== null){ | |
current_level.files.push(parsed_file); | |
} | |
} | |
else { | |
let i = 0; | |
while(i < project_path.length){ | |
if(!current_level.children[project_path[i]]){ | |
current_level.children[project_path[i]] = getStructBit(); | |
} | |
if(i === project_path.length - 1) { | |
const parsed_file = parseFile(`${root}/${fileStats.name}`); | |
if(parsed_file !== null){ | |
current_level.children[project_path[i]].files.push(parsed_file); | |
} | |
} | |
else { | |
current_level = current_level.children[project_path[i]]; | |
} | |
i++; | |
} | |
} | |
next(); | |
} | |
/** | |
* pug files are not parsed but we only get the path because we will | |
* compile them later | |
*/ | |
else if(fileStats.name.split('.').slice(-1)[0] === 'pug'){ | |
const project_path = root.replace(source, '').split('/').filter(p => p.length); | |
let current_level = pug_structure; | |
if(project_path.length === 0){ | |
current_level.files.push(`${root}/${fileStats.name}`); | |
} | |
else { | |
let i = 0; | |
while(i < project_path.length){ | |
if(!current_level.children[project_path[i]]){ | |
current_level.children[project_path[i]] = getStructBit(); | |
} | |
if(i === project_path.length - 1) { | |
current_level.children[project_path[i]].files.push(`${root}/${fileStats.name}`); | |
} | |
else { | |
current_level = current_level.children[project_path[i]]; | |
} | |
i++; | |
} | |
} | |
next(); | |
} | |
}, | |
errors: (a,b,c) => console.log(a,b,c) | |
} | |
} | |
const walker = walk.walkSync(source, walk_options); | |
// console.log(JSON.stringify(md_structure, null, '\t')); | |
// console.log(JSON.stringify(pug_structure, null, '\t')); | |
const locals = Object.assign({}, base_locals, { | |
md_structure: md_structure, | |
pug_structure: pug_structure | |
}); | |
// compile and write md files | |
createDestStructure(`${__dirname}`, md_structure, locals); | |
// compile and write pug files | |
createPugDestStructure(`${__dirname}`, pug_structure, locals); | |
done(); | |
}); | |
gulp.task('default', ['build-site-pages']); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment