ponyfoo.com

The Progressive Web

I’ve blogged very little about Taunus since I first released it, roughly a year ago. Back then, it only powered ponyfoo.com, but now there’s a few cases in the wild where it’s being used, and I even got to do some consulting in a project where they’re using Taunus! In the year since its release, it has had a whooping 174 releases, but not a whole lot has changed, and its API has remained stable for the most part. Its feature-set grew quite a bit, although it remains fairly light-weight at 18.8kB after gzip and minification. Today, it’s able to figure out how to submit forms via AJAX automatically, as long as they already work as plain HTML, and it has support for WebSockets via a plugin.

Improve this article
Nicolás Bevacqua
| 22 minute read | 4

If I were to define Taunus in “elevator pitch” style, I would say:

Taunus is the logical step forward after server-side MVC web frameworks such as Rails or ASP.NET MVC. It turns server-side rendered apps in Node.js (or io.js?) into single-page applications after the initial page load by hijacking link clicks, form submissions, and defining a format you can leverage for realtime communications.

Building an app in a Server-First fashion is important because then you aren’t taking a huge leap of faith in assuming that your customers have a browser capable of supporting all the bleeding edge features your dedicated client-side application demands.

After the initial load, which should be blazing fast so that your customers are happier (tons of research point to this fact), you can should turn to a single page application, hijacking links, making AJAX requests that ask for the bare minimum (view models) and then rendering those view models directly in the client-side.

Why Server-First Matters

Server-First matters because it’s the exact opposite of breaking the web. You establish a bare minimum experience that you know most people can access, a baseline, and you go from there. This baseline isn’t just there for SEO purposes or to be more amicable to people turning off JavaScript.

Think of the ways in which your app is shared on the web. What other places is it rendered to? Services that crawl around it. With client-side rendering, Twitter and Facebook display a pile of garbage instead of descriptive metadata and a thumbnail whenever someone links to your site. Humans might think your site is bogus and not even click on links leading to it, because the description on their Facebook feed just shows a bunch of Mustache templates and gibberish.

Search engines other than Google are completely oblivious to your content. Even Google is not as good at crawling client-side rendered apps as you think they are. Often times, you also get penalized for not being fast enough.

Mobile performance degrades substantially in client-side rendered applications as opposed to those server-side rendered. Both because the connection is slower, and because the scripts you depend on to actually render your site take a long time to download. When they do, mobile devices take longer to parse them and execute them, because they’re not as powerful as the Mac Book Pro you use during development.

Demand Progress!
Demand Progress!

Not doing server-side rendering might be just as bad as not designing a website to be responsive.

It’s about time we .shift() "SEO purposes and <noscript>" from our list of excuses for not doing server-side rendering anymore.

Productivity, Then?

In a sense, it’s all about perceived productivity. If you pause and think about it, we’ve made the shift towards client-side rendering because it’s “more productive”. That’s why the whole “no-backend” approach even exists. If we ignore the poorly named “Offline First” philosophy for a minute, “hey, you can implement this in under five minutes and not even have to spin up a server instance!” sounds like a reasonable goal.

But in reality, how much more productive are using Angular than using something else? What is productivity, really? Is it about saving time, keystrokes? Or is it about standing back, taking a walk, and being able to think a problem through and come up with a solution? Because, let’s face it, some of our most productive engineering sessions happen while we’re in the shower, not due to saving precious milliseconds with our over-engineered build systems (where we actually spend more time than we used to, ironically).

We surely aren’t all just building throw-away prototypes, are we? And even when we do, we are definitely throwing them away, since that’s what they’re for. Right?

In the complex world of front-end development that exists today, why are we even bothering to learn complicated domain languages in order to do something that’s detrimental to the web platform? You could argue it’s more productive, sure.

Productivity only takes you so far, though. There are lots of frameworks out there, and while developing in Angular might be confusingly fun, you don’t really take away a lot from it. There’s all these complicated abstractions that don’t translate very well when you try to move on and use something else. I can almost hear somebody complaining just about now, “Angular’s not that hard”.

Sure, it’s not. But ask yourself, when are you going to ever come across things like directives, transclusion, isolated scopes, and factory service value providers that return a single integer?

You gain very little by specializing in Angular, because once the technology moves on, you’ll have a harder time adapting, since you’ll have to peel back many layers of abstraction until you can get back out to the real world.

What is Taunus?

Taunus latches onto Express or Hapi and lays out a few conventions (which you can configure as per the “Convention over Configuration” paradigm). To get started, you might want to look at the Tutorial in the documentation, or play around with giffy.club, a silly site we built to demonstrate how you can use Taunus to build your apps in a way that makes sense from a UX standpoint while still being server-side rendered.

