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

How I accidentally build a static page generator

By Tobias Hartmann

How I accidentally build a static page generator

In this talk, I'll walk through how I build my own website and blog out of handlebarsjs, with a little help of gulp and node. I'll share my experience about how hard but simple it can be, to build up a personal page and blog. This experience includes thoughts about iterative work, prototype first and splitting of tasks.

  • 3,844