Using Hexo and Angular to build A Static Blog

By: Matias Niemelä

http://www.yearofmoo.com
matias [at] yearofmoo [dot] com
@yearofmoo

Slides & Code

Slides: http://yom.nu/ng-toronto-hexo-slides

Code: http://yom.nu/ng-toronto-hexo-code

Blogs blogs blogs

What comes to mind first?

Wordpress?

Squarespace?

Server-side blogs

Relies on the backend

With some kind of database

And an admin portal

Pros

Easy to setup

Instant CMS / Admin Interface

Unmanaged Hosts exist

Plugins, plugins, plugins

SEO-friendly

Cons

Things can get pricey

Insecure (admin, DoS)

Slows over time

Lots of bandwidth

But what can we do?

What about static websites?

After all a blog is just HTML right?

Can we just build on the client first?

Static-site Blogs

The blog is built locally

Then uploaded to a server

CDN is then used to proxy it

And the domain is attached to the CDN

Pros

A super simple HTML website

No backend required

Caching + Performance-friendly

Secure, cheap and reliable

Cons

Dynamic content is difficult (no backend)

No CMS backend (you upload yourself)

Each file is a big HTML file

The build can get complex

Let’s go with a static blog

We’re all programmers

So why not rely on ourselves

And build a website powered by a blog

What’s out there

Jekyll (Ruby)

Hakyll (Haskel)

Hugo (Go)

Hexo (JavaScript)

Let’s go with Hexo

JavaScript + NodeJS + Extensibility

Well documented and well designed

Super configurable (templates, permalinks, etc…)

Excellent internal tools (warehouse.js, markdown, etc…)

http://hexo.io/

Hexo is still kinda new

Yes hexo is lacking popularity

But why am I up here talking?

Hexo is the best choice for my website

Just give it some love already

The Hosting Setup

Hexo + S3 + Cloudflare

We use hexo locally to generate our website

Then we build the website

And upload to S3

Which then is served via a CDN

The best of all worlds

Cheap (S3 costs pennies)

Optimized (Cloudflare is amazing)

Testable (the website runs locally)

Extendable (we can do anything locally)

SSL IS FREE (Cloudflare rocks)

Using Hexo

Install `npm install hexo -g`

Run `hexo init`

Then `hexo server`

And goto localhost:4000

Hexo behind the scenes

Hexo runs a dynamic web server in the background

No constant rebuilding

We can also dynamically add / remove content

Content + Templates?

Content + Templates are separated

Which makes hot-swapping themes easy

We can also host our content publicly

And the website can be privately hosted

source/_posts

A regular HTML file with a YAML header

Or we can use Markdown

Let’s see an example

themes/…/layout

Used to store template files

Post.ejs, page.ejs, index.ejs, etc…

Templates can be referenced in posts

Let’s see an example

themes/…/source

Used to store static assets

Images, js files, css files, etc…

Custom Pages

We can make new pages in layouts

And reference them in any source document

And we can also set a permalink directly

Let’s see an example

Going Dynamic

Static content made dynamic == complex

All dynamic code must be pre-generated

Or created on the front-end via JavaScript

What is good for static-sites?

JavaScript == Dynamic Content

We can really use anything

So long as it improves the blog experience

The less code then the better

Let’s make use of Angular

Angular makes static HTML dynamic

And turns websites into web apps

But a blog isn’t a web application

Other reasons why?

Directives keep HTML readable

DI keeps code uncluttered

Form validations

Animations are easy (ngAnimate)

Dynamic code is just template code

Example 1: Building a search

Angular downloads the file

Then lunr.js sets up a search index

Hexo Generator Code

generator.register(function(site, render, callback) { var searchResponse = parsePostsIntoJson(locals.posts); hexo.route.set('/search/results.json', searchResponse); callback(); });

Hexo Generator Code

[ <% posts.forEach(function(post, index) { %> <% if (index > 0) { %>,<% } %> { "title": "<%= post.title %>", "description": "<%- sanitize(post.excerpt) %>", "date": "<%= post.updated.toDate().toISOString() || post.date.toDate().toISOString() %>", "href": "<%= encodeURI(permalink(post.permalink)) %>" } <% }) %> ]

Angular HTTP Code

var ctrl = this; $scope.$watchCollection( function() { return $location.search(); }, function(data) { var q = data.q; searchRequest(q).then(function(results) { ctrl.results = results; }); } );

Angular HTTP Code

.factory('searchRequest', ['$http', '$q', 'searchIndex', function($http, $q, searchIndex) { var index, allResults; return function(q, startIndex) { if (!index) { return downloadJSONFile().then(function() { return performSearch(q, startIndex); }); } return $q.when(performSearch(q, startIndex)); }; }]);