Just because you’re on an article on Pony Foo, we’ll walk over how rendering an article works, since ponyfoo is open-source. First of all, we have a traditional Express application (you can also use Hapi), with a few routes in it. Then we add a call to taunus-express to mount Taunus in the back-end.

taunusExpress(taunus, app, {
  routes: routes,
  layout: layout,
  getDefaultViewModel: getDefaultViewModel,
  plaintext: {
    root: 'article', ignore: 'footer,.mm-count,.at-meta'
  },
  deferMinified: production
});

Ignore the rest of the options for now, and let’s focus on the routes property. This points to a module where the view routes that are used by Taunus are declared. You’ll notice the exported array follows the patterns in Express when it comes to routing, allowing you to use parameters, wildcards, regular expressions, and whatnot. The routes are ultimately used to set up raw Express GET routes with all the relevant middleware applied to them. You can also supply any extra middleware of your own, besides the controller which is assumed (although optional).

These routes are kept in a module that exports an array because Taunus comes with a CLI that will be used to translate those Express routes into something the front-end understands. This is an important piece of the puzzle, because it means you don’t have to maintain a list of routes for the front-end that’s effectively a duplicate of the routes you use to render views in the server-side.

The route that we care about is /articles/:slug.

{ route: '/articles/:slug', action: 'articles/article' }

This innocent looking route follows a few conventions. Because we indicated that the action is articles/article, Taunus comes up with a few expectations, all of which are optional, that is, these files don’t necessarily have to exist.

  • A server-side controller in $SERVER_CTRL/articles/article.js
  • A view exposed as $VIEWS/articles/article.js
  • A client-side controller in $CLIENT_CTRL/articles/article.js

I intentionally used variables like $SERVER_CTRL to highlight the fact that all of these directories are configurable.

Server-Side Flow

The general idea in server-side controllers is that you’re getting ready to tell Taunus what action to render and what viewModel to use. Pony Foo’s articles/article action controller ends up populating the viewModel property in the response.

A controller example can be found below, inspired by the author/compose action. In this example we set the viewModel that’ll be used but we omit the action. That means the action will be inferred from the route, just like the server-side controller’s location was.

module.exports = function article (req, res, next) {
  res.viewModel = {
    model: {
      articles: [{
        title: 'Stop Breaking the Web',
        description: '...'
      }]
    }
  };
  next();
  };
};

Note also how we’re using next() to fall into Taunuses’ rendering engine.

Once we’ve figured out the viewModel and action, Taunus will internally determine how to render the response, based on request headers and query string. If the request had asked for JSON, then the response is in JSON and it contains the viewModel.model for your partial view. If the request had asked for HTML, then the response will be in HTML, and rendered by using the template functions for both the layout and the partial view. When a request asks for plain text, Taunus renders an HTML response and figures out the plain text.

This article as a plain text response
This article as a plain text response

This article can be rendered as plain text in terminal, just use curl!

Following the progression we’ve laid out, suppose we are going to be handling the first request made by a browser against ponyfoo.com/articles/random. The server-side controller comes into play, decides what the viewModel should be, and it also has the ability to change what view should be rendered.

Then, the view gets rendered. This is done by compiling the view template and then passing that to the layout function as a plain HTML string partial.

Views as Pure Functions in Taunus

Views are expected to be CommonJS modules that export a single method, the view method. Here’s an example view method.

module.exports = function (model) {
  return '<a href=' + model.url + '>' + model.title + '</a>';
};

Security terribleness aside, it can be nice that Taunus doesn’t make lots of demands about view rendering engines, and instead just asks that you somehow write a function(model) that returns an HTML string, and place it in a module at $VIEWS/$ACTION_NAME.

Of course, expecting you to define your views directly using JavaScript functions is kind of bonkers, as it’d be pretty cumbersome and clunky. Fortunately, most templating engines offer the ability to “pre-compile” your views into JavaScript methods, and export those to files. Thus, you can pick your favorite template engine (granted that it can compile views into JavaScript functions), and use that to code up your views for Taunus applications.

I personally like Jade, so I use that. The function above could be expressed in a Jade template as follows:

a(href=url)=title

The problem with Jade is that their compiler inlines entire view templates when you use the include keyword, making compiled views fat. I presume this is because Jade strives to not make assumptions about compiled views being related which would mean they’d be able to use require statements instead of inlining the whole dependency tree. Inlining becomes an issue when you plan to reuse views in a few places, because your views get fat fast and there’s a lot of duplicate code in the compiled templates. I made jadum to solve the duplication issue. It uses require statements pointed at other files when you use include in your Jade templates. Jadum knows a view doesn’t just exist in isolation, but that they are part of a whole and live in the tree structure jadum created on your behalf.

