how I accidentally build a static page generator...
tobias hartmann . @ToH_82
I decided to build a private Homepage
a long long time ago
\o/
tobias hartmann . @ToH_82
How long does it take for a frontend dev to build a website?
It's compilcated
a lot of pain but I survived
the tech stack trap
>.<
tobias hartmann . @ToH_82
I realized, it's content
what really matters
o.O
tobias hartmann . @ToH_82
as simple as possible
as small as possible
o/
tobias hartmann . @ToH_82
SCSS, REACT, GULP, POSTCSS, [buzzwords]
but all the cool stuff
:|
tobias hartmann . @ToH_82
ookay, let's add some tech
I want a blog
^.^
tobias hartmann . @ToH_82
because it makes happy
break it down
:)
tobias hartmann . @ToH_82
how small is small enough?
SIZE OF CHUNKS
AMOUNT
OF TIME
=
what is your experience?
tobias hartmann . @ToH_82
handlebarsjs keeps it simple
warning: code
o.o
tobias hartmann . @ToH_82
partials for page parts
<!-- image within a post -->
<figure class="figure{{#if position}} figure--break-{{position}}{{/if}}">
<img class="figure__image" alt="{{title}}"
src="{{site.url.static}}assets/media/{{image}}" />
<p class="figure__caption">{{caption}}</p>
</figure>
<!-- article list item -->
<div class="card js-hash-filter" data-tags="{{tags}}">
<header class="card-header">
<h2><a href="{{link}}">{{title}}</a> ({{lang}})</h2>
<p>{{author}}</p>
<p>{{date}}</p>
</header>
<div class="card-content">
<div class="inner">
<p>{{intro}}</p>
<a href="{{link}}" class="btn btn-primary btn-ghost"
title="open article: {{title}} ({{lang}})">Open article</a>
</div>
</div>
</div>
helper for the list
module.exports = articleList
var glob = require('glob')
var fs = require('fs')
var cheerio = require('cheerio')
var getDocumentData = require('../document/getData')
var _ = require('underscore')
/**
* @private
* @param {object} articles
* @param {object} options
* @returns {string}
*/
function renderArticleList (articles, options) {
if(articles.length === 0) {
return ''
}
articles = _.sortBy(articles, 'dateCode').reverse()
var articlesHtml = ''
articles.forEach(function (article) {
articlesHtml = articlesHtml + options.fn(article)
})
return articlesHtml
}
/**
* @private
* @param {string} folder
* @returns {Array}
*/
function getArticlesFromFolder (folder) {
var baseFolder = './src/'
var files = glob.sync(baseFolder + folder + '/*.html')
var articles = []
files.forEach(function (file) {
var content = fs.readFileSync(file, 'utf8')
var $ = cheerio.load(content)
articles.push({
title: $('header > h1').text(),
author: $('header > .author').text(),
intro: $('.main > p.intro').text(),
link: file.replace(baseFolder, ''),
lang: $('html').attr('lang'),
date: $('time').text(),
dateCode: $('time').attr('datetime'),
tags: getDocumentData(content, 'tags')
})
})
return articles
}
/**
* @public
* @param {string} folder
* @param {object} options
* @returns {string}
*/
function articleList (folder, options) {
var articles = getArticlesFromFolder(folder)
return renderArticleList(articles, options)
}
but that sounds so complex...
a gulp module
-.-
tobias hartmann . @ToH_82
gulp module... easy
// trigger the data header reader
module.exports = loadData
var through = require('through2')
var extend = require('util')._extend
var getDocumentData = require('./getData')
function loadData () {
return through.obj(function (file, enc, cb) {
var fileContent = file.contents.toString()
if (!file.data) {
file['data'] = {}
}
extend(
file.data,
getDocumentData(fileContent)
)
cb(null, file)
})
}
// read the data header
module.exports = getData
/**
*
* @param {string} content
* @param {string} which
* @returns {object}
*/
function getData (content, which) {
which = which || null
var matches = content.match(/\|-.*.-\|/g)
var docData = {}
if (matches !== null) {
matches.forEach(function (value) {
var tmpData = value
.replace('|-', '')
.replace('-|', '')
.split(':')
docData[tmpData[0]] = tmpData[1]
})
}
if (which !== null) {
return docData[which]
}
return docData
}
// remove the data header
module.exports = removeData
var through = require('through2')
function removeData () {
return through.obj(function (file, enc, cb) {
var fileContent = file.contents.toString()
fileContent = fileContent.replace(/\|-.*.-\|/g, '')
if (file.isBuffer()) {
file.contents = new Buffer(fileContent)
}
cb(null, file)
})
}
how does the gulp look like
var gulp = require('gulp')
gulp.task('hbs', function () {
var handlebars = require('gulp-compile-handlebars')
var loadDocumentData = require('./script/document/loadData')
var removeDocumentData = require('./script/document/removeData')
var pageConfig = require('./src/config.json')
var options = {
ignorePartials: true,
batch: ['./src/partials'],
helpers: {
formatTags: require('./script/helper/formatTags'),
articleList: require('./script/helper/articleList'),
tagNavigation: require('./script/helper/tagNavigation'),
markdown: require('./script/helper/markdown')
}
}
return gulp.src('./src/**/*.html')
.pipe(loadDocumentData())
.pipe(handlebars(pageConfig, options))
.pipe(removeDocumentData())
.pipe(gulp.dest('web'))
})
gulp.task('js', function () {
var uglify = require('gulp-uglify')
var concat = require('gulp-concat')
return gulp.src('src/js/*.js')
.pipe(uglify())
.pipe(concat('script.js'))
.pipe(gulp.dest('web/assets/js'))
})
gulp.task('css', function () {
var postcss = require('gulp-postcss')
var sourcemaps = require('gulp-sourcemaps')
var atImport = require('postcss-import')
var cssnano = require('cssnano')
return gulp.src('./src/css/styles.css')
.pipe(sourcemaps.init())
.pipe(postcss([
atImport({
path: ['./node_modules', './src/css']
}),
require('precss'),
require('autoprefixer'),
cssnano()
]))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('web/assets/css'))
})
gulp.task('default', ['hbs', 'css', 'js'])
gulp.task('watch', function () {
return gulp.watch('./src/*', ['hbs', 'css', 'js'])
})
fullpage template, markdown, easy
|-tags:test,fullpage,template-|
|-headline:headline test-|
|-subline:subline test-|
|-author:Tobias Hartmann-|
{{#> blogArticle}}# This is a Test with a full template
<p>you can put markdown or html for the content, have fun</p>
{{/blogArticle}}
<body class="hack hack--toh">
<div class="topbar">
<div class="container">
<div class="logo">
<object type="image/svg+xml" data="http://static.toh82.dev/assets/logo.svg">TOH</object>
</div>
<nav class="menu menu--horizontal">
<a href="index.html" class="menu-item">» Index/About</a>
<a href="blog.html" class="menu-item">» Blog</a>
</nav>
</div>
</div>
<div class="container">
<header>
<h1>headline test <span class="subline">subline test</span></h1>
<p class="author">von Tobias Hartmann</p>
<time datetime="2017-01-01">01 Januar 2017</time>
<p><a target="_blank" href="https://twitter.com/intent/tweet?url=http://www.toh82.dev/posts/bohnenkaffee-und-was-daraus-wurde.html&text=headline test - by ">Share this article on twitter</a>
</p>
</header>
</div>
<main class="main container">
<h1>This is a Test with a full template</h1>
<p>you can put markdown or html for the content</p>
make it better, iteratively
prototype first
;)
tobias hartmann . @ToH_82
what, if you prototype first?
- faster development
- faster decisions
- more flexibility
- less code for the trashbin
- better estimations
tobias hartmann . @ToH_82
tobias hartmann . @ToH_82
tobias hartmann . @ToH_82
why is refactoring a good thing?
- code quality
- structure
- easy updates
- remember
tobias hartmann . @ToH_82
key to refactoring and iterative work
- transparency
- don't touch everything at once
- measure it
- trust
- focus
if a paper isn't enough anymore
ideas everywhere
:O
tobias hartmann . @ToH_82
waffle.io
tobias hartmann . @ToH_82
realize your great work and party hard
uh, what's this
\o/
tobias hartmann . @ToH_82
keep it simple,
keep it small,
feel great,
make it better,
make it awesome
thanks
tobias hartmann
@ToH_82