lunr.js Indexing Engine

var index = lunr(function () { this.field('title', {boost: 50}) this.field('description', { boost : 20 }) this.field('tags') this.ref('id') }); return { add : fn, search: fn };

lunr.js Search Code

function fetchResults(q) { var matches = index.search(q); var results = []; for(var i=0;i<matches.length;i++) { var ref = matches[i].ref; results[i] = allResults[ref]; } return results; }

Search Template Code

<div ng-controller="SearchController as searchCtrl"> <div data-hexo-search-results="searchCtrl.results"> Loading search results... </div> </div> <div ng-repeat="result in searchResultsCtrl.results"> <h2> <a ng-href="{{ result.href }}" ng-bind-html="result.title | unsafeHtml"></a> </h2> <p>{{ result.description }}</p> </div>

Example 2: Building a newsletter form

We use ngModel on the inputs

And we submit a form normally

Then we style using CSS classes

And submit to a 3rd party API

Form Code

<form name="newsletterForm" class="newsletter-form row fade-block" data-ng-if="!ctrl.submitted" data-ng-submit="ctrl.submit(newsletterForm.$valid, data)"> <input data-ng-model="data.name" required /> <input data-ng-model="data.email" required /> <button class="submit-button button" ng-disabled="ctrl.loading"> Submit </button> </form>

Form Controller Code

.controller('NewsletterFormController', ['mcSignup', function(mcSignup) { var ctrl = this; this.submit = function(isValid, data) { if (!isValid) return; mcSignup(name[0], name[1], data.email).then(function(message) { ctrl.success = true; ctrl.message = message; ctrl.submitted = true; }).finally(function() { ctrl.loading = false; }); }; }]);

Example 3: Building a code editor

Custom hexo content tag creates an editor

Tag JS code builds HTML data

Angular then hops in

And renders out a component

Content Code

// in the post content My post does this and that .... {% code myExample %} More text ... // myExample.json { "files": [ { "file" : "some-file.html", "type" : "html" }, { "title" : "CSS Code", "file" : "some-file.css" } ] }

Helper JS Code

hexo.extend.tag.register('code', function(args, content, options){ var data = require(file); var html = "\n<div class=\"hexo-editor\">\n"; _.each(data.files, function(file) { html += "<pre class=\"" + file.type + " hexo-editor-slide\"" + " data-type=\"" + file.type + "\"" + " data-title=\"" + file.title + "\"><code>" + file.content + "</code></pre>\n"; }); html += "</div>\n"; return html; });

Angular Directive Code

.directive('hexoEditor', ['syntaxHighlight', function(syntaxHighlight) { return { templateUrl : '/ng-templates/code_editor_tpl.html', transclude: true, link : function(scope, element, attrs, editor) { var slides = element.find('.hexo-editor-slide'); angular.forEach(slides, function(slide) { var file = angular.element(elm).attr('data-title'); editor.files.push(file); }); // highlight and show editor.setCurrent = function(index) {}; } } }])

Angular Directive Template

<!-- /ng-templates/code_editor_tpl.html --> <div class="hexo-editor-nav"> <a ng-repeat="file in editor.files" class="hexo-editor-nav-item" ng-class="{active:editor.current == $index}" href="" ng-click="editor.setCurrent($index)"> {{ file }} </a> </div> <div class="hexo-editor-code" ng-transclude></div>

What About Page Changes?

This stuff requires some work

We capture all link requests

And then use HTML5 history

To keep the URL the same

We can optimize at build

Plainview Directive

// damn $location service won't give up .factory('$location', function() {}) .directive('plainview', ['$window', 'allowClick', 'hexoLocation', 'hexoPage', function($window, allowClick, hexoLocation, hexoPage) { return function(scope, element, attrs) { element.delegate('a', 'click', function(e) { var elm = $(this); var url = elm.attr('href'); if (allowClick(url, url.attr('target'), e) && !elm.hasClass('ignore')) { e.preventDefault(); hexoLocation(url); } }); $($window).on('popstate', function(e) { hexoPage($window.location); }); }; }])

Keeping it SEO friendly

Keep the original blog data the same

Use ng-bind instead of {{curly}}

Use empty directives to load content

Match up the URLs

Where to go from here?

Hexo + Angular is more hands on

But we can build really complex sites

That are super fast and efficient

Just put the time into it!

Slides & Code

Slides: http://yom.nu/ng-toronto-hexo-slides

Code: http://yom.nu/ng-toronto-hexo-code

Thanks!

Twitter: @yearofmoo

Website: www.yearofmoo.com

Email: matias [at] yearofmoo [dot] com