Why does Taunus want pure view functions? Because they offer great simplicity. It’s simple for Taunus to use your views in both the server and the client, since these are just functions that you can require. This way, Taunus doesn’t need to worry about providing its own view engine. There’s plenty to choose from already.

The articles/article.jade template in Pony Foo is compiled into a module using jadum, and then fed into Taunus. This sounds hard, but jadum lets you compile every single template in your app with a single shell command.

jadum views/**/*.jade -o .bin --no-debug --obj '{"basedir":"views"}'

In Pony Foo, we keep compiled view functions in .bin/views, in a tree structure. After Taunus renders a view, it needs to take control of rendering in the client-side, when JavaScript finally loads. This is done in a couple of steps.

The Client-Side “Wiring” Module

First off, we need a build step using the Taunus CLI to build out the “wiring module”. This module is basically a collection of every assumption Taunus is going to make about your app.

Here is how the wiring module looks like for Pony Foo (except in reality it’s actually a bit larger). It follows the CommonJS module spec by convention, but you may produce a standalone <script> version if you’re not using Browserify.

A screenshot of Taunuses' client-side wiring module
A screenshot of Taunuses' client-side wiring module

Why is this file necessary? A couple of reasons. First off, Browserify isn’t able to read dynamic require statements, unless they’re look like require(__dirname + '/foo.js'), and you definitely don’t want to be typing all of these by hand. Secondly, if you’re using Hapi then you can use the hapiify transform to convert Hapi routes into something the front-end router understands. Finally, this file is a clearly defined bridge between your back-end routes and what the front-end is doing.

All you have to do to generate the wiring module is run the following command.

taunus --output

Mounting Taunus in the Front-End

Once we’ve incorporated the wiring module into our build process, we need to .mount Taunus in the front-end, just like we did in the back-end (through taunus-express). Mounting takes a few parameters. It needs a DOM element that exists in your layout where the view should be rendered. It asks for the wiring module we’ve just generated. It also has some options.

taunus.mount(main, wiring, { bootstrap: 'manual' });

The bootstrap option asks the question: “how are you going to get the viewModel.model to the client-side view controller on first load?”

Once client-side JavaScript hits, Taunus is mounted on the client, and it starts to hijack link clicks (and <form> submissions, unless you’ve disabled gradual). Taunus will also immediately execute the client-side view controller for the current action, if one exists. That’s why you need to figure out how to provide the model, because the client-side controller might need it!

When a link is clicked, instead of navigating away like the web regularly does, Taunus will issue an AJAX request. On the server-side your view controller will generate a view model just like it did the first time, and trust Taunus again to render the appropriate response. Taunus will notice the browser is asking for JSON this time around, and respond in kind.

When the AJAX response gets back to the client-side, Taunus will figure out whether the action has been changed by the response, or if it should still use the route’s default. Then, Taunus will render said action’s view template by passing it the model we just received from the AJAX response. Once it has the view’s HTML, it’ll simply render it on the DOM element that was passed to taunus.mount a while back. Finally, the client-side view controller will be invoked, if one exists, just like on first load.

Along the way, Taunus emits events, caches responses (if you want it to), and ensures the server-side is serving data for the view templates cached in the client-side (otherwise you may want to reload the page because of outdated views in the client).

How Much does Taunus Assume?

At first, the only assumption Taunus makes is that it can render views for most of the links that point to the current domain. That is, if there’s a link to, say, /articles/first, it’ll just be a link. Then, when Taunus gets mounted in the client-side, an event handler will be bound to that link. When the link gets clicked, Taunus will check if it matches one of its routes (in this case, it matches the /articles/first route), and if it does, it’ll trigger the AJAX mechanism and use the history API to behave just like a true single page application.

If the history API isn’t supported, then Taunus will fall back to setting location.href, like in the old days. Sure, it’s slower than using hashed routes. But it’s more accessible, and it’s not like there’s that many users in browsers without a functioning history API nowadays anyway. The point is that the site will still work if the history API isn’t there, because Taunus is an upgrade on top of plainly server-side rendered HTML views. This is key if we want to provide the baseline we discussed earlier, and we want to make as few assumptions as possible while doing so.

What About Data Binding?

Taunus doesn’t offer a data binding mechanism out the box (skyrocket is such a mechanism, but it has a rough API at the moment). Instead, it strongly expects you to rely on the progressive web.

The more progressively your site is going to be built, the more it makes sense to use Taunus to build it.

Let’s suppose for a minute that you aren’t such a big proponent on data-binding, because you feel sometimes it’s too magic, confusing, or just because you got so far into this article, or all of the above. What else could we do to lighten the load on your end while providing a progressively enhanced experience where Taunus does the heavy lifting of improving the UX on your behalf while you develop sane plain server-first web apps?

Gradual: Automated AJAX Form Submission

