DEV Community

Cover image for hafcaf - The No-Framework SPA Solution for Everyone
Andrew Steele
Andrew Steele

Posted on • Updated on

hafcaf - The No-Framework SPA Solution for Everyone

Think about the last time you had to learn a new front end framework. It might have been React, or Vue, or maybe you’ve been scared to learn any of them because of how much ”stuff” you have to learn and do before you even start. Did you know React has 42 pages of “guides” to help developers know the “right” way to program with React? Vue and Angular both have 31. Then there’s the size; it’s common for the final size of your app’s code to be measured in megabytes, meaning your users can wait several seconds before anything displays on the screen.

Don’t even get me started on the ecosystem of build tools.

So there I was, fed up with having to carry in my head these huge frameworks with all their nuances, and I decided there had to be a better way. A way to return to a simpler time, when you didn’t have to have a manual handy to be able to do the simplest tasks. Failing to find what I was looking for, I made my own solution.

hafcaf weighs in at less than 1KB when minified and gzipped. It features only two pages of docs: a tutorial, and the API specifications. It requires you to learn only three methods: init, addRoute, and updateRoute. There’s only one (optional) lifecycle hook: onRender. Best of all, if you don’t want or need any of the JavaScript pieces, you can ditch them and the CSS-based routing function will still work.

hafcaf really does let you have as little JavaScript as you want while offering just enough to make website and web app development a lot less painful. You can check it out here on GitHub.

How the Magic Happens

hafcaf was inspired by an article by Heydon Pickering. Here’s the gist: instead of JavaScript-powered URL re-routing - which often requires server-side directives to redirect all URLs to your index.html file - hafcaf uses CSS-based routing using the :target pseudo-class. This pseudo-class automatically responds to anchor tags that point to internal IDs using the href="#link" syntax.

Don't worry, it's much easier than it sounds. Here's an example. Given this incredibly stripped-down example webpage code:

<main>
  <section id="section1">
    I'm the content for section one.
    <a href="#section2">Section Two</a>
    <a href="#home">Home</a>
  </section>
  <section id="section2">
    I'm the content for section two.
    <a href="#section1">Section One</a>
    <a href="#home">Home</a>
  </section>
  <section id="home">
    I'm the content for the Home page.
    <a href="#section1">Section One</a>
    <a href="#section2">Section Two</a>
  </section>
</main>

Using hafcaf, if you point your browser to yourdomain.com/#section2, then the content for section 2 would show instead of both sections' content. hafcaf does this by hiding all content that isn’t being targeted, effectively showing only one 'page’ of content at a time. By default, hafcaf shows the last block of content; in the example above, “home” would show by default.

Up to this point, we haven't needed to use any JavaScript at all. If static page loading meets you where you're at and provides all you need, then, by all means, don't bother loading the JavaScript file! That's the whole point of this library: you can have as little JavaScript in your site/app as you want, without sacrificing the speed and UX goodness of a SPA.

A Little JavaScript Goes a Long Way

If you don't mind a little bit of JavaScript in your codebase and want some fancy features like dynamic and/or lazy page loading, hafcaf’s JavaScript module gives you what you need - and nothing else.

  • Support for asynchronous/dynamic page loading
  • Support for lazy-loading (only request and load content when the user requests to view it)
  • Automatic population and activation of a navigation menu (e.g. setting a menu item with a .active class)
  • Addition of onRender actions which fire if and when a page is loaded

There are a few different ways to make use of the dynamic page loading functionality, but the key parts boil down to two API methods: addRoute and updateRoute. What these methods do is probably pretty self-explanatory: addRoute adds a route entry to hafcaf's collection of routes, and updateRoute modifies the content and/or settings.

The process basically looks like this: create a ‘page object’ with the basic information about your page, register it with hafcaf using the addRoute function, then go and fetch your page’s content in whichever way you like (I recommend fetch), and then finally call updateRoute with the actual content of the page.

It may seem like a lot of steps, but it’s easier than you might think. hafcaf was designed to handle ‘asynchronous’ content in a very graceful way, that will fit into any flow or framework with ease. Starting with the previous example, let’s add a new page called page 3.

<!-- page3.html -->
<div id="page-three">
    <h1>Page 3</h3>
  <p>Even moar content.</p>
</div>

Note that there’s no extra DOCTYPE, head, or body sections, only the content we want for this new page. This is because we’re going to inject it directly into the existing page, so it doesn’t need all the extra definitions of a normal HTML file.

Next, in your site’s main JavaScript file (or even in the index.html, if you want), set up the hafcaf.addRoute() function with an optional linkLabel property specified:

var exampleDynamicView = {
  id: "page-3",
  linkLabel: "Page 3"
};

hafcaf.addRoute(exampleDynamicView);

This linkLabel property tells hafcaf to add a new entry to the page's menu with the label we specified, "Page 3". This extra property is optional if you want to define your menu in a different way, or if you don't have a menu.

Now that you have a placeholder for your page, go and get the actual content. I prefer using fetch(), so here’s how I’d do it:

fetch("https://yourserver.it/pages/page3.html")
  .then(response => response.text())
  .then(innerHTML => ({ innerHTML, id: "page-3" }))
  .then(page => hafcaf.updateRoute(page));

Note that we process the response as text, then we assign that text to the innerHTML property of the new page object. If we had already had this content - maybe by fetching it earlier - we could have passed this same page object to addRoute() instead of updateRoute(); they work very similarly.

What else?!?

hafcaf has lots more features too, including an onRender() lifecycle hook you can use to set up things like event handlers (e.g. onClick) or subscriptions to streams or web sockets - complete with an exitFunctions collection you can add any disposer functions to. Through a couple of plugin-like additions, you can also get access to hafcaf Barista which helps automate the fetch-and-update pattern I showed you above, and hafcaf Tamper - a simple templating language. Coming soon, I plan to add hafcaf Roaster for handling query params (they basically get ignored right now), and the one I’m most excited about: hafcaf Refill - an implementation of the Observer pattern that will leverage hafcaf Tamper to give you automatically-updating views; just like those fancy frameworks, but with much less cost.

My main goal with hafcaf is to give people a simpler option. hafcaf isn’t meant to replace complex frameworks when you have to make complex applications. You’re not going to be making a 2D side-scroller with it. But for the majority of websites and apps that focus on displaying content with little or no ‘interactive’ parts, hafcaf might just make things simpler and easier.

Check it out here on Github.

Top comments (2)

Collapse
 
andrevrensburg profile image
Andre van Rensburg

Brilliant work! Thanks for sharing!

Collapse
 
heryyustana profile image
Negeri Lucu-lucuan ⚡

Gonna play with this stuff, looks like very promising. Thank you!