Using
Hexo
and
Angular
to build
A Static Blog
By: Matias Niemelä
matias [at] yearofmoo [dot] com
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!