I’ve just recently open-sourced gradual. As we strive to simplify the usability of Taunuses’ API, we’ve incorporated it into the core experience. It provides a mechanism to turn regular HTML forms into AJAX.

This is powerful because we end up with a pure SPA out of just HTML <a> and <form> tags.

For gradual to behave properly, you need to add support to your back-end so that <form> submission responses (these aren’t managed by Taunus) use a Content-Type of text/plain if the request contains as-text as a query string parameter. This is due to formium, which makes form submissions against an <iframe> because it’s better than AJAX (you get a loading spinner and form autofill values are persisted in the browser). Some browsers render application/json responses inside <pre><code> tags or even prettier HTML pages, if the human has some sort of browser extension that does that.

Gradual also lays out a few conventions of its own. See, Taunus is a strong proponent of conventions, as they drastically simplify what you have to do as the implementor. That’s why we build sites in this way. You set up an anchor tag, then it becomes an AJAX request that fetches a model, renders a view, and executes a controller. But you don’t care about how, you just want it to work. Same goes for <form> submissions, you just want them to feel quicker.

Forms usually end up in a redirect. Given that you’re using Taunus, you’re probably using the taunus.redirect API in the server-side for all your form redirection needs. That method is just an abstraction that either does an HTTP redirect or responds with a JSON payload that denotes a redirect. If gradual receives one such response, it’ll make sure to follow the redirect. This is probably the easiest way to handle a form submission’s response. You just redirect somewhere else, if it was made by the browser using plain HTML, then the redirect is followed by the browser, and if Taunus made the submission, then the (JSON) redirect is followed by gradual.

Another very common kind of response is validation. In the traditional web you simply add some messages to the session and discard them after one use (a so-called session “flash”). Gradual is keen on taking these validation messages from a variety of places in your response and displaying them accordingly.

So what is it you should do in a server-side controller that handles a form submission?

  • Flash via session
  • Redirect via Taunus

Taunus will then grab the flash messages (if any) and display them using the partials/form-validation view template.

Realtime Communication with Skyrocket

Here is where things get really interesting. It takes some setup (we’re definitely trying to make this one simpler), but when you do set it up, it’s pretty awesome.

The idea is that a form submission can end up in a view update, instead of a full redirect, because even if the redirect goes through Taunus, it still has to hit the server again. In skyrocket we came up with a schema where you’re able to respond with model updates you want to apply to a model, or operations you want to apply to a certain path on the model.

Consider for example the following response:

{
  updates: [{
    rooms: ['/issues/34'],
    model: {
      title: 'Implement realtime communication via skyrocket'
    },
    operations: [{
      op: 'edit',
      concern: 'todos.1',
      model: {
        completed: true
      }
    }]
  }]
}

This works in a pretty straightforward manner. If the person getting this response to a form submission that marked a TODO as completed, is listening for events on the /issues/34 rooms, he gets back an update that will be automatically applied against the model in his view controller. He’ll then get a callback where he can re-render parts of the view as he deems necessary. The client-side controller might look like this:

var $ = require('dominus');
var skyrocket = require('skyrocket');

module.exports = function controller (model, container, route) {
  var rocket = skyrocket.scope(container, model);

  rocket.on('/issues/34', function reaction () {
    // the model has been updated at this point
    var todos = $.findOne('.todos', container);
    var action = 'issues/todos';
    taunus.partial(todos, action, model);
  });
};

That’ll be enough to apply changes to the view when the model changes, immediately, as the response comes through, without the need for a redirection. We can also do one better. If you’ve configured Skyrocket for realtime, then you’re also able to broadcast this update, using the exact same schema, to other connected clients interested in the /issues/34 room (or “entity”), and they’ll also be able to handle that just as well, because the code is routed through the same handlers, except that the data is getting routed through WebSockets (e.g socket.io) in realtime.

You don’t have magical data binding in Taunus, other than the magical model updates via Skyrocket, but you do get the ability to easily update portions of your view via WebSocket just by implementing an HTML <form> response handler.

That’s pretty rad.

P.S: I’m running a little survey regarding the content direction in this blog, if you’d like to help me with your feedback, come this way.

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments (4)

John Doe wrote

For some reason this is one of the websites where block quotes for the code you write doesn’t show up. Using latest chrome and cleared cache if that helps.

John Doe wrote

Opened a new tab then reloaded the page. Code blocks loaded this time. Odd.

Dan wrote

Using Taunus on a current project and I must say it’s really a cool framework to work with. I love the simplicity of it and the neat way of transferring data from server to client is just awesome.

I can only recommend others to try it out.

#h5yr (hi-five you rock)

/Dan

Nicolas Bevacqua wrote

Sweet! Have you tried out gradual